use cranelift::prelude::types;
use shape_value::heap_value::HeapKind;
use shape_value::v2::ConcreteType;
use shape_vm::mir::types::*;
use shape_vm::type_tracking::NativeKind;
pub(crate) fn is_heap_type(type_info: &LocalTypeInfo) -> bool {
matches!(type_info, LocalTypeInfo::NonCopy)
}
pub(crate) fn is_copy_type(type_info: &LocalTypeInfo) -> bool {
matches!(type_info, LocalTypeInfo::Copy)
}
pub(crate) fn slot_kind_for_local(
slot_kinds: &[Option<NativeKind>],
slot_idx: u16,
) -> Option<NativeKind> {
slot_kinds.get(slot_idx as usize).copied().flatten()
}
pub(crate) fn is_i32_slot(kind: NativeKind) -> bool {
matches!(kind, NativeKind::Int32 | NativeKind::UInt32)
}
#[allow(dead_code)]
pub(crate) fn is_native_slot(kind: NativeKind) -> bool {
matches!(
kind,
NativeKind::Float64
| NativeKind::Int32
| NativeKind::UInt32
| NativeKind::Bool
| NativeKind::Int8
| NativeKind::UInt8
| NativeKind::Int16
| NativeKind::UInt16
)
}
pub(crate) fn cranelift_type_for_slot(kind: NativeKind) -> cranelift::prelude::Type {
match kind {
NativeKind::Float64 => types::F64,
NativeKind::Int32 | NativeKind::UInt32 => types::I32,
NativeKind::Int8 | NativeKind::UInt8 | NativeKind::Bool => types::I8,
NativeKind::Int16 | NativeKind::UInt16 => types::I16,
_ => types::I64,
}
}
pub(crate) fn is_v2_heap_slot(kind: NativeKind) -> bool {
let _ = kind;
false
}
pub(crate) fn elem_slot_kind_for_concrete(elem: &ConcreteType) -> Option<NativeKind> {
match elem {
ConcreteType::F64 => Some(NativeKind::Float64),
ConcreteType::I64 => Some(NativeKind::Int64),
ConcreteType::I32 => Some(NativeKind::Int32),
ConcreteType::I16 => Some(NativeKind::Int16),
ConcreteType::I8 => Some(NativeKind::Int8),
ConcreteType::U64 => Some(NativeKind::UInt64),
ConcreteType::U32 => Some(NativeKind::UInt32),
ConcreteType::U16 => Some(NativeKind::UInt16),
ConcreteType::U8 => Some(NativeKind::UInt8),
ConcreteType::Bool => Some(NativeKind::Bool),
ConcreteType::F32 => Some(NativeKind::Float32),
ConcreteType::Char => Some(NativeKind::Char),
ConcreteType::String => Some(NativeKind::StringV2),
ConcreteType::Decimal => Some(NativeKind::DecimalV2),
_ => None,
}
}
pub(crate) fn is_v2_typed_array_slot(
concrete_types: &[ConcreteType],
slot_idx: u16,
) -> Option<NativeKind> {
let ct = concrete_types.get(slot_idx as usize)?;
match ct {
ConcreteType::Array(elem) => elem_slot_kind_for_concrete(elem),
_ => None,
}
}
pub(crate) fn native_kind_from_concrete_type(ct: &ConcreteType) -> Option<NativeKind> {
use shape_value::heap_value::HeapKind;
Some(match ct {
ConcreteType::F64 => NativeKind::Float64,
ConcreteType::I64 => NativeKind::Int64,
ConcreteType::I32 => NativeKind::Int32,
ConcreteType::I16 => NativeKind::Int16,
ConcreteType::I8 => NativeKind::Int8,
ConcreteType::U64 => NativeKind::UInt64,
ConcreteType::U32 => NativeKind::UInt32,
ConcreteType::U16 => NativeKind::UInt16,
ConcreteType::U8 => NativeKind::UInt8,
ConcreteType::Bool => NativeKind::Bool,
ConcreteType::String => NativeKind::String,
ConcreteType::Closure(_) | ConcreteType::Function(_) => {
NativeKind::Ptr(HeapKind::Closure)
}
ConcreteType::Result(_, _) => NativeKind::Ptr(HeapKind::Result),
ConcreteType::Option(_) => NativeKind::Ptr(HeapKind::Option),
ConcreteType::Array(_) => NativeKind::Ptr(HeapKind::TypedArray),
ConcreteType::HashMap(_, _) => NativeKind::Ptr(HeapKind::HashMap),
ConcreteType::Struct(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Enum(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Decimal => NativeKind::Ptr(HeapKind::Decimal),
ConcreteType::BigInt => NativeKind::Ptr(HeapKind::BigInt),
ConcreteType::DateTime => NativeKind::Ptr(HeapKind::Temporal),
ConcreteType::Pointer(_) => NativeKind::UInt64,
ConcreteType::Tuple(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::HashSet(_) => NativeKind::Ptr(HeapKind::HashSet),
ConcreteType::Deque(_) => NativeKind::Ptr(HeapKind::Deque),
ConcreteType::PriorityQueue => NativeKind::Ptr(HeapKind::PriorityQueue),
ConcreteType::Channel(_) => NativeKind::Ptr(HeapKind::Channel),
ConcreteType::Mutex(_) => NativeKind::Ptr(HeapKind::Mutex),
ConcreteType::Atomic => NativeKind::Ptr(HeapKind::Atomic),
ConcreteType::Lazy(_) => NativeKind::Ptr(HeapKind::Lazy),
ConcreteType::F32 => NativeKind::Float32,
ConcreteType::Char => NativeKind::Char,
ConcreteType::Void => return None,
})
}
pub(crate) fn infer_slot_kinds(
mir: &MirFunction,
existing: &[Option<NativeKind>],
) -> Vec<Option<NativeKind>> {
infer_slot_kinds_with_concrete(mir, existing, &[])
}
pub(crate) fn infer_slot_kinds_with_concrete(
mir: &MirFunction,
existing: &[Option<NativeKind>],
concrete_types: &[ConcreteType],
) -> Vec<Option<NativeKind>> {
let n = mir.num_locals as usize;
let mut kinds: Vec<Option<NativeKind>> = vec![None; n];
for (i, &k) in existing.iter().enumerate() {
if i < n && k.is_some() {
kinds[i] = k;
}
}
for block in &mir.blocks {
if let TerminatorKind::Call {
func,
args,
destination,
..
} = &block.terminator.kind
{
if let Place::Local(slot) = destination {
let idx = slot.0 as usize;
if idx < n && kinds[idx].is_none() {
let ret_kind = match func {
Operand::Constant(MirConstant::Method(name)) => {
well_known_method_return_kind(name).or_else(|| {
parametric_method_return_kind_from_receiver(
name,
args,
concrete_types,
)
})
}
Operand::Constant(MirConstant::Function(name)) => {
well_known_function_return_kind(name)
}
_ => None,
};
if let Some(k) = ret_kind {
kinds[idx] = Some(k);
}
}
}
}
}
let field_kinds_pre: std::collections::HashMap<String, NativeKind> = {
let mut tmp_kinds = kinds.clone();
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::Assign(
Place::Local(slot),
Rvalue::Use(Operand::Constant(c)),
) = &stmt.kind
{
let idx = slot.0 as usize;
if idx < n && tmp_kinds[idx].is_none() {
tmp_kinds[idx] = infer_constant_kind(c);
}
}
}
}
let mut fk: std::collections::HashMap<String, NativeKind> =
std::collections::HashMap::new();
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::ObjectStore {
operands,
field_names,
..
} = &stmt.kind
{
for (op, name) in operands.iter().zip(field_names.iter()) {
if name.is_empty() {
continue;
}
if let Some(kind) =
infer_operand_kind_with_fields(op, &tmp_kinds, None, None)
{
fk.insert(name.clone(), kind);
}
}
}
}
}
fk
};
for block in &mir.blocks {
for stmt in &block.statements {
match &stmt.kind {
StatementKind::Assign(place, rvalue) => {
if let Place::Local(slot) = place {
let idx = slot.0 as usize;
if idx < n && kinds[idx].is_none() {
if let Some(inferred) = infer_rvalue_kind_with_projections(
rvalue,
&kinds,
Some(&field_kinds_pre),
Some(&mir.field_name_table),
Some(concrete_types),
) {
kinds[idx] = Some(inferred);
}
} else if idx < n {
if let Some(inferred) = infer_rvalue_kind_with_projections(
rvalue,
&kinds,
Some(&field_kinds_pre),
Some(&mir.field_name_table),
Some(concrete_types),
) {
if Some(inferred) != kinds[idx] {
}
}
}
}
}
StatementKind::ClosureCapture { closure_slot, .. } => {
let idx = closure_slot.0 as usize;
if idx < n && kinds[idx].is_none() {
kinds[idx] = Some(NativeKind::Ptr(HeapKind::Closure));
}
}
StatementKind::EnumStore {
container_slot,
variant_name: Some(name),
..
} => {
let collection_kind = match name.as_str() {
"Set" | "HashSet" => Some(NativeKind::Ptr(HeapKind::HashSet)),
"HashMap" => Some(NativeKind::Ptr(HeapKind::HashMap)),
"Deque" => Some(NativeKind::Ptr(HeapKind::Deque)),
"PriorityQueue" => {
Some(NativeKind::Ptr(HeapKind::PriorityQueue))
}
"Channel" => Some(NativeKind::Ptr(HeapKind::Channel)),
"Mutex" => Some(NativeKind::Ptr(HeapKind::Mutex)),
"Atomic" => Some(NativeKind::Ptr(HeapKind::Atomic)),
"Lazy" => Some(NativeKind::Ptr(HeapKind::Lazy)),
_ => None,
};
if let Some(k) = collection_kind {
let idx = container_slot.0 as usize;
if idx < n {
kinds[idx] = Some(k);
}
}
}
_ => {}
}
}
}
fn is_collection_kind(k: NativeKind) -> bool {
matches!(
k,
NativeKind::Ptr(HeapKind::HashSet)
| NativeKind::Ptr(HeapKind::HashMap)
| NativeKind::Ptr(HeapKind::Deque)
| NativeKind::Ptr(HeapKind::PriorityQueue)
| NativeKind::Ptr(HeapKind::Channel)
| NativeKind::Ptr(HeapKind::Mutex)
| NativeKind::Ptr(HeapKind::Atomic)
| NativeKind::Ptr(HeapKind::Lazy)
)
}
let mut changed = true;
let mut iterations = 0;
let max_iterations = n + 4; while changed && iterations < max_iterations {
changed = false;
iterations += 1;
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::Assign(
Place::Local(dst),
Rvalue::Use(operand),
) = &stmt.kind
{
let src_slot = match operand {
Operand::Copy(Place::Local(s))
| Operand::Move(Place::Local(s))
| Operand::MoveExplicit(Place::Local(s)) => Some(*s),
_ => None,
};
if let Some(src) = src_slot {
let dst_idx = dst.0 as usize;
let src_idx = src.0 as usize;
if dst_idx < n && src_idx < n {
if let Some(src_kind) = kinds[src_idx] {
if is_collection_kind(src_kind)
&& kinds[dst_idx] != Some(src_kind)
{
kinds[dst_idx] = Some(src_kind);
changed = true;
}
}
}
}
}
}
if let TerminatorKind::Call {
func,
args,
destination,
..
} = &block.terminator.kind
{
if let Place::Local(slot) = destination {
let idx = slot.0 as usize;
if idx < n && kinds[idx].is_none() {
let ret_kind = match func {
Operand::Constant(MirConstant::Method(name)) => {
method_return_kind_from_in_pass_kinds(
name, args, &kinds,
)
}
_ => None,
};
if let Some(k) = ret_kind {
kinds[idx] = Some(k);
changed = true;
}
}
}
}
}
}
let mut opaque_slots: std::collections::HashSet<usize> = std::collections::HashSet::new();
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::Assign(Place::Local(slot), rvalue) = &stmt.kind {
let opaque = match rvalue {
Rvalue::Use(operand) => is_opaque_operand(operand),
_ => false,
};
if opaque {
opaque_slots.insert(slot.0 as usize);
}
}
}
}
let mut changed = true;
let mut rounds = 0;
while changed && rounds < n {
changed = false;
rounds += 1;
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::Assign(_, Rvalue::BinaryOp(op, lhs, rhs)) = &stmt.kind {
let _ = op;
let lk = infer_operand_kind(lhs, &kinds);
let rk = infer_operand_kind(rhs, &kinds);
match (lk, rk) {
(Some(k), None) => {
if let Some(slot) = operand_local_slot(rhs) {
if !opaque_slots.contains(&slot)
&& set_kind_if_unknown(&mut kinds, slot, k)
{
changed = true;
}
}
}
(None, Some(k)) => {
if let Some(slot) = operand_local_slot(lhs) {
if !opaque_slots.contains(&slot)
&& set_kind_if_unknown(&mut kinds, slot, k)
{
changed = true;
}
}
}
_ => {}
}
}
}
}
}
for ¶m_slot in &mir.param_slots {
let idx = param_slot.0 as usize;
if idx < n {
if let Some(Some(k)) = existing.get(idx).copied() {
kinds[idx] = Some(k);
}
}
}
kinds
}
fn well_known_method_return_kind(name: &str) -> Option<NativeKind> {
match name {
"size" | "len" | "length" | "count" => Some(NativeKind::Int64),
"isEmpty" | "is_empty" | "has" | "contains" => Some(NativeKind::Bool),
_ => None,
}
}
fn well_known_function_return_kind(name: &str) -> Option<NativeKind> {
match name {
"len" => Some(NativeKind::Int64),
_ => None,
}
}
fn parametric_method_return_kind_from_receiver(
name: &str,
args: &[Operand],
concrete_types: &[ConcreteType],
) -> Option<NativeKind> {
use shape_value::heap_value::HeapKind;
let receiver = args.first()?;
let receiver_slot = match receiver {
Operand::Copy(p) | Operand::Move(p) | Operand::MoveExplicit(p) => p.root_local(),
Operand::Constant(_) => return None,
};
let receiver_ct = concrete_types.get(receiver_slot.0 as usize)?;
if matches!(receiver_ct, ConcreteType::Void) {
return None;
}
match (name, receiver_ct) {
("sum" | "mean" | "min" | "max", ConcreteType::Array(elem)) => {
native_kind_from_concrete_type(elem)
}
("get", ConcreteType::Array(elem)) => native_kind_from_concrete_type(elem),
("first" | "last" | "pop", ConcreteType::Array(elem)) => {
native_kind_from_concrete_type(elem)
}
("get", ConcreteType::HashMap(_, _)) => Some(NativeKind::Ptr(HeapKind::Option)),
("set" | "delete" | "merge", ConcreteType::HashMap(_, _)) => {
Some(NativeKind::Ptr(HeapKind::HashMap))
}
(
"add" | "delete" | "union" | "intersection" | "difference",
ConcreteType::HashSet(_),
) => Some(NativeKind::Ptr(HeapKind::HashSet)),
("pushBack" | "pushFront", ConcreteType::Deque(_)) => {
Some(NativeKind::Ptr(HeapKind::Deque))
}
("push", ConcreteType::PriorityQueue) => {
Some(NativeKind::Ptr(HeapKind::PriorityQueue))
}
("send" | "close", ConcreteType::Channel(_)) => {
Some(NativeKind::Ptr(HeapKind::Channel))
}
("get", ConcreteType::Mutex(inner)) => native_kind_from_concrete_type(inner),
(
"load" | "fetch_add" | "fetch_sub" | "compare_exchange",
ConcreteType::Atomic,
) => Some(NativeKind::Int64),
("get", ConcreteType::Lazy(inner)) => native_kind_from_concrete_type(inner),
_ => None,
}
}
fn method_return_kind_from_in_pass_kinds(
name: &str,
args: &[Operand],
kinds: &[Option<NativeKind>],
) -> Option<NativeKind> {
use shape_value::heap_value::HeapKind;
let receiver = args.first()?;
let receiver_slot = match receiver {
Operand::Copy(p) | Operand::Move(p) | Operand::MoveExplicit(p) => p.root_local(),
Operand::Constant(_) => return None,
};
let receiver_kind = kinds.get(receiver_slot.0 as usize).and_then(|k| *k)?;
match (name, receiver_kind) {
("set" | "delete" | "merge", NativeKind::Ptr(HeapKind::HashMap)) => {
Some(NativeKind::Ptr(HeapKind::HashMap))
}
(
"add" | "delete" | "union" | "intersection" | "difference",
NativeKind::Ptr(HeapKind::HashSet),
) => Some(NativeKind::Ptr(HeapKind::HashSet)),
("pushBack" | "pushFront", NativeKind::Ptr(HeapKind::Deque)) => {
Some(NativeKind::Ptr(HeapKind::Deque))
}
("push", NativeKind::Ptr(HeapKind::PriorityQueue)) => {
Some(NativeKind::Ptr(HeapKind::PriorityQueue))
}
("send" | "close", NativeKind::Ptr(HeapKind::Channel)) => {
Some(NativeKind::Ptr(HeapKind::Channel))
}
_ => None,
}
}
#[allow(dead_code)]
fn infer_index_element_kind(
rvalue: &Rvalue,
concrete_types: &[ConcreteType],
) -> Option<NativeKind> {
let operand = match rvalue {
Rvalue::Use(op) => op,
_ => return None,
};
let place = match operand {
Operand::Copy(p) | Operand::Move(p) | Operand::MoveExplicit(p) => p,
Operand::Constant(_) => return None,
};
let (arr_place, _index) = match place {
Place::Index(arr, idx) => (arr.as_ref(), idx),
_ => return None,
};
let arr_slot = match arr_place {
Place::Local(slot) => *slot,
_ => return None,
};
let ct = concrete_types.get(arr_slot.0 as usize)?;
let ConcreteType::Array(elem) = ct else {
return None;
};
elem_slot_kind_for_concrete(elem)
}
fn is_opaque_operand(operand: &Operand) -> bool {
match operand {
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
is_opaque_place(place)
}
Operand::Constant(_) => false,
}
}
fn is_opaque_place(place: &Place) -> bool {
match place {
Place::Local(_) => false,
Place::Field(_, _) | Place::Index(_, _) | Place::Deref(_) => true,
}
}
fn operand_local_slot(operand: &Operand) -> Option<usize> {
match operand {
Operand::Copy(Place::Local(slot))
| Operand::Move(Place::Local(slot))
| Operand::MoveExplicit(Place::Local(slot)) => Some(slot.0 as usize),
_ => None,
}
}
fn set_kind_if_unknown(kinds: &mut [Option<NativeKind>], idx: usize, kind: NativeKind) -> bool {
if idx < kinds.len() && kinds[idx].is_none() {
kinds[idx] = Some(kind);
true
} else {
false
}
}
fn infer_rvalue_kind(rvalue: &Rvalue, kinds: &[Option<NativeKind>]) -> Option<NativeKind> {
infer_rvalue_kind_with_fields(rvalue, kinds, None, None)
}
#[allow(dead_code)]
fn infer_rvalue_kind_with_fields(
rvalue: &Rvalue,
kinds: &[Option<NativeKind>],
field_kinds: Option<&std::collections::HashMap<String, NativeKind>>,
field_name_table: Option<&std::collections::HashMap<FieldIdx, String>>,
) -> Option<NativeKind> {
infer_rvalue_kind_with_projections(rvalue, kinds, field_kinds, field_name_table, None)
}
fn infer_rvalue_kind_with_projections(
rvalue: &Rvalue,
kinds: &[Option<NativeKind>],
field_kinds: Option<&std::collections::HashMap<String, NativeKind>>,
field_name_table: Option<&std::collections::HashMap<FieldIdx, String>>,
concrete_types: Option<&[ConcreteType]>,
) -> Option<NativeKind> {
match rvalue {
Rvalue::Use(operand) => infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
concrete_types,
),
Rvalue::BinaryOp(op, lhs, rhs) => {
let lk = infer_operand_kind_with_projections(
lhs,
kinds,
field_kinds,
field_name_table,
concrete_types,
);
let rk = infer_operand_kind_with_projections(
rhs,
kinds,
field_kinds,
field_name_table,
concrete_types,
);
match (lk, rk) {
(Some(l), Some(r)) if l == r => {
if is_comparison_op(op) {
Some(NativeKind::Bool)
} else {
Some(l)
}
}
_ => {
if is_comparison_op(op) {
Some(NativeKind::Bool)
} else {
None
}
}
}
}
Rvalue::UnaryOp(UnOp::Neg, operand) => infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
concrete_types,
),
Rvalue::UnaryOp(UnOp::BitNot, operand) => infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
concrete_types,
),
Rvalue::UnaryOp(UnOp::Not, operand) => {
let op_kind = infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
concrete_types,
);
match op_kind {
Some(shape_value::NativeKind::Ptr(_)) => op_kind,
Some(_) => Some(NativeKind::Bool),
None => None,
}
}
Rvalue::Clone(operand) => infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
concrete_types,
),
Rvalue::Borrow(_, _) => None, Rvalue::Aggregate(_) => None, Rvalue::EnumTest { .. } => Some(NativeKind::Bool),
Rvalue::EnumPayload { operand, variant } => {
infer_enum_payload_kind(operand, *variant, concrete_types)
}
Rvalue::TypePatternTest { .. } => Some(NativeKind::Bool),
Rvalue::EnumDiscriminantTest { .. } => Some(NativeKind::Bool),
}
}
fn infer_enum_payload_kind(
operand: &Operand,
variant: VariantTag,
concrete_types: Option<&[ConcreteType]>,
) -> Option<NativeKind> {
let concrete_types = concrete_types?;
let place = match operand {
Operand::Copy(p) | Operand::Move(p) | Operand::MoveExplicit(p) => p,
Operand::Constant(_) => return None,
};
let base_slot = place.root_local();
let ct = concrete_types.get(base_slot.0 as usize)?;
let inner: &ConcreteType = match (variant, ct) {
(VariantTag::Ok, ConcreteType::Result(ok, _)) => ok.as_ref(),
(VariantTag::Err, ConcreteType::Result(_, err)) => err.as_ref(),
(VariantTag::Some_, ConcreteType::Option(inner)) => inner.as_ref(),
_ => return None,
};
native_kind_from_concrete_type(inner)
}
fn infer_operand_kind(operand: &Operand, kinds: &[Option<NativeKind>]) -> Option<NativeKind> {
infer_operand_kind_with_fields(operand, kinds, None, None)
}
fn infer_operand_kind_with_fields(
operand: &Operand,
kinds: &[Option<NativeKind>],
field_kinds: Option<&std::collections::HashMap<String, NativeKind>>,
field_name_table: Option<&std::collections::HashMap<FieldIdx, String>>,
) -> Option<NativeKind> {
infer_operand_kind_with_projections(
operand,
kinds,
field_kinds,
field_name_table,
None,
)
}
fn infer_operand_kind_with_projections(
operand: &Operand,
kinds: &[Option<NativeKind>],
field_kinds: Option<&std::collections::HashMap<String, NativeKind>>,
field_name_table: Option<&std::collections::HashMap<FieldIdx, String>>,
concrete_types: Option<&[ConcreteType]>,
) -> Option<NativeKind> {
match operand {
Operand::Constant(c) => infer_constant_kind(c),
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
if let (Place::Field(_, field_idx), Some(fk), Some(fnt)) =
(place, field_kinds, field_name_table)
{
if let Some(name) = fnt.get(field_idx) {
if let Some(k) = fk.get(name).copied() {
return Some(k);
}
}
}
if let (Place::Index(base, _), Some(cts)) = (place, concrete_types) {
let base_slot = base.root_local();
if let Some(elem_kind) = is_v2_typed_array_slot(cts, base_slot.0) {
return Some(elem_kind);
}
}
let slot = place.root_local();
let idx = slot.0 as usize;
kinds.get(idx).copied().flatten()
}
}
}
pub(crate) fn infer_field_native_kinds(
mir: &MirFunction,
slot_kinds: &[Option<NativeKind>],
) -> std::collections::HashMap<String, NativeKind> {
let mut field_kinds: std::collections::HashMap<String, NativeKind> =
std::collections::HashMap::new();
for block in &mir.blocks {
for stmt in &block.statements {
if let StatementKind::ObjectStore {
operands,
field_names,
..
} = &stmt.kind
{
for (op, name) in operands.iter().zip(field_names.iter()) {
if name.is_empty() {
continue;
}
if let Some(kind) = infer_operand_kind(op, slot_kinds) {
field_kinds.insert(name.clone(), kind);
}
}
}
}
}
field_kinds
}
fn infer_constant_kind(constant: &MirConstant) -> Option<NativeKind> {
match constant {
MirConstant::Float(_) => Some(NativeKind::Float64),
MirConstant::Int(_) => Some(NativeKind::Int64),
MirConstant::Bool(_) => Some(NativeKind::Bool),
MirConstant::Char(_) => Some(NativeKind::Char),
MirConstant::Decimal(_) => Some(NativeKind::DecimalV2),
MirConstant::None => None,
MirConstant::StringId(_) | MirConstant::Str(_) => Some(NativeKind::String),
MirConstant::Function(_) => Some(NativeKind::UInt64),
MirConstant::Method(_) => Some(NativeKind::String),
MirConstant::ClosurePlaceholder => Some(NativeKind::Ptr(HeapKind::Closure)),
}
}
fn is_comparison_op(op: &BinOp) -> bool {
matches!(
op,
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
)
}
#[cfg(test)]
mod tests {
use super::*;
use shape_vm::mir::types::*;
fn make_mir(stmts: Vec<MirStatement>) -> MirFunction {
MirFunction {
name: "test".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: stmts,
terminator: Terminator {
kind: TerminatorKind::Return,
span: shape_ast::Span::default(),
},
}],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
}
}
fn assign_const(slot: u16, constant: MirConstant) -> MirStatement {
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(slot)),
Rvalue::Use(Operand::Constant(constant)),
),
span: shape_ast::Span::default(),
point: Point(0),
}
}
#[test]
fn infer_float_from_constant() {
let mir = make_mir(vec![assign_const(1, MirConstant::Float(0))]);
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[1], Some(NativeKind::Float64));
}
#[test]
fn infer_int_from_constant() {
let mir = make_mir(vec![assign_const(1, MirConstant::Int(42))]);
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[1], Some(NativeKind::Int64));
}
#[test]
fn infer_bool_from_constant() {
let mir = make_mir(vec![assign_const(1, MirConstant::Bool(true))]);
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[1], Some(NativeKind::Bool));
}
#[test]
fn infer_float_from_binop() {
let mir = make_mir(vec![
assign_const(1, MirConstant::Float(0)),
assign_const(2, MirConstant::Float(0)),
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(3)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(1))),
Operand::Copy(Place::Local(SlotId(2))),
),
),
span: shape_ast::Span::default(),
point: Point(0),
},
]);
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[3], Some(NativeKind::Float64));
}
#[test]
fn infer_bool_from_comparison() {
let mir = make_mir(vec![
assign_const(1, MirConstant::Float(0)),
assign_const(2, MirConstant::Float(0)),
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(3)),
Rvalue::BinaryOp(
BinOp::Lt,
Operand::Copy(Place::Local(SlotId(1))),
Operand::Copy(Place::Local(SlotId(2))),
),
),
span: shape_ast::Span::default(),
point: Point(0),
},
]);
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[3], Some(NativeKind::Bool));
}
#[test]
fn infer_backward_from_typed_sibling_on_binop() {
let mut mir = make_mir(vec![MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(0))),
Operand::Constant(MirConstant::Int(1)),
),
),
span: shape_ast::Span::default(),
point: Point(0),
}]);
mir.param_slots = vec![SlotId(0)];
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(
kinds[0],
Some(NativeKind::Int64),
"backward pass should infer x: Int64 from `x + Int(1)`"
);
}
#[test]
fn infer_backward_chains_across_params() {
let mut mir = make_mir(vec![
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(0))),
Operand::Copy(Place::Local(SlotId(1))),
),
),
span: shape_ast::Span::default(),
point: Point(0),
},
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(3)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(Place::Local(SlotId(2))),
Operand::Constant(MirConstant::Int(1)),
),
),
span: shape_ast::Span::default(),
point: Point(0),
},
]);
mir.param_slots = vec![SlotId(0), SlotId(1)];
let kinds = infer_slot_kinds(&mir, &[]);
assert_eq!(kinds[2], Some(NativeKind::Int64));
}
#[test]
fn existing_kinds_preserved() {
let mir = make_mir(vec![assign_const(1, MirConstant::Float(0))]);
let existing = vec![None, Some(NativeKind::Int32)];
let kinds = infer_slot_kinds(&mir, &existing);
assert_eq!(kinds[1], Some(NativeKind::Int32));
}
#[test]
fn cranelift_type_mapping() {
assert_eq!(cranelift_type_for_slot(NativeKind::Float64), types::F64);
assert_eq!(cranelift_type_for_slot(NativeKind::Int32), types::I32);
assert_eq!(cranelift_type_for_slot(NativeKind::Bool), types::I8);
assert_eq!(cranelift_type_for_slot(NativeKind::Int64), types::I64);
assert_eq!(cranelift_type_for_slot(NativeKind::String), types::I64);
}
#[test]
fn r4_2f_borrow_cell_sizes() {
assert_eq!(cranelift_type_for_slot(NativeKind::Float64).bytes(), 8);
assert_eq!(cranelift_type_for_slot(NativeKind::Int64).bytes(), 8);
assert_eq!(cranelift_type_for_slot(NativeKind::Int32).bytes(), 4);
assert_eq!(cranelift_type_for_slot(NativeKind::UInt32).bytes(), 4);
assert_eq!(cranelift_type_for_slot(NativeKind::Int16).bytes(), 2);
assert_eq!(cranelift_type_for_slot(NativeKind::UInt16).bytes(), 2);
assert_eq!(cranelift_type_for_slot(NativeKind::Int8).bytes(), 1);
assert_eq!(cranelift_type_for_slot(NativeKind::UInt8).bytes(), 1);
assert_eq!(cranelift_type_for_slot(NativeKind::Bool).bytes(), 1);
assert_eq!(cranelift_type_for_slot(NativeKind::String).bytes(), 8);
}
#[test]
fn r4_2f_borrow_cell_alignment_shifts() {
for kind in [
NativeKind::Float64,
NativeKind::Int64,
NativeKind::Int32,
NativeKind::UInt32,
NativeKind::Int16,
NativeKind::UInt16,
NativeKind::Int8,
NativeKind::UInt8,
NativeKind::Bool,
NativeKind::String,
] {
let size = cranelift_type_for_slot(kind).bytes();
assert!(
size.is_power_of_two(),
"slot kind {:?} has non-power-of-two size {}",
kind,
size
);
let shift = size.trailing_zeros() as u8;
assert_eq!(
1u32 << shift,
size,
"slot kind {:?}: shift {} does not reconstruct size {}",
kind,
shift,
size
);
}
}
use shape_value::heap_value::HeapKind;
use shape_value::v2::ConcreteType;
fn copy_local(slot: u16) -> Operand {
Operand::Copy(Place::Local(SlotId(slot)))
}
#[test]
fn parametric_array_sum_returns_element_kind() {
let cts = vec![
ConcreteType::Array(Box::new(ConcreteType::I64)),
];
let kind = parametric_method_return_kind_from_receiver("sum", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Int64));
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::F64))];
let kind = parametric_method_return_kind_from_receiver("sum", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Float64));
}
#[test]
fn parametric_array_mean_and_min_max_inherit_element() {
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::F64))];
assert_eq!(
parametric_method_return_kind_from_receiver("mean", &[copy_local(0)], &cts),
Some(NativeKind::Float64)
);
assert_eq!(
parametric_method_return_kind_from_receiver("min", &[copy_local(0)], &cts),
Some(NativeKind::Float64)
);
assert_eq!(
parametric_method_return_kind_from_receiver("max", &[copy_local(0)], &cts),
Some(NativeKind::Float64)
);
}
#[test]
fn parametric_array_first_last_pop_return_element_kind() {
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::I64))];
assert_eq!(
parametric_method_return_kind_from_receiver("first", &[copy_local(0)], &cts),
Some(NativeKind::Int64)
);
assert_eq!(
parametric_method_return_kind_from_receiver("last", &[copy_local(0)], &cts),
Some(NativeKind::Int64)
);
assert_eq!(
parametric_method_return_kind_from_receiver("pop", &[copy_local(0)], &cts),
Some(NativeKind::Int64)
);
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::F64))];
assert_eq!(
parametric_method_return_kind_from_receiver("first", &[copy_local(0)], &cts),
Some(NativeKind::Float64)
);
}
#[test]
fn parametric_hashmap_get_returns_option_carrier() {
let cts = vec![ConcreteType::HashMap(
Box::new(ConcreteType::String),
Box::new(ConcreteType::I64),
)];
let kind =
parametric_method_return_kind_from_receiver("get", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::Option)));
}
#[test]
fn parametric_mutex_get_returns_inner_kind() {
let cts = vec![ConcreteType::Mutex(Box::new(ConcreteType::I64))];
let kind =
parametric_method_return_kind_from_receiver("get", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Int64));
let cts = vec![ConcreteType::Mutex(Box::new(ConcreteType::Bool))];
let kind =
parametric_method_return_kind_from_receiver("get", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Bool));
}
#[test]
fn parametric_atomic_load_fetch_returns_int64() {
let cts = vec![ConcreteType::Atomic];
for name in &["load", "fetch_add", "fetch_sub", "compare_exchange"] {
let kind =
parametric_method_return_kind_from_receiver(name, &[copy_local(0)], &cts);
assert_eq!(
kind,
Some(NativeKind::Int64),
"Atomic.{name} should return Int64"
);
}
}
#[test]
fn parametric_lazy_get_returns_inner_kind() {
let cts = vec![ConcreteType::Lazy(Box::new(ConcreteType::I64))];
let kind =
parametric_method_return_kind_from_receiver("get", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Int64));
}
#[test]
fn parametric_unknown_method_returns_none() {
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::I64))];
let kind = parametric_method_return_kind_from_receiver(
"unknown_method",
&[copy_local(0)],
&cts,
);
assert_eq!(kind, None);
}
#[test]
fn parametric_constant_receiver_returns_none() {
let kind = parametric_method_return_kind_from_receiver(
"sum",
&[Operand::Constant(MirConstant::Int(42))],
&[],
);
assert_eq!(kind, None);
}
#[test]
fn parametric_void_receiver_returns_none() {
let cts = vec![ConcreteType::Void];
let kind =
parametric_method_return_kind_from_receiver("sum", &[copy_local(0)], &cts);
assert_eq!(kind, None);
}
#[test]
fn parametric_size_is_invariant_not_parametric() {
let cts = vec![ConcreteType::Array(Box::new(ConcreteType::I64))];
let kind =
parametric_method_return_kind_from_receiver("size", &[copy_local(0)], &cts);
assert_eq!(
kind, None,
"size belongs to well_known_method_return_kind, not the parametric cohort"
);
assert_eq!(
well_known_method_return_kind("size"),
Some(NativeKind::Int64)
);
}
#[test]
fn parametric_method_return_kind_integrates_in_call_terminator_seed() {
let mir = MirFunction {
name: "test_sum".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method("sum".to_string())),
args: vec![copy_local(0)],
destination: Place::Local(SlotId(1)),
next: BasicBlockId(0),
},
span: shape_ast::Span::default(),
},
}],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
};
let concrete_types = vec![
ConcreteType::Array(Box::new(ConcreteType::I64)),
ConcreteType::Void,
ConcreteType::Void,
ConcreteType::Void,
];
let kinds = infer_slot_kinds_with_concrete(&mir, &[], &concrete_types);
assert_eq!(
kinds[1],
Some(NativeKind::Int64),
".sum() on Array<int> should stamp Int64 on the destination slot"
);
}
#[test]
fn parametric_hashmap_set_returns_hashmap_carrier() {
let cts = vec![ConcreteType::HashMap(
Box::new(ConcreteType::String),
Box::new(ConcreteType::I64),
)];
let kind =
parametric_method_return_kind_from_receiver("set", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
}
#[test]
fn parametric_hashmap_delete_returns_hashmap_carrier() {
let cts = vec![ConcreteType::HashMap(
Box::new(ConcreteType::String),
Box::new(ConcreteType::I64),
)];
let kind =
parametric_method_return_kind_from_receiver("delete", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
}
#[test]
fn parametric_hashmap_merge_returns_hashmap_carrier() {
let cts = vec![ConcreteType::HashMap(
Box::new(ConcreteType::String),
Box::new(ConcreteType::I64),
)];
let kind =
parametric_method_return_kind_from_receiver("merge", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
}
#[test]
fn in_pass_kinds_classifier_stamps_hashmap_mutators_from_kinds_track() {
let kinds = vec![Some(NativeKind::Ptr(HeapKind::HashMap)), None, None];
let kind = method_return_kind_from_in_pass_kinds(
"set",
&[copy_local(0)],
&kinds,
);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
let kind = method_return_kind_from_in_pass_kinds(
"delete",
&[copy_local(0)],
&kinds,
);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
let kind = method_return_kind_from_in_pass_kinds(
"merge",
&[copy_local(0)],
&kinds,
);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::HashMap)));
}
#[test]
fn in_pass_kinds_classifier_returns_none_for_non_hashmap_receivers() {
let kinds = vec![Some(NativeKind::Ptr(HeapKind::TypedArray)), None];
let kind =
method_return_kind_from_in_pass_kinds("set", &[copy_local(0)], &kinds);
assert_eq!(kind, None, "Non-HashMap receiver must not be classified");
let kinds = vec![Some(NativeKind::Int64), None];
let kind =
method_return_kind_from_in_pass_kinds("set", &[copy_local(0)], &kinds);
assert_eq!(kind, None, "Scalar receiver must not be classified");
let kinds = vec![Some(NativeKind::Ptr(HeapKind::HashMap)), None];
let kind = method_return_kind_from_in_pass_kinds(
"unknown_method",
&[copy_local(0)],
&kinds,
);
assert_eq!(kind, None, "Unknown method must not be classified");
}
#[test]
fn hashmap_chain_propagates_kind_through_call_stamp_fixpoint() {
let mir = MirFunction {
name: "hashmap_chain".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Aggregate(vec![]),
),
span: shape_ast::Span::default(),
point: Point(0),
},
MirStatement {
kind: StatementKind::EnumStore {
container_slot: SlotId(0),
operands: vec![],
variant_name: Some("HashMap".to_string()),
},
span: shape_ast::Span::default(),
point: Point(1),
},
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
"set".to_string(),
)),
args: vec![
copy_local(0),
Operand::Constant(MirConstant::Str("a".to_string())),
Operand::Constant(MirConstant::Int(1)),
],
destination: Place::Local(SlotId(1)),
next: BasicBlockId(1),
},
span: shape_ast::Span::default(),
},
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
"set".to_string(),
)),
args: vec![
copy_local(1),
Operand::Constant(MirConstant::Str("b".to_string())),
Operand::Constant(MirConstant::Int(2)),
],
destination: Place::Local(SlotId(2)),
next: BasicBlockId(2),
},
span: shape_ast::Span::default(),
},
},
BasicBlock {
id: BasicBlockId(2),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
"has".to_string(),
)),
args: vec![
copy_local(2),
Operand::Constant(MirConstant::Str("a".to_string())),
],
destination: Place::Local(SlotId(3)),
next: BasicBlockId(2),
},
span: shape_ast::Span::default(),
},
},
],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
};
let concrete_types = vec![
ConcreteType::Void,
ConcreteType::Void,
ConcreteType::Void,
ConcreteType::Void,
];
let kinds = infer_slot_kinds_with_concrete(&mir, &[], &concrete_types);
assert_eq!(
kinds[0],
Some(NativeKind::Ptr(HeapKind::HashMap)),
"temp0 (HashMap() ctor) must be classified via EnumStore arm"
);
assert_eq!(
kinds[1],
Some(NativeKind::Ptr(HeapKind::HashMap)),
"temp1 (1st .set) must inherit Ptr(HashMap) via in-pass-kinds classifier"
);
assert_eq!(
kinds[2],
Some(NativeKind::Ptr(HeapKind::HashMap)),
"temp2 (2nd .set) must inherit Ptr(HashMap) — fixpoint propagates the chain"
);
assert_eq!(
kinds[3],
Some(NativeKind::Bool),
"temp3 (.has) must be Bool from well_known_method_return_kind"
);
}
#[test]
fn parametric_hashset_mutators_return_hashset_carrier() {
let cts = vec![ConcreteType::HashSet(Box::new(ConcreteType::String))];
for name in ["add", "delete", "union", "intersection", "difference"] {
let kind =
parametric_method_return_kind_from_receiver(name, &[copy_local(0)], &cts);
assert_eq!(
kind,
Some(NativeKind::Ptr(HeapKind::HashSet)),
"HashSet.{name} must classify to Ptr(HeapKind::HashSet)"
);
}
}
#[test]
fn parametric_deque_mutators_return_deque_carrier() {
let cts = vec![ConcreteType::Deque(Box::new(ConcreteType::I64))];
for name in ["pushBack", "pushFront"] {
let kind =
parametric_method_return_kind_from_receiver(name, &[copy_local(0)], &cts);
assert_eq!(
kind,
Some(NativeKind::Ptr(HeapKind::Deque)),
"Deque.{name} must classify to Ptr(HeapKind::Deque)"
);
}
for name in ["popBack", "popFront"] {
let kind =
parametric_method_return_kind_from_receiver(name, &[copy_local(0)], &cts);
assert_eq!(kind, None, "Deque.{name} (tuple-return) must not classify here");
}
}
#[test]
fn parametric_priority_queue_push_returns_priority_queue_carrier() {
let cts = vec![ConcreteType::PriorityQueue];
let kind =
parametric_method_return_kind_from_receiver("push", &[copy_local(0)], &cts);
assert_eq!(kind, Some(NativeKind::Ptr(HeapKind::PriorityQueue)));
let kind =
parametric_method_return_kind_from_receiver("pop", &[copy_local(0)], &cts);
assert_eq!(kind, None, "PriorityQueue.pop (tuple-return) must not classify here");
}
#[test]
fn parametric_channel_mutators_return_channel_carrier() {
let cts = vec![ConcreteType::Channel(Box::new(ConcreteType::I64))];
for name in ["send", "close"] {
let kind =
parametric_method_return_kind_from_receiver(name, &[copy_local(0)], &cts);
assert_eq!(
kind,
Some(NativeKind::Ptr(HeapKind::Channel)),
"Channel.{name} must classify to Ptr(HeapKind::Channel)"
);
}
}
#[test]
fn in_pass_kinds_classifier_stamps_collection_mutators_from_kinds_track() {
let hs_kinds = vec![Some(NativeKind::Ptr(HeapKind::HashSet)), None];
for name in ["add", "delete", "union", "intersection", "difference"] {
assert_eq!(
method_return_kind_from_in_pass_kinds(name, &[copy_local(0)], &hs_kinds),
Some(NativeKind::Ptr(HeapKind::HashSet)),
"HashSet.{name} in-pass-kinds classification"
);
}
let dq_kinds = vec![Some(NativeKind::Ptr(HeapKind::Deque)), None];
for name in ["pushBack", "pushFront"] {
assert_eq!(
method_return_kind_from_in_pass_kinds(name, &[copy_local(0)], &dq_kinds),
Some(NativeKind::Ptr(HeapKind::Deque)),
"Deque.{name} in-pass-kinds classification"
);
}
let pq_kinds = vec![Some(NativeKind::Ptr(HeapKind::PriorityQueue)), None];
assert_eq!(
method_return_kind_from_in_pass_kinds("push", &[copy_local(0)], &pq_kinds),
Some(NativeKind::Ptr(HeapKind::PriorityQueue)),
);
let ch_kinds = vec![Some(NativeKind::Ptr(HeapKind::Channel)), None];
for name in ["send", "close"] {
assert_eq!(
method_return_kind_from_in_pass_kinds(name, &[copy_local(0)], &ch_kinds),
Some(NativeKind::Ptr(HeapKind::Channel)),
"Channel.{name} in-pass-kinds classification"
);
}
}
#[test]
fn in_pass_kinds_classifier_rejects_cross_collection_method_names() {
let dq_kinds = vec![Some(NativeKind::Ptr(HeapKind::Deque)), None];
assert_eq!(
method_return_kind_from_in_pass_kinds("add", &[copy_local(0)], &dq_kinds),
None,
"HashSet method `add` on a Deque receiver must not classify"
);
let hs_kinds = vec![Some(NativeKind::Ptr(HeapKind::HashSet)), None];
assert_eq!(
method_return_kind_from_in_pass_kinds("pushBack", &[copy_local(0)], &hs_kinds),
None,
"Deque method `pushBack` on a HashSet receiver must not classify"
);
assert_eq!(
method_return_kind_from_in_pass_kinds("send", &[copy_local(0)], &hs_kinds),
None,
"Channel method `send` on a HashSet receiver must not classify"
);
let scalar_kinds = vec![Some(NativeKind::Int64), None];
assert_eq!(
method_return_kind_from_in_pass_kinds("add", &[copy_local(0)], &scalar_kinds),
None,
);
}
#[test]
fn hashset_chain_propagates_kind_through_call_stamp_fixpoint() {
let mir = collection_chain_mir(
"Set",
"add",
&[Operand::Constant(MirConstant::Str("a".to_string()))],
"has",
&[Operand::Constant(MirConstant::Str("a".to_string()))],
);
let concrete_types = vec![ConcreteType::Void; 4];
let kinds = infer_slot_kinds_with_concrete(&mir, &[], &concrete_types);
assert_eq!(kinds[0], Some(NativeKind::Ptr(HeapKind::HashSet)));
assert_eq!(
kinds[1],
Some(NativeKind::Ptr(HeapKind::HashSet)),
"1st .add chain temp must inherit Ptr(HashSet)"
);
assert_eq!(
kinds[2],
Some(NativeKind::Ptr(HeapKind::HashSet)),
"2nd .add chain temp must inherit Ptr(HashSet) — fixpoint propagates"
);
assert_eq!(
kinds[3],
Some(NativeKind::Bool),
".has destination must be Bool from well_known_method_return_kind"
);
}
#[test]
fn deque_chain_propagates_kind_through_call_stamp_fixpoint() {
let mir = collection_chain_mir(
"Deque",
"pushBack",
&[Operand::Constant(MirConstant::Int(1))],
"size",
&[],
);
let concrete_types = vec![ConcreteType::Void; 4];
let kinds = infer_slot_kinds_with_concrete(&mir, &[], &concrete_types);
assert_eq!(kinds[0], Some(NativeKind::Ptr(HeapKind::Deque)));
assert_eq!(kinds[1], Some(NativeKind::Ptr(HeapKind::Deque)));
assert_eq!(kinds[2], Some(NativeKind::Ptr(HeapKind::Deque)));
assert_eq!(
kinds[3],
Some(NativeKind::Int64),
".size destination must be Int64 from well_known_method_return_kind"
);
}
#[test]
fn priority_queue_and_channel_chains_propagate_kind_through_fixpoint() {
let pq_mir = collection_chain_mir(
"PriorityQueue",
"push",
&[Operand::Constant(MirConstant::Int(5))],
"size",
&[],
);
let pq_kinds =
infer_slot_kinds_with_concrete(&pq_mir, &[], &vec![ConcreteType::Void; 4]);
assert_eq!(pq_kinds[0], Some(NativeKind::Ptr(HeapKind::PriorityQueue)));
assert_eq!(pq_kinds[1], Some(NativeKind::Ptr(HeapKind::PriorityQueue)));
assert_eq!(pq_kinds[2], Some(NativeKind::Ptr(HeapKind::PriorityQueue)));
assert_eq!(pq_kinds[3], Some(NativeKind::Int64));
let ch_mir = collection_chain_mir(
"Channel",
"send",
&[Operand::Constant(MirConstant::Int(7))],
"is_closed",
&[],
);
let ch_kinds =
infer_slot_kinds_with_concrete(&ch_mir, &[], &vec![ConcreteType::Void; 4]);
assert_eq!(ch_kinds[0], Some(NativeKind::Ptr(HeapKind::Channel)));
assert_eq!(ch_kinds[1], Some(NativeKind::Ptr(HeapKind::Channel)));
assert_eq!(ch_kinds[2], Some(NativeKind::Ptr(HeapKind::Channel)));
}
fn collection_chain_mir(
ctor: &str,
mutator: &str,
mut_arg: &[Operand],
query: &str,
query_args: &[Operand],
) -> MirFunction {
let mut mut_args_1 = vec![copy_local(0)];
mut_args_1.extend_from_slice(mut_arg);
let mut mut_args_2 = vec![copy_local(1)];
mut_args_2.extend_from_slice(mut_arg);
let mut query_full = vec![copy_local(2)];
query_full.extend_from_slice(query_args);
MirFunction {
name: "collection_chain".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![
MirStatement {
kind: StatementKind::Assign(
Place::Local(SlotId(0)),
Rvalue::Aggregate(vec![]),
),
span: shape_ast::Span::default(),
point: Point(0),
},
MirStatement {
kind: StatementKind::EnumStore {
container_slot: SlotId(0),
operands: vec![],
variant_name: Some(ctor.to_string()),
},
span: shape_ast::Span::default(),
point: Point(1),
},
],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
mutator.to_string(),
)),
args: mut_args_1,
destination: Place::Local(SlotId(1)),
next: BasicBlockId(1),
},
span: shape_ast::Span::default(),
},
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
mutator.to_string(),
)),
args: mut_args_2,
destination: Place::Local(SlotId(2)),
next: BasicBlockId(2),
},
span: shape_ast::Span::default(),
},
},
BasicBlock {
id: BasicBlockId(2),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
query.to_string(),
)),
args: query_full,
destination: Place::Local(SlotId(3)),
next: BasicBlockId(2),
},
span: shape_ast::Span::default(),
},
},
],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
}
}
#[test]
fn user_defined_trait_method_on_struct_returns_none() {
let cts = vec![ConcreteType::placeholder_struct(shape_value::v2::concrete_type::StructLayoutId(0))];
let kind =
parametric_method_return_kind_from_receiver("name", &[copy_local(0)], &cts);
assert_eq!(
kind, None,
"User-defined trait method on Struct receiver must surface \
(return None); the trait registry's declared return type is \
not threaded into the JIT MIR builder. See classifier doc \
block 'User-defined-trait surface boundary'."
);
}
#[test]
fn user_defined_trait_method_call_terminator_remains_unstamped() {
let mir = MirFunction {
name: "test_trait_dispatch".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
"name".to_string(),
)),
args: vec![copy_local(0)],
destination: Place::Local(SlotId(1)),
next: BasicBlockId(0),
},
span: shape_ast::Span::default(),
},
}],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
};
let concrete_types = vec![
ConcreteType::placeholder_struct(shape_value::v2::concrete_type::StructLayoutId(0)),
ConcreteType::Void,
ConcreteType::Void,
ConcreteType::Void,
];
let kinds = infer_slot_kinds_with_concrete(&mir, &[], &concrete_types);
assert_eq!(
kinds[1],
None,
"Call-terminator destination for `t.name()` on a Struct(_) \
receiver must remain unstamped — the trait-dispatch return \
kind cannot be classified without a cross-crate conduit \
extension. See classifier doc block 'User-defined-trait \
surface boundary'."
);
assert_eq!(
well_known_method_return_kind("name"),
None,
"`name` must not be a well-known method name — that would \
be a soundness violation (different traits could declare \
`name` with different return types, e.g. `trait T \
{{ method name() -> string }}` vs `trait U {{ method name() -> int }}`)."
);
}
#[test]
fn parametric_classifier_remains_silent_for_struct_receiver_with_known_method_names() {
let cts = vec![ConcreteType::placeholder_struct(shape_value::v2::concrete_type::StructLayoutId(0))];
for method_name in [
"get",
"sum",
"mean",
"min",
"max",
"first",
"last",
"pop",
"load",
"fetch_add",
"fetch_sub",
"compare_exchange",
"name",
"display",
"to_string",
"into",
"from",
"try_into",
"try_from",
] {
let kind = parametric_method_return_kind_from_receiver(
method_name,
&[copy_local(0)],
&cts,
);
assert_eq!(
kind, None,
"method `{method_name}` on Struct(_) receiver must \
not be classified by the parametric cohort"
);
}
}
#[test]
fn trait_method_call_destination_seeded_from_concrete_types() {
let mir = MirFunction {
name: "test_trait_dispatch_post_t1prime".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: Terminator {
kind: TerminatorKind::Call {
func: Operand::Constant(MirConstant::Method(
"name".to_string(),
)),
args: vec![copy_local(0)],
destination: Place::Local(SlotId(1)),
next: BasicBlockId(0),
},
span: shape_ast::Span::default(),
},
}],
num_locals: 4,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: shape_ast::Span::default(),
field_name_table: Default::default(),
local_struct_type_names: Default::default(),
local_typed_array_element_types: Default::default(),
local_declared_scalar_types: Default::default(),
};
let concrete_types = vec![
ConcreteType::placeholder_struct(shape_value::v2::concrete_type::StructLayoutId(0)),
ConcreteType::String,
ConcreteType::Void,
ConcreteType::Void,
];
let existing = vec![
None,
Some(NativeKind::String),
None,
None,
];
let kinds = infer_slot_kinds_with_concrete(&mir, &existing, &concrete_types);
assert_eq!(
kinds[1],
Some(NativeKind::String),
"Post-T1' upstream-seeded Call-terminator destination slot \
must preserve the trait-method declared return kind through \
the JIT consumer's existing-seed pass — no clobber by the \
classifier fallthrough"
);
}
}