use crate::ir::{ArgType, Argument, Data, Node, TensorData, TensorType};
#[derive(Debug, Clone)]
pub struct ReshapeConfig {
pub shape: ReshapeInput,
}
#[derive(Debug, Clone)]
pub enum ReshapeInput {
Static(Vec<i64>),
Runtime(Argument),
}
pub fn reshape_update_outputs(node: &mut Node) {
let input_info = extract_input_info(&node.inputs[0]);
let output_rank = infer_reshape_output_rank(node);
let static_shape = match &node.outputs[0].ty {
ArgType::Tensor(t) => t.static_shape.clone(),
_ => None,
};
node.outputs[0].ty = determine_output_type(&input_info, output_rank, static_shape, node);
}
struct InputInfo {
elem_type: crate::ElementType,
is_shape: bool,
shape_size: Option<usize>,
}
fn extract_input_info(input: &Argument) -> InputInfo {
match &input.ty {
ArgType::Tensor(tensor) => InputInfo {
elem_type: tensor.elem_type.clone(),
is_shape: false,
shape_size: None,
},
ArgType::Shape(size) => InputInfo {
elem_type: crate::ElementType::Int64,
is_shape: true,
shape_size: Some(*size),
},
_ => panic!(
"Reshape: invalid input type - expected Tensor or Shape, got {:?}",
input.ty
),
}
}
fn determine_output_type(
input_info: &InputInfo,
output_rank: usize,
static_shape: Option<Vec<usize>>,
node: &Node,
) -> ArgType {
if output_rank == 0 {
log::debug!("Reshape node {} outputs a scalar", node.name);
return ArgType::Scalar(input_info.elem_type.clone());
}
if input_info.is_shape && output_rank == 1 && input_info.elem_type == crate::ElementType::Int64
{
let output_size =
calculate_shape_output_size(input_info.shape_size.unwrap_or(1), node, &static_shape);
log::debug!(
"Reshape node {} with Shape({}) input outputs Shape({})",
node.name,
input_info.shape_size.unwrap_or(1),
output_size
);
return ArgType::Shape(output_size);
}
ArgType::Tensor(TensorType {
rank: output_rank,
static_shape,
elem_type: input_info.elem_type.clone(),
})
}
fn calculate_shape_output_size(
input_size: usize,
node: &Node,
static_shape: &Option<Vec<usize>>,
) -> usize {
if let Some(shape_values) = get_static_shape(node)
&& shape_values.len() == 1
{
return match shape_values[0] {
-1 => input_size, n if n > 0 => n as usize,
_ => 1, };
}
if let Some(shape) = static_shape
&& shape.len() == 1
{
return shape[0];
}
input_size
}
fn infer_reshape_output_rank(node: &Node) -> usize {
if let Some(shape) = get_static_shape(node) {
return shape.len();
}
if let Some(rank) = get_rank_from_shape_input(node) {
return rank;
}
if let Some(rank) = get_rank_from_output(node) {
return rank;
}
panic!(
"Reshape node {} has dynamic shape with no rank information available. \
Cannot determine output rank.",
node.name
)
}
fn get_rank_from_shape_input(node: &Node) -> Option<usize> {
if node.inputs.len() != 2 {
return None;
}
match &node.inputs[1].ty {
ArgType::Shape(rank) => Some(*rank),
ArgType::Tensor(tensor) => tensor
.static_shape
.as_ref()
.filter(|dims| !dims.is_empty())
.map(|dims| dims[0]),
_ => None,
}
}
fn get_rank_from_output(node: &Node) -> Option<usize> {
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.static_shape.as_ref().map(|shape| shape.len()),
_ => None,
}
}
fn get_static_shape(node: &Node) -> Option<Vec<i64>> {
if node.inputs.len() == 2
&& let Some(value) = &node.inputs[1].value
&& let Data::Int64s(shape) = &value.data
{
return Some(shape.clone());
}
None
}
pub fn reshape_config(node: &Node) -> ReshapeConfig {
validate_reshape_node(node);
let shape = extract_shape_input(node);
ReshapeConfig { shape }
}
fn validate_reshape_node(node: &Node) {
if node.inputs.len() != 2 {
panic!("Reshape requires exactly 2 inputs");
}
}
fn extract_shape_input(node: &Node) -> ReshapeInput {
match &node.inputs[1].ty {
ArgType::Tensor(_) => extract_tensor_shape(node),
ArgType::Shape(_) => ReshapeInput::Runtime(node.inputs[1].clone()),
_ => panic!("Reshape: second input must be either a Tensor or Shape type"),
}
}
fn extract_tensor_shape(node: &Node) -> ReshapeInput {
match &node.inputs[1].value {
Some(TensorData { data, shape, .. }) => {
assert_eq!(shape.len(), 1, "Reshape: shape tensor must be 1D");
ReshapeInput::Static(data.clone().into_i64s())
}
None => ReshapeInput::Runtime(node.inputs[1].clone()),
}
}
pub fn reshape_config_vec(node: &Node) -> Vec<i64> {
let config = reshape_config(node);
match config.shape {
ReshapeInput::Static(shape) => shape,
ReshapeInput::Runtime(_) => {
panic!("reshape_config_vec cannot be used with runtime shape inputs")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ElementType;
use crate::ir::NodeType;
use crate::node::test_utils::NodeBuilder;
fn create_test_node(allowzero: i64, shape_vec: Vec<i64>) -> Node {
let mut builder = NodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 4, None)
.input_tensor_i64_data("shape", shape_vec.clone(), vec![shape_vec.len()])
.output_tensor_f32("reshaped", 2, None);
if allowzero != 0 {
builder = builder.attr_int("allowzero", allowzero);
}
builder.build()
}
fn create_runtime_reshape_node() -> Node {
NodeBuilder::new(NodeType::Reshape, "test_runtime_reshape")
.input_tensor_f32("data", 2, None)
.input_tensor_i64("shape", 0, None) .output_tensor_f32("reshaped", 2, None)
.build()
}
fn create_reshape_with_shape_input() -> Node {
NodeBuilder::new(NodeType::Reshape, "test_reshape_with_shape")
.input_tensor_f32("data", 4, None)
.add_input("shape", ArgType::Shape(2))
.output_tensor_f32("reshaped", 2, None)
.build()
}
#[test]
fn test_reshape_config_basic() {
let node = create_test_node(0, vec![2, 3]);
let config = reshape_config(&node);
match config.shape {
ReshapeInput::Static(shape) => assert_eq!(shape, vec![2, 3]),
_ => panic!("Expected static shape"),
}
}
#[test]
fn test_reshape_config_allowzero_supported() {
let node = create_test_node(1, vec![2, 3]);
let _ = reshape_config(&node);
}
#[test]
fn test_reshape_config_runtime() {
let node = create_runtime_reshape_node();
let config = reshape_config(&node);
match config.shape {
ReshapeInput::Runtime(arg) => assert_eq!(arg.name, "shape"),
_ => panic!("Expected runtime shape"),
}
}
#[test]
#[should_panic(expected = "Reshape requires exactly 2 inputs")]
fn test_reshape_config_no_shape_input() {
let mut node = create_test_node(0, vec![2, 3]);
node.inputs.pop(); let _ = reshape_config(&node);
}
#[test]
#[should_panic(expected = "shape tensor must be 1D")]
fn test_reshape_config_invalid_shape_dim() {
let mut node = create_test_node(0, vec![2, 3]);
if let Some(tensor_data) = &mut node.inputs[1].value {
tensor_data.shape = vec![2, 1];
}
let _ = reshape_config(&node);
}
#[test]
fn test_reshape_update_outputs_basic() {
let mut node = create_test_node(0, vec![2, 3]);
reshape_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.static_shape, None);
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 2);
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_update_outputs_int() {
let mut node = create_test_node(0, vec![2, 3]);
node.inputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Int32,
rank: 4,
static_shape: None,
});
reshape_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.static_shape, None);
assert_eq!(tensor.elem_type, ElementType::Int32);
assert_eq!(tensor.rank, 2);
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_config_with_shape_type() {
let node = create_reshape_with_shape_input();
let config = reshape_config(&node);
match config.shape {
ReshapeInput::Runtime(arg) => assert_eq!(arg.name, "shape"),
_ => panic!("Expected runtime shape"),
}
}
#[test]
fn test_reshape_update_outputs_with_shape_type() {
let mut node = create_reshape_with_shape_input();
reshape_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.static_shape, None);
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 2); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_to_scalar() {
let mut node = NodeBuilder::new(NodeType::Reshape, "test_reshape_scalar")
.input_tensor_f32("data", 2, None)
.input_tensor_i64_data("shape", vec![], vec![0]) .output_tensor_f32("reshaped", 0, None)
.build();
reshape_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Scalar(elem_type) => {
assert_eq!(*elem_type, ElementType::Float32);
}
_ => panic!("Expected scalar output"),
}
}
}