use std::collections::{BTreeMap, BTreeSet};
use crate::{
IrValue, XlsynthError, dslx,
dslx_bridge::{BridgeBuilder, FunctionParamData, StructMemberData},
ir_value::IrFormatPreference,
};
pub struct RustBridgeBuilder {
lines: Vec<String>,
module_path: Vec<String>,
runner_items: Option<String>,
leading_items: Vec<String>,
emitted_parametric_structs: BTreeSet<String>,
defer_parametric_struct_emission: bool,
target: RustBridgeTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RustBridgeTarget {
StandaloneAot,
PirCompilerNative,
}
#[derive(Debug, Clone)]
pub(crate) struct RustModuleFragment {
pub(crate) path: Vec<String>,
pub(crate) body: String,
}
#[derive(Debug, Clone, Default)]
struct RustModuleNode {
body: Vec<String>,
children: BTreeMap<String, RustModuleNode>,
}
impl RustModuleNode {
fn with_fragment(mut self, path: &[String], body: String) -> Self {
if let Some((module_name, child_path)) = path.split_first() {
let child = self
.children
.remove(module_name)
.unwrap_or_default()
.with_fragment(child_path, body);
self.children.insert(module_name.clone(), child);
self
} else {
self.body.push(body);
self
}
}
fn render_contents(&self) -> String {
self.body
.iter()
.cloned()
.chain(
self.children
.iter()
.map(|(module_name, child)| child.render_module(module_name)),
)
.collect::<Vec<_>>()
.join("\n\n")
}
fn render_module(&self, module_name: &str) -> String {
format!("pub mod {module_name} {{\n{}\n}}", self.render_contents())
}
}
pub(crate) fn render_rust_module_fragments(
fragments: impl IntoIterator<Item = RustModuleFragment>,
) -> String {
fragments
.into_iter()
.fold(RustModuleNode::default(), |root, fragment| {
root.with_fragment(&fragment.path, fragment.body)
})
.render_contents()
}
impl RustBridgeBuilder {
pub fn new() -> Self {
Self {
lines: vec![],
module_path: vec![],
runner_items: None,
leading_items: vec![],
emitted_parametric_structs: BTreeSet::new(),
defer_parametric_struct_emission: false,
target: RustBridgeTarget::StandaloneAot,
}
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn with_pir_compiler_native_target(mut self) -> Self {
self.target = RustBridgeTarget::PirCompilerNative;
self
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn with_runner_items(mut self, runner_items: impl Into<String>) -> Self {
self.runner_items = Some(runner_items.into());
self
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn with_leading_items(
mut self,
leading_items: impl IntoIterator<Item = String>,
) -> Self {
self.leading_items = leading_items.into_iter().collect();
self
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn with_deferred_parametric_struct_emission(mut self) -> Self {
self.defer_parametric_struct_emission = true;
self
}
pub fn build(&self) -> String {
render_rust_module_fragments([
RustModuleFragment {
path: vec![],
body: render_runtime_imports(self.target).to_string(),
},
self.module_fragment(),
])
}
pub(crate) fn module_fragment(&self) -> RustModuleFragment {
RustModuleFragment {
path: self.module_path.clone(),
body: self.lines.join("\n"),
}
}
pub fn rust_type_name(
type_annotation: Option<&dslx::TypeAnnotation>,
ty: &dslx::Type,
) -> Result<String, XlsynthError> {
Self::convert_type_with_annotation(
RustBridgeTarget::StandaloneAot,
&[],
None,
type_annotation,
ty,
)
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn rust_type_name_from_dslx_module(
current_module_name: &str,
type_info: &dslx::TypeInfo,
type_annotation: Option<&dslx::TypeAnnotation>,
ty: &dslx::Type,
) -> Result<String, XlsynthError> {
let module_path = rust_module_path_from_dslx_module_name(current_module_name);
Self::convert_type_with_annotation(
RustBridgeTarget::StandaloneAot,
&module_path,
Some(type_info),
type_annotation,
ty,
)
}
fn convert_type_with_annotation(
target: RustBridgeTarget,
current_module_path: &[String],
type_info: Option<&dslx::TypeInfo>,
type_annotation: Option<&dslx::TypeAnnotation>,
ty: &dslx::Type,
) -> Result<String, XlsynthError> {
if let Some(type_annotation) = type_annotation {
if let Some(array_annotation) = type_annotation.to_array_type_annotation()
&& ty.is_array()
{
let element_annotation = array_annotation.get_element_type();
let element_ty = ty.get_array_element_type();
let rust_ty = Self::convert_type_with_annotation(
target,
current_module_path,
type_info,
Some(&element_annotation),
&element_ty,
)?;
return Ok(format!("[{rust_ty}; {}]", ty.get_array_size()));
}
if let Some(type_ref_annotation) = type_annotation.to_type_ref_type_annotation()
&& let Some(rust_ty) = Self::convert_type_ref_annotation(
current_module_path,
type_info,
&type_ref_annotation,
ty,
)?
{
return Ok(rust_ty);
}
}
if let Some((is_signed, bit_count)) = ty.is_bits_like() {
Ok(bits_rust_type(target, is_signed, bit_count))
} else if ty.is_enum() {
let enum_def = ty.get_enum_def()?;
Ok(enum_def.get_identifier().to_string())
} else if ty.is_struct() {
let struct_def = ty.get_struct_def()?;
Ok(struct_def.get_identifier().to_string())
} else if ty.is_array() {
let array_ty = ty.get_array_element_type();
let array_size = ty.get_array_size();
let rust_ty = Self::convert_type_with_annotation(
target,
current_module_path,
type_info,
None,
&array_ty,
)?;
Ok(format!("[{rust_ty}; {array_size}]"))
} else {
let ty_text = ty.to_string()?;
if ty_text.trim_start().starts_with('(') {
return Ok(parse_concrete_dslx_type_shape(&ty_text)?.rust_type(target));
}
Err(XlsynthError(format!(
"Unsupported type for conversion from DSLX to Rust: {:?}",
ty_text
)))
}
}
fn convert_type_ref_annotation(
current_module_path: &[String],
type_info: Option<&dslx::TypeInfo>,
type_ref_annotation: &dslx::TypeRefTypeAnnotation,
ty: &dslx::Type,
) -> Result<Option<String>, XlsynthError> {
let type_ref = type_ref_annotation.get_type_ref();
let type_definition = type_ref.get_type_definition();
let suffix = Self::parametric_type_suffix(type_info, type_ref_annotation, ty)?;
if let Some(colon_ref) = type_definition.to_colon_ref() {
let attr = format!("{}{}", colon_ref.get_attr(), suffix);
if let Some(import) = colon_ref.resolve_import_subject() {
let module_path = rust_module_path_from_import(&import);
Ok(Some(rust_type_path_between_module_paths(
current_module_path,
&module_path,
&attr,
)))
} else {
Ok(Some(attr))
}
} else {
Ok(type_definition
.to_type_alias()
.map(|alias| alias.get_identifier())
.or_else(|| {
if ty.is_struct() {
ty.get_struct_def()
.ok()
.map(|struct_def| format!("{}{}", struct_def.get_identifier(), suffix))
} else if ty.is_enum() {
ty.get_enum_def()
.ok()
.map(|enum_def| enum_def.get_identifier())
} else {
None
}
}))
}
}
fn parametric_type_suffix(
type_info: Option<&dslx::TypeInfo>,
type_ref_annotation: &dslx::TypeRefTypeAnnotation,
ty: &dslx::Type,
) -> Result<String, XlsynthError> {
let parametric_count = type_ref_annotation.get_parametric_count();
if parametric_count == 0 {
return Ok(String::new());
}
let type_info = type_info.ok_or_else(|| {
XlsynthError(
"DSLX Rust bridge cannot name parametric type without a TypeInfo context"
.to_string(),
)
})?;
if !ty.is_struct() {
return Err(XlsynthError(format!(
"DSLX Rust bridge only supports parametric struct type references, got `{}`",
ty.to_string()?
)));
}
let struct_def = ty.get_struct_def()?;
let binding_count = struct_def.get_parametric_binding_count();
if binding_count != parametric_count {
return Err(XlsynthError(format!(
"DSLX Rust bridge parametric mismatch for `{}`: struct has {binding_count} binding(s), annotation has {parametric_count} argument(s)",
struct_def.get_identifier()
)));
}
let mut parts = Vec::with_capacity(parametric_count);
for index in 0..parametric_count {
let binding = struct_def.get_parametric_binding(index);
let expr = type_ref_annotation.get_parametric_expr(index).ok_or_else(|| {
XlsynthError(format!(
"DSLX Rust bridge does not support type-valued parametric argument {index} for `{}`",
struct_def.get_identifier()
))
})?;
let is_signed = typed_literal_text_is_signed(&expr.to_text());
let value = const_parametric_expr_value(type_info, &expr)?;
parts.push(format!(
"{}_{}",
sanitize_type_parametric_segment(&binding.get_identifier()),
rust_type_parametric_value_suffix(&value, is_signed)?
));
}
Ok(format!("__{}", parts.join("__")))
}
fn emit_concrete_parametric_types_for_type(
&mut self,
type_info: &dslx::TypeInfo,
type_annotation: Option<&dslx::TypeAnnotation>,
ty: &dslx::Type,
) -> Result<(), XlsynthError> {
if let Some(type_annotation) = type_annotation {
if let Some(array_annotation) = type_annotation.to_array_type_annotation()
&& ty.is_array()
{
let element_annotation = array_annotation.get_element_type();
let element_ty = ty.get_array_element_type();
self.emit_concrete_parametric_types_for_type(
type_info,
Some(&element_annotation),
&element_ty,
)?;
return Ok(());
}
if let Some(type_ref_annotation) = type_annotation.to_type_ref_type_annotation() {
self.emit_concrete_parametric_struct_for_type_ref(
type_info,
&type_ref_annotation,
ty,
)?;
}
}
if ty.is_array() {
let element_ty = ty.get_array_element_type();
self.emit_concrete_parametric_types_for_type(type_info, None, &element_ty)?;
}
Ok(())
}
fn emit_concrete_parametric_struct_for_type_ref(
&mut self,
type_info: &dslx::TypeInfo,
type_ref_annotation: &dslx::TypeRefTypeAnnotation,
ty: &dslx::Type,
) -> Result<(), XlsynthError> {
if type_ref_annotation.get_parametric_count() == 0 {
return Ok(());
}
if self.defer_parametric_struct_emission {
return Ok(());
}
let Some(rust_ty) = Self::convert_type_ref_annotation(
&self.module_path,
Some(type_info),
type_ref_annotation,
ty,
)?
else {
return Ok(());
};
if rust_ty.contains("::") {
return Err(XlsynthError(format!(
"DSLX Rust bridge does not support direct imported parametric struct instantiation `{rust_ty}`; define and use a concrete type alias in the imported module"
)));
}
if !self.emitted_parametric_structs.insert(rust_ty.clone()) {
return Ok(());
}
let struct_def = ty.get_struct_def()?;
let concrete_member_count = ty.get_struct_member_count();
let definition_member_count = struct_def.get_member_count();
if concrete_member_count != definition_member_count {
return Err(XlsynthError(format!(
"DSLX Rust bridge parametric struct `{}` has {definition_member_count} definition member(s) but {concrete_member_count} concrete member type(s)",
struct_def.get_identifier()
)));
}
let mut field_lines = Vec::with_capacity(concrete_member_count);
for index in 0..concrete_member_count {
let member = struct_def.get_member(index);
let field_annotation = member.get_type();
let field_ty = ty.get_struct_member_type(index);
self.emit_concrete_parametric_types_for_type(
type_info,
Some(&field_annotation),
&field_ty,
)?;
let field_rust_ty = Self::convert_type_with_annotation(
self.target,
&self.module_path,
Some(type_info),
Some(&field_annotation),
&field_ty,
)?;
field_lines.push(format!(" pub {}: {},", member.get_name(), field_rust_ty));
}
self.lines
.push("#[allow(non_camel_case_types)]".to_string());
self.push_struct_representation();
self.lines.push(format!("pub struct {rust_ty} {{"));
self.lines.extend(field_lines);
self.lines.push("}\n".to_string());
Ok(())
}
}
impl RustBridgeBuilder {
fn push_struct_representation(&mut self) {
if self.target == RustBridgeTarget::PirCompilerNative {
self.lines.push("#[repr(C)]".to_string());
self.lines
.push("#[derive(Debug, Clone, Copy, PartialEq, Eq)]".to_string());
} else {
self.lines
.push("#[derive(Debug, Clone, PartialEq, Eq)]".to_string());
}
}
}
pub(crate) fn render_standalone_runtime_imports() -> &'static str {
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
"#
}
pub(crate) fn render_pir_compiler_native_runtime_imports() -> &'static str {
r#"pub use xlsynth_pir_compiler_runtime::{
BitsInU8, BitsInU16, BitsInU32, BitsInU64, Token, WideBits,
};
"#
}
fn render_runtime_imports(target: RustBridgeTarget) -> &'static str {
match target {
RustBridgeTarget::StandaloneAot => render_standalone_runtime_imports(),
RustBridgeTarget::PirCompilerNative => render_pir_compiler_native_runtime_imports(),
}
}
fn bits_rust_type(target: RustBridgeTarget, is_signed: bool, bit_count: usize) -> String {
match target {
RustBridgeTarget::StandaloneAot => {
if is_signed {
format!("SBits<{bit_count}>")
} else {
format!("UBits<{bit_count}>")
}
}
RustBridgeTarget::PirCompilerNative => native_bits_rust_type(bit_count),
}
}
pub(crate) fn native_bits_rust_type(bit_count: usize) -> String {
match bit_count {
1..=8 => format!("BitsInU8<{bit_count}>"),
9..=16 => format!("BitsInU16<{bit_count}>"),
17..=32 => format!("BitsInU32<{bit_count}>"),
33..=64 => format!("BitsInU64<{bit_count}>"),
_ => format!("WideBits<{bit_count}, {}>", bit_count.div_ceil(64)),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ConcreteDslxTypeShape {
Bits {
is_signed: bool,
bit_count: usize,
},
Tuple {
elements: Vec<ConcreteDslxTypeShape>,
},
Array {
size: usize,
element: Box<ConcreteDslxTypeShape>,
},
}
impl ConcreteDslxTypeShape {
pub(crate) fn rust_type(&self, target: RustBridgeTarget) -> String {
match self {
ConcreteDslxTypeShape::Bits {
is_signed,
bit_count,
} => bits_rust_type(target, *is_signed, *bit_count),
ConcreteDslxTypeShape::Tuple { elements } => match elements.as_slice() {
[] => "()".to_string(),
[element] => format!("({},)", element.rust_type(target)),
_ => format!(
"({})",
elements
.iter()
.map(|element| element.rust_type(target))
.collect::<Vec<_>>()
.join(", ")
),
},
ConcreteDslxTypeShape::Array { size, element } => {
format!("[{}; {size}]", element.rust_type(target))
}
}
}
}
pub(crate) fn parse_concrete_dslx_type_shape(
text: &str,
) -> Result<ConcreteDslxTypeShape, XlsynthError> {
let mut parser = ConcreteDslxTypeShapeParser { text, offset: 0 };
let shape = parser.parse_type()?;
parser.skip_ws();
if parser.is_eof() {
Ok(shape)
} else {
Err(XlsynthError(format!(
"unsupported concrete DSLX type shape near `{}` in `{text}`",
&text[parser.offset..]
)))
}
}
struct ConcreteDslxTypeShapeParser<'a> {
text: &'a str,
offset: usize,
}
impl ConcreteDslxTypeShapeParser<'_> {
fn is_eof(&self) -> bool {
self.offset >= self.text.len()
}
fn skip_ws(&mut self) {
while self
.text
.as_bytes()
.get(self.offset)
.is_some_and(u8::is_ascii_whitespace)
{
self.offset += 1;
}
}
fn peek(&mut self) -> Option<u8> {
self.skip_ws();
self.text.as_bytes().get(self.offset).copied()
}
fn consume_char(&mut self, expected: u8) -> bool {
self.skip_ws();
if self.text.as_bytes().get(self.offset).copied() == Some(expected) {
self.offset += 1;
true
} else {
false
}
}
fn consume_str(&mut self, expected: &str) -> bool {
self.skip_ws();
if self.text[self.offset..].starts_with(expected) {
self.offset += expected.len();
true
} else {
false
}
}
fn expect_char(&mut self, expected: u8) -> Result<(), XlsynthError> {
if self.consume_char(expected) {
Ok(())
} else {
Err(XlsynthError(format!(
"expected `{}` while parsing concrete DSLX type shape `{}`",
expected as char, self.text
)))
}
}
fn parse_usize(&mut self) -> Result<usize, XlsynthError> {
self.skip_ws();
let start = self.offset;
while self
.text
.as_bytes()
.get(self.offset)
.is_some_and(u8::is_ascii_digit)
{
self.offset += 1;
}
if self.offset == start {
return Err(XlsynthError(format!(
"expected decimal number while parsing concrete DSLX type shape `{}`",
self.text
)));
}
self.text[start..self.offset]
.parse::<usize>()
.map_err(|error| {
XlsynthError(format!(
"invalid decimal number `{}` in concrete DSLX type shape `{}`: {error}",
&self.text[start..self.offset],
self.text
))
})
}
fn parse_type(&mut self) -> Result<ConcreteDslxTypeShape, XlsynthError> {
let mut shape = self.parse_atom()?;
while self.consume_char(b'[') {
let size = self.parse_usize()?;
self.expect_char(b']')?;
shape = ConcreteDslxTypeShape::Array {
size,
element: Box::new(shape),
};
}
Ok(shape)
}
fn parse_atom(&mut self) -> Result<ConcreteDslxTypeShape, XlsynthError> {
if self.consume_char(b'(') {
let mut elements = Vec::new();
if self.consume_char(b')') {
return Ok(ConcreteDslxTypeShape::Tuple { elements });
}
loop {
elements.push(self.parse_type()?);
if self.consume_char(b')') {
break;
}
self.expect_char(b',')?;
if self.consume_char(b')') {
break;
}
}
return Ok(ConcreteDslxTypeShape::Tuple { elements });
}
if self.consume_str("uN") {
return self.parse_bits( false);
}
if self.consume_str("sN") {
return self.parse_bits( true);
}
if self.consume_str("bits") {
return self.parse_bits( false);
}
if self.peek() == Some(b'u') || self.peek() == Some(b's') {
let is_signed = self.consume_char(b's');
if !is_signed {
self.expect_char(b'u')?;
}
let bit_count = self.parse_usize()?;
return Ok(ConcreteDslxTypeShape::Bits {
is_signed,
bit_count,
});
}
Err(XlsynthError(format!(
"unsupported concrete DSLX type shape near `{}` in `{}`",
&self.text[self.offset..],
self.text
)))
}
fn parse_bits(&mut self, is_signed: bool) -> Result<ConcreteDslxTypeShape, XlsynthError> {
self.expect_char(b'[')?;
let bit_count = self.parse_usize()?;
self.expect_char(b']')?;
Ok(ConcreteDslxTypeShape::Bits {
is_signed,
bit_count,
})
}
}
fn const_parametric_expr_value(
type_info: &dslx::TypeInfo,
expr: &dslx::Expr,
) -> Result<IrValue, XlsynthError> {
if let Ok(value) =
dslx::InterpValue::from_string(&expr.to_text()).and_then(|value| value.convert_to_ir())
{
return Ok(value);
}
type_info.get_const_expr(expr)?.convert_to_ir()
}
fn typed_literal_text_is_signed(text: &str) -> bool {
let text = text.trim();
text.starts_with('s') || text.starts_with("-")
}
impl Default for RustBridgeBuilder {
fn default() -> Self {
Self::new()
}
}
impl BridgeBuilder for RustBridgeBuilder {
fn start_module(&mut self, module_name: &str) -> Result<(), XlsynthError> {
self.module_path = rust_module_path_from_dslx_module_name(module_name);
self.emitted_parametric_structs.clear();
let root = std::iter::repeat_n("super", self.module_path.len())
.collect::<Vec<_>>()
.join("::");
let imports = if root.is_empty() {
String::new()
} else if self.target == RustBridgeTarget::PirCompilerNative {
format!("use {root}::{{BitsInU8, BitsInU16, BitsInU32, BitsInU64, Token, WideBits}};\n")
} else {
format!(
"use {root}::{{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits}};\n"
)
};
self.lines = vec![
"#![allow(dead_code)]".to_string(),
"#![allow(unused_imports)]".to_string(),
imports,
];
self.lines.extend(self.leading_items.clone());
Ok(())
}
fn end_module(&mut self, module_name: &str) -> Result<(), XlsynthError> {
let _ = module_name;
if let Some(runner_items) = &self.runner_items {
self.lines.push(runner_items.clone());
}
Ok(())
}
fn add_enum_def(
&mut self,
dslx_name: &str,
is_signed: bool,
underlying_bit_count: usize,
members: &[(String, IrValue)],
) -> Result<(), XlsynthError> {
if self.target == RustBridgeTarget::PirCompilerNative {
if !(1..=64).contains(&underlying_bit_count) {
return Err(XlsynthError(format!(
"DSLX Rust bridge does not yet support native enum `{dslx_name}` with bits[{underlying_bit_count}] storage"
)));
}
let carrier = native_bits_rust_type(underlying_bit_count);
let carrier_expr = carrier.replacen('<', "::<", 1);
self.lines.push("#[repr(transparent)]".to_string());
self.lines
.push("#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]".to_string());
self.lines
.push(format!("pub struct {dslx_name}(pub {carrier});"));
self.lines.push(format!("impl {dslx_name} {{"));
for (name, value) in members {
let value = value.to_bits()?.to_u64()?;
self.lines
.push(" #[allow(non_upper_case_globals)]".to_string());
self.lines.push(format!(
" pub const {name}: Self = Self({carrier_expr}::wrapping({value}));"
));
}
self.lines.push("}\n".to_string());
return Ok(());
}
let value_to_string = |value: &IrValue| -> Result<String, XlsynthError> {
if is_signed {
value.to_i64().map(|v| v.to_string())
} else {
value.to_u64().map(|v| v.to_string())
}
};
self.lines
.push("#[derive(Debug, Clone, Copy, PartialEq, Eq)]".to_string());
self.lines.push(format!("pub enum {dslx_name} {{"));
for (name, value) in members.iter() {
self.lines
.push(format!(" {} = {},", name, value_to_string(value)?));
}
self.lines.push("}\n".to_string());
Ok(())
}
fn add_struct_def(
&mut self,
dslx_name: &str,
members: &[StructMemberData],
) -> Result<(), XlsynthError> {
self.push_struct_representation();
self.lines.push(format!("pub struct {dslx_name} {{"));
for member in members {
let rust_ty = Self::convert_type_with_annotation(
self.target,
&self.module_path,
None,
Some(&member.type_annotation),
&member.concrete_type,
)?;
self.lines
.push(format!(" pub {}: {},", member.name, rust_ty));
}
self.lines.push("}\n".to_string());
Ok(())
}
fn add_struct_def_typed(
&mut self,
dslx_name: &str,
type_info: &dslx::TypeInfo,
members: &[StructMemberData],
) -> Result<(), XlsynthError> {
for member in members {
self.emit_concrete_parametric_types_for_type(
type_info,
Some(&member.type_annotation),
&member.concrete_type,
)?;
}
self.push_struct_representation();
self.lines.push(format!("pub struct {dslx_name} {{"));
for member in members {
let rust_ty = Self::convert_type_with_annotation(
self.target,
&self.module_path,
Some(type_info),
Some(&member.type_annotation),
&member.concrete_type,
)?;
self.lines
.push(format!(" pub {}: {},", member.name, rust_ty));
}
self.lines.push("}\n".to_string());
Ok(())
}
fn add_alias(
&mut self,
dslx_name: &str,
type_annotation: &dslx::TypeAnnotation,
concrete_type: &dslx::Type,
) -> Result<(), XlsynthError> {
let rust_ty = Self::convert_type_with_annotation(
self.target,
&self.module_path,
None,
Some(type_annotation),
concrete_type,
)?;
self.lines
.push(format!("pub type {dslx_name} = {rust_ty};\n"));
Ok(())
}
fn add_alias_typed(
&mut self,
dslx_name: &str,
type_info: &dslx::TypeInfo,
type_annotation: &dslx::TypeAnnotation,
concrete_type: &dslx::Type,
) -> Result<(), XlsynthError> {
self.emit_concrete_parametric_types_for_type(
type_info,
Some(type_annotation),
concrete_type,
)?;
let rust_ty = Self::convert_type_with_annotation(
self.target,
&self.module_path,
Some(type_info),
Some(type_annotation),
concrete_type,
)?;
self.lines
.push(format!("pub type {dslx_name} = {rust_ty};\n"));
Ok(())
}
fn add_function_signature_typed(
&mut self,
_dslx_name: &str,
type_info: &dslx::TypeInfo,
params: &[FunctionParamData],
return_type_annotation: Option<&dslx::TypeAnnotation>,
return_type: Option<&dslx::Type>,
) -> Result<(), XlsynthError> {
for param in params {
if let Some(concrete_type) = ¶m.concrete_type {
self.emit_concrete_parametric_types_for_type(
type_info,
Some(¶m.type_annotation),
concrete_type,
)?;
}
}
if let Some(return_type) = return_type {
self.emit_concrete_parametric_types_for_type(
type_info,
return_type_annotation,
return_type,
)?;
}
Ok(())
}
fn add_constant(
&mut self,
_name: &str,
_constant_def: &dslx::ConstantDef,
_ty: &dslx::Type,
_ir_value: &IrValue,
) -> Result<(), XlsynthError> {
Ok(())
}
}
pub(crate) fn rust_module_path_from_dslx_module_name(module_name: &str) -> Vec<String> {
module_name
.split('.')
.filter(|segment| !segment.is_empty())
.map(sanitize_module_segment)
.collect()
}
fn rust_module_path_from_import(import: &dslx::Import) -> Vec<String> {
import
.get_subject()
.iter()
.map(|segment| sanitize_module_segment(segment))
.collect()
}
#[cfg(any(feature = "standalone-aot", feature = "pir-compiler-dslx-aot"))]
pub(crate) fn rust_type_path_between_dslx_modules(
current_module_name: &str,
target_module_name: &str,
type_name: &str,
) -> String {
let current_path = rust_module_path_from_dslx_module_name(current_module_name);
let target_path = rust_module_path_from_dslx_module_name(target_module_name);
rust_type_path_between_module_paths(¤t_path, &target_path, type_name)
}
pub(crate) fn rust_type_path_between_module_paths(
current_module_path: &[String],
target_module_path: &[String],
type_name: &str,
) -> String {
if current_module_path == target_module_path {
type_name.to_string()
} else {
std::iter::repeat_n("super".to_string(), current_module_path.len())
.chain(target_module_path.iter().cloned())
.chain([type_name.to_string()])
.collect::<Vec<_>>()
.join("::")
}
}
fn sanitize_module_segment(segment: &str) -> String {
segment
.chars()
.map(|ch| {
if ch == '_' || ch.is_ascii_alphanumeric() {
ch
} else {
'_'
}
})
.collect()
}
fn sanitize_type_parametric_segment(segment: &str) -> String {
let mut out = String::with_capacity(segment.len());
for (index, ch) in segment.chars().enumerate() {
let valid = ch == '_' || ch.is_ascii_alphanumeric();
let ch = if valid { ch } else { '_' };
if index == 0 && ch.is_ascii_digit() {
out.push('_');
}
out.push(ch);
}
if out.is_empty() { "P".to_string() } else { out }
}
fn rust_type_parametric_value_suffix(
value: &IrValue,
is_signed: bool,
) -> Result<String, XlsynthError> {
let format = if is_signed {
IrFormatPreference::SignedDecimal
} else {
IrFormatPreference::UnsignedDecimal
};
let value = value.to_string_fmt_no_prefix(format)?;
Ok(sanitize_type_parametric_value_segment(&value))
}
fn sanitize_type_parametric_value_segment(value: &str) -> String {
let value = value.trim();
if let Some(value) = value.strip_prefix('-') {
format!("m{}", sanitize_type_parametric_value_atom(value))
} else {
sanitize_type_parametric_value_atom(value)
}
}
fn sanitize_type_parametric_value_atom(value: &str) -> String {
let out = value
.chars()
.map(|ch| {
if ch == '_' || ch.is_ascii_alphanumeric() {
ch
} else {
'_'
}
})
.collect::<String>();
if out.is_empty() { "P".to_string() } else { out }
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::dslx_bridge::{convert_imported_module, convert_leaf_module};
use super::*;
#[test]
fn concrete_dslx_type_shape_parses_nested_tuple_arrays() {
let shape = parse_concrete_dslx_type_shape("(uN[8], (sN[4], bits[1])[2])").unwrap();
assert_eq!(
shape.rust_type(RustBridgeTarget::StandaloneAot),
"(UBits<8>, [(SBits<4>, UBits<1>); 2])"
);
assert_eq!(
shape.rust_type(RustBridgeTarget::PirCompilerNative),
"(BitsInU8<8>, [(BitsInU8<4>, BitsInU8<1>); 2])"
);
}
#[test]
fn test_convert_leaf_module_enum_def_only() {
let dslx = r#"
enum MyEnum : u2 { A = 0, B = 3 }
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MyEnum {
A = 0,
B = 3,
}
}"#
);
}
#[test]
fn test_convert_leaf_module_struct_def_only() {
let dslx = r#"
struct MyStruct {
a: u32,
b: s16,
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: UBits<32>,
pub b: SBits<16>,
}
}"#
);
}
#[test]
fn test_convert_leaf_module_struct_with_enum_field() {
let dslx = r#"
enum MyEnum : u2 { A = 0, B = 3 }
struct MyStruct {
a: MyEnum,
b: s16,
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MyEnum {
A = 0,
B = 3,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: MyEnum,
pub b: SBits<16>,
}
}"#
);
}
#[test]
fn test_convert_leaf_module_nested_struct() {
let dslx = r#"
struct MyInnerStruct {
x: u8,
y: u8,
}
struct MyStruct {
a: u32,
b: s16,
c: MyInnerStruct,
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyInnerStruct {
pub x: UBits<8>,
pub y: UBits<8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: UBits<32>,
pub b: SBits<16>,
pub c: MyInnerStruct,
}
}"#
);
}
#[test]
fn test_convert_leaf_module_struct_with_array() {
let dslx = r#"
struct MyStruct {
a: u32,
b: s16,
c: u8[4],
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: UBits<32>,
pub b: SBits<16>,
pub c: [UBits<8>; 4],
}
}"#
);
}
#[test]
fn test_convert_leaf_module_type_alias_to_concrete_bits_alias_only() {
let dslx = "type MyType = u8;";
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
pub type MyType = UBits<8>;
}"#
);
}
#[test]
fn test_function_signatures_do_not_emit_aliases() {
let dslx = r#"
type Count = u8;
struct ResultStruct { value: Count }
pub fn do_frob(count: Count) -> ResultStruct {
ResultStruct { value: count }
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut default_builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut default_builder).unwrap();
let output = default_builder.build();
assert!(!output.contains("DoFrobCount"));
assert!(!output.contains("DoFrobReturn"));
}
#[test]
fn test_struct_with_extern_type_ref_member() {
let imported_dslx = "pub struct MyImportedStruct { a: u8 }";
let importer_dslx = "import imported; struct MyStruct { a: imported::MyImportedStruct }";
let mut import_data = dslx::ImportData::default();
let _imported_typechecked =
dslx::parse_and_typecheck(imported_dslx, "imported.x", "imported", &mut import_data)
.unwrap();
let importer_typechecked =
dslx::parse_and_typecheck(importer_dslx, "importer.x", "importer", &mut import_data)
.unwrap();
let mut builder = RustBridgeBuilder::new();
convert_imported_module(&importer_typechecked, &mut builder).unwrap();
assert_eq!(
builder.build(),
"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod importer {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: super::imported::MyImportedStruct,
}
}"
);
}
#[test]
fn test_struct_with_extern_type_ref_array_member() {
let imported_dslx = "pub struct MyImportedStruct { a: u8 }";
let importer_dslx = "import imported; struct MyStruct { a: imported::MyImportedStruct[2] }";
let mut import_data = dslx::ImportData::default();
let _imported_typechecked =
dslx::parse_and_typecheck(imported_dslx, "imported.x", "imported", &mut import_data)
.unwrap();
let importer_typechecked =
dslx::parse_and_typecheck(importer_dslx, "importer.x", "importer", &mut import_data)
.unwrap();
let mut builder = RustBridgeBuilder::new();
convert_imported_module(&importer_typechecked, &mut builder).unwrap();
assert_eq!(
builder.build(),
"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod importer {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: [super::imported::MyImportedStruct; 2],
}
}"
);
}
#[test]
fn test_struct_with_dotted_import_alias_type_ref_member() {
let imported_dslx = "pub struct MyImportedStruct { a: u8 }";
let importer_dslx =
"import common.logic.imported as ext; struct MyStruct { a: ext::MyImportedStruct }";
let tmpdir = xlsynth_test_helpers::make_test_tmpdir("xlsynth_rust_bridge_builder_test");
let common_logic_dir = tmpdir.path().join("common/logic");
std::fs::create_dir_all(&common_logic_dir).unwrap();
let imported_path = common_logic_dir.join("imported.x");
std::fs::write(&imported_path, imported_dslx).unwrap();
let importer_path = tmpdir.path().join("importer.x");
std::fs::write(&importer_path, importer_dslx).unwrap();
let mut import_data = dslx::ImportData::new(None, &[tmpdir.path()]);
let importer_typechecked = dslx::parse_and_typecheck(
importer_dslx,
importer_path.to_str().unwrap(),
"importer",
&mut import_data,
)
.unwrap();
let mut builder = RustBridgeBuilder::new();
convert_imported_module(&importer_typechecked, &mut builder).unwrap();
assert_eq!(
builder.build(),
"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod importer {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: super::common::logic::imported::MyImportedStruct,
}
}"
);
}
#[test]
fn test_struct_with_dotted_import_alias_type_ref_array_member() {
let imported_dslx = "pub struct MyImportedStruct { a: u8 }";
let importer_dslx =
"import common.logic.imported as ext; struct MyStruct { a: ext::MyImportedStruct[2] }";
let tmpdir = xlsynth_test_helpers::make_test_tmpdir("xlsynth_rust_bridge_builder_test");
let common_logic_dir = tmpdir.path().join("common/logic");
std::fs::create_dir_all(&common_logic_dir).unwrap();
let imported_path = common_logic_dir.join("imported.x");
std::fs::write(&imported_path, imported_dslx).unwrap();
let importer_path = tmpdir.path().join("importer.x");
std::fs::write(&importer_path, importer_dslx).unwrap();
let mut import_data = dslx::ImportData::new(None, &[tmpdir.path()]);
let importer_typechecked = dslx::parse_and_typecheck(
importer_dslx,
importer_path.to_str().unwrap(),
"importer",
&mut import_data,
)
.unwrap();
let mut builder = RustBridgeBuilder::new();
convert_imported_module(&importer_typechecked, &mut builder).unwrap();
assert_eq!(
builder.build(),
"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod importer {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MyStruct {
pub a: [super::common::logic::imported::MyImportedStruct; 2],
}
}"
);
}
#[test]
fn test_parametric_struct_alias_emits_concrete_struct() {
let dslx = r#"
struct Box<N: u32> {
value: bits[N],
}
type Box8 = Box<u32:8>;
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Box__N_8 {
pub value: UBits<8>,
}
pub type Box8 = Box__N_8;
}"#
);
}
#[test]
fn test_parametric_struct_function_signature_emits_concrete_struct() {
let dslx = r#"
struct Box<N: u32> {
value: bits[N],
}
pub fn echo_box(x: Box<u32:8>) -> Box<u32:8> {
x
}
"#;
let mut import_data = dslx::ImportData::default();
let path = std::path::PathBuf::from_str("/memfile/my_module.x").unwrap();
let mut builder = RustBridgeBuilder::new();
convert_leaf_module(&mut import_data, dslx, &path, &mut builder).unwrap();
assert_eq!(
builder.build(),
r#"pub use xlsynth_aot_runtime::{
AotArtifactMetadata, AotError, AotRunResult, SBits, UBits,
};
#[allow(unused_imports)]
use xlsynth_aot_runtime::{
read_leaf_element, write_leaf_element, AotElementLayout, AotRunnerLayout, StandaloneRunner,
};
pub mod my_module {
#![allow(dead_code)]
#![allow(unused_imports)]
use super::{read_leaf_element, write_leaf_element, AotArtifactMetadata, AotElementLayout, AotError, AotRunResult, AotRunnerLayout, SBits, StandaloneRunner, UBits};
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Box__N_8 {
pub value: UBits<8>,
}
}"#
);
}
#[test]
fn test_direct_imported_parametric_struct_reference_errors() {
let imported_dslx = r#"
pub struct RemoteBox<N: u32> {
value: bits[N],
}
"#;
let importer_dslx = r#"
import imported;
pub fn echo_box(x: imported::RemoteBox<u32:8>) -> imported::RemoteBox<u32:8> {
x
}
"#;
let mut import_data = dslx::ImportData::default();
let _imported_typechecked =
dslx::parse_and_typecheck(imported_dslx, "imported.x", "imported", &mut import_data)
.unwrap();
let importer_typechecked =
dslx::parse_and_typecheck(importer_dslx, "importer.x", "importer", &mut import_data)
.unwrap();
let mut builder = RustBridgeBuilder::new();
let error = convert_imported_module(&importer_typechecked, &mut builder).unwrap_err();
assert!(
error
.to_string()
.contains("direct imported parametric struct instantiation")
);
assert!(error.to_string().contains("concrete type alias"));
}
}