use crate::bytecode::{
ArrayElem, Chunk, ChunkSignature, ConstValue, EnumField, Module, NewCompositeOperand, Op,
StructField, TupleField, WireShape,
};
use crate::value_layout::{CompositeKind, ScalarKind};
use crate::verify::op_depth_effect;
use alloc::vec::Vec;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AbsVal {
Scalar(ScalarKind),
Flat {
kind: CompositeKind,
size: u32,
},
Top,
}
impl AbsVal {
fn packed_size(&self, word_bytes: usize, float_bytes: usize) -> Option<u32> {
match self {
AbsVal::Scalar(k) => Some(k.size_in_bytes(word_bytes, float_bytes) as u32),
AbsVal::Flat { size, .. } => Some(*size),
AbsVal::Top => None,
}
}
fn join(&self, other: &AbsVal) -> AbsVal {
if self == other {
self.clone()
} else {
AbsVal::Top
}
}
fn from_wire(shape: &WireShape) -> AbsVal {
match shape {
WireShape::Top => AbsVal::Top,
WireShape::Scalar { kind } => match ScalarKind::from_tag(*kind) {
Some(k) => AbsVal::Scalar(k),
None => AbsVal::Top,
},
WireShape::Flat { kind, size } => match CompositeKind::from_tag(*kind) {
Some(k) => AbsVal::Flat {
kind: k,
size: *size,
},
None => AbsVal::Top,
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypedError {
StackUnderflow {
ip: usize,
},
OffsetOutOfBounds {
ip: usize,
offset: usize,
need: usize,
size: u32,
},
ExpectedComposite {
ip: usize,
},
NewCompositeSizeMismatch {
ip: usize,
baked: u32,
computed: u32,
},
BranchHeightMismatch {
ip: usize,
then_height: usize,
else_height: usize,
},
LoopNotNeutral {
ip: usize,
entry_height: usize,
exit_height: usize,
},
LocalTypeMismatch {
ip: usize,
slot: usize,
},
CallArgMismatch {
ip: usize,
callee: usize,
arg: usize,
},
ArrayStrideMismatch {
ip: usize,
element_size: u32,
array_size: u32,
},
SharedSlotOutOfBounds {
slot: usize,
offset: usize,
size: usize,
buffer: u32,
},
EnumBodySizeMismatch {
ip: usize,
baked: u32,
},
PrivateCompositeLayoutInvalid {
index: usize,
slot: usize,
reason: &'static str,
},
SharedLayoutCountMismatch {
shared_slots: usize,
layout_len: usize,
reason: &'static str,
},
}
#[derive(Clone, Debug, Default)]
pub struct ChunkSig {
pub locals: Vec<AbsVal>,
pub resume: Option<AbsVal>,
}
impl ChunkSig {
fn from_signature(sig: &ChunkSignature) -> ChunkSig {
let locals = sig.params.iter().map(AbsVal::from_wire).collect();
let resume = match &sig.resume {
WireShape::Top => None,
other => Some(AbsVal::from_wire(other)),
};
ChunkSig { locals, resume }
}
fn local(&self, i: usize) -> AbsVal {
self.locals.get(i).cloned().unwrap_or(AbsVal::Top)
}
fn resume_shape(&self) -> AbsVal {
self.resume.clone().unwrap_or(AbsVal::Top)
}
}
pub fn typed_check_chunk(
chunk: &Chunk,
word_bytes: usize,
float_bytes: usize,
) -> Result<(), TypedError> {
typed_check_chunk_with_sig(chunk, &ChunkSig::default(), word_bytes, float_bytes)
}
pub fn typed_check_chunk_with_sig(
chunk: &Chunk,
sig: &ChunkSig,
word_bytes: usize,
float_bytes: usize,
) -> Result<(), TypedError> {
check_chunk_seeded(chunk, sig, &[], &[], &[], word_bytes, float_bytes)
}
pub fn typed_check_module(
module: &Module,
word_bytes: usize,
float_bytes: usize,
) -> Result<(), TypedError> {
validate_data_layout(module, word_bytes, float_bytes)?;
let enum_body_sizes: Vec<u32> = module
.enum_layouts
.iter()
.map(|el| (word_bytes as u32).saturating_add(el.min_payload))
.collect();
let default_sig = ChunkSig::default();
for (i, chunk) in module.chunks.iter().enumerate() {
let seeded = module.signatures.get(i).map(ChunkSig::from_signature);
let sig = seeded.as_ref().unwrap_or(&default_sig);
check_chunk_seeded(
chunk,
sig,
&module.signatures,
&enum_body_sizes,
&module.native_return_shapes,
word_bytes,
float_bytes,
)?;
}
Ok(())
}
fn validate_data_layout(
module: &Module,
word_bytes: usize,
float_bytes: usize,
) -> Result<(), TypedError> {
let Some(layout) = &module.data_layout else {
return Ok(());
};
let prefix_shared = layout
.slots
.iter()
.take_while(|s| matches!(s.visibility, crate::bytecode::SlotVisibility::Shared))
.count();
let total_shared = layout
.slots
.iter()
.filter(|s| matches!(s.visibility, crate::bytecode::SlotVisibility::Shared))
.count();
if prefix_shared != total_shared {
return Err(TypedError::SharedLayoutCountMismatch {
shared_slots: total_shared,
layout_len: layout.shared_layout.len(),
reason: "shared slots are not a contiguous prefix of the slot array",
});
}
if layout.shared_layout.len() != total_shared {
return Err(TypedError::SharedLayoutCountMismatch {
shared_slots: total_shared,
layout_len: layout.shared_layout.len(),
reason: "shared-slot layout entry count does not match the shared-slot count",
});
}
let buffer = module.shared_data_bytes;
for (slot, sl) in layout.shared_layout.iter().enumerate() {
let size = if sl.kind & crate::bytecode::SHARED_SLOT_COMPOSITE_FLAG != 0 {
usize::from(sl.len)
} else {
match ScalarKind::from_tag(sl.kind) {
Some(k) => k.size_in_bytes(word_bytes, float_bytes),
None => {
return Err(TypedError::SharedSlotOutOfBounds {
slot,
offset: usize::from(sl.offset),
size: 0,
buffer,
});
}
}
};
let need = usize::from(sl.offset) + size;
if need > buffer as usize {
return Err(TypedError::SharedSlotOutOfBounds {
slot,
offset: usize::from(sl.offset),
size,
buffer,
});
}
}
let data_len = layout.slots.len();
let pool = module.persistent_composite_bytes;
let mut prev: Option<(u16, u32)> = None;
for (index, e) in layout.private_composite_layout.iter().enumerate() {
let invalid = |reason| TypedError::PrivateCompositeLayoutInvalid {
index,
slot: usize::from(e.slot),
reason,
};
if usize::from(e.slot) >= data_len {
return Err(invalid("slot index out of range"));
}
if e.offset >= pool {
return Err(invalid("pool offset outside the persistent composite pool"));
}
if let Some((pslot, poff)) = prev {
if e.slot <= pslot {
return Err(invalid("slots are not strictly ascending"));
}
if e.offset <= poff {
return Err(invalid("pool offsets are not strictly ascending"));
}
}
prev = Some((e.slot, e.offset));
}
Ok(())
}
fn check_chunk_seeded(
chunk: &Chunk,
sig: &ChunkSig,
module_sigs: &[ChunkSignature],
enum_body_sizes: &[u32],
native_return_shapes: &[WireShape],
word_bytes: usize,
float_bytes: usize,
) -> Result<(), TypedError> {
let ctx = Ctx {
chunk,
sig,
module_sigs,
enum_body_sizes,
native_return_shapes,
wb: word_bytes,
fb: float_bytes,
};
let locals: Vec<AbsVal> = (0..chunk.local_count as usize)
.map(|i| sig.local(i))
.collect();
let state = AbsState {
stack: Vec::new(),
locals,
};
let mut breaks: Vec<AbsState> = Vec::new();
interp_region(&ctx, 0, chunk.ops.len(), state, &mut breaks).map(|_| ())
}
#[derive(Clone)]
struct AbsState {
stack: Vec<AbsVal>,
locals: Vec<AbsVal>,
}
struct Ctx<'a> {
chunk: &'a Chunk,
sig: &'a ChunkSig,
module_sigs: &'a [ChunkSignature],
enum_body_sizes: &'a [u32],
native_return_shapes: &'a [WireShape],
wb: usize,
fb: usize,
}
fn interp_region(
ctx: &Ctx,
start: usize,
end: usize,
mut state: AbsState,
breaks: &mut Vec<AbsState>,
) -> Result<Option<AbsState>, TypedError> {
let ops = &ctx.chunk.ops;
let mut ip = start;
while ip < end {
let op = &ops[ip];
match op {
Op::Trap(_) | Op::Return => return Ok(None),
Op::Break(_) => {
breaks.push(state);
return Ok(None);
}
Op::BreakIf(_) => {
let AbsState { stack, locals } = &mut state;
apply_op(ctx, op, stack, locals, ip)?;
breaks.push(state.clone());
ip += 1;
}
Op::If(target) => {
{
let AbsState { stack, locals } = &mut state;
apply_op(ctx, op, stack, locals, ip)?;
}
let target = *target as usize;
if target > 0 && matches!(&ops[target - 1], Op::Else(_)) {
let endif = match &ops[target - 1] {
Op::Else(e) => *e as usize,
_ => unreachable!(),
};
let then_end = interp_region(ctx, ip + 1, target - 1, state.clone(), breaks)?;
let else_end = interp_region(ctx, target, endif, state, breaks)?;
match join_ends(then_end, else_end, ip)? {
Some(joined) => state = joined,
None => return Ok(None),
}
ip = endif + 1;
} else {
let skip = state.clone();
let then_end = interp_region(ctx, ip + 1, target, state, breaks)?;
match join_ends(then_end, Some(skip), ip)? {
Some(joined) => state = joined,
None => return Ok(None),
}
ip = target + 1;
}
}
Op::Loop(target) => {
let exit = *target as usize;
let entry_height = state.stack.len();
let mut head = state.clone();
let cap = head.locals.len() + 2;
let mut loop_breaks: Vec<AbsState> = Vec::new();
let mut body_end;
let mut iters = 0usize;
loop {
loop_breaks.clear();
body_end =
interp_region(ctx, ip + 1, exit - 1, head.clone(), &mut loop_breaks)?;
let Some(be) = &body_end else {
break;
};
if be.stack != head.stack {
return Err(TypedError::LoopNotNeutral {
ip,
entry_height,
exit_height: be.stack.len(),
});
}
let widened = join_locals(&head.locals, &be.locals);
if widened == head.locals {
break; }
if iters >= cap {
invalidate_written_locals(ctx.chunk, ip + 1, exit - 1, &mut head.locals);
loop_breaks.clear();
body_end =
interp_region(ctx, ip + 1, exit - 1, head.clone(), &mut loop_breaks)?;
if let Some(be) = &body_end
&& be.stack != head.stack
{
return Err(TypedError::LoopNotNeutral {
ip,
entry_height,
exit_height: be.stack.len(),
});
}
break;
}
head.locals = widened;
iters += 1;
}
state = match join_all(loop_breaks, ip)? {
Some(s) => s,
None => body_end.unwrap_or(head),
};
ip = exit;
}
_ => {
let AbsState { stack, locals } = &mut state;
apply_op(ctx, op, stack, locals, ip)?;
ip += 1;
}
}
}
Ok(Some(state))
}
fn invalidate_written_locals(chunk: &Chunk, start: usize, end: usize, locals: &mut [AbsVal]) {
for op in &chunk.ops[start..end] {
if let Op::SetLocal(i) = op
&& let Some(slot) = locals.get_mut(*i as usize)
{
*slot = AbsVal::Top;
}
}
}
fn join_stacks(a: &[AbsVal], b: &[AbsVal], ip: usize) -> Result<Vec<AbsVal>, TypedError> {
if a.len() != b.len() {
return Err(TypedError::BranchHeightMismatch {
ip,
then_height: a.len(),
else_height: b.len(),
});
}
Ok(a.iter().zip(b.iter()).map(|(x, y)| x.join(y)).collect())
}
fn join_locals(a: &[AbsVal], b: &[AbsVal]) -> Vec<AbsVal> {
let n = a.len().max(b.len());
(0..n)
.map(|i| {
let x = a.get(i).unwrap_or(&AbsVal::Top);
let y = b.get(i).unwrap_or(&AbsVal::Top);
x.join(y)
})
.collect()
}
fn join_ends(
then_end: Option<AbsState>,
else_end: Option<AbsState>,
ip: usize,
) -> Result<Option<AbsState>, TypedError> {
match (then_end, else_end) {
(Some(a), Some(b)) => Ok(Some(AbsState {
stack: join_stacks(&a.stack, &b.stack, ip)?,
locals: join_locals(&a.locals, &b.locals),
})),
(Some(a), None) => Ok(Some(a)),
(None, Some(b)) => Ok(Some(b)),
(None, None) => Ok(None),
}
}
fn join_all(states: Vec<AbsState>, ip: usize) -> Result<Option<AbsState>, TypedError> {
let mut it = states.into_iter();
let Some(mut acc) = it.next() else {
return Ok(None);
};
for s in it {
acc = AbsState {
stack: join_stacks(&acc.stack, &s.stack, ip)?,
locals: join_locals(&acc.locals, &s.locals),
};
}
Ok(Some(acc))
}
fn apply_op(
ctx: &Ctx,
op: &Op,
stack: &mut Vec<AbsVal>,
locals: &mut [AbsVal],
ip: usize,
) -> Result<(), TypedError> {
let (req, net) = op_depth_effect(op, ctx.chunk);
if (stack.len() as i32) < req {
return Err(TypedError::StackUnderflow { ip });
}
match op {
Op::Dup => {
let top = stack.last().cloned().unwrap_or(AbsVal::Top);
stack.push(top);
}
Op::IsEnum(_, _, _) | Op::IsStruct(_) => stack.push(AbsVal::Scalar(ScalarKind::Bool)),
Op::GetLocal(i) => {
let shape = locals.get(*i as usize).cloned().unwrap_or(AbsVal::Top);
stack.push(shape);
}
Op::SetLocal(i) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
if !shapes_compatible(&v, &ctx.sig.local(*i as usize)) {
return Err(TypedError::LocalTypeMismatch {
ip,
slot: *i as usize,
});
}
if let Some(slot) = locals.get_mut(*i as usize) {
*slot = v;
}
}
Op::Yield => {
stack.pop();
stack.push(ctx.sig.resume_shape());
}
Op::Const(idx) => stack.push(const_abs(ctx.chunk.constants.get(*idx as usize))),
Op::NewComposite(NewCompositeOperand::Flat {
kind,
count,
byte_size,
}) => {
let mut computed: u32 = 0;
let mut all_known = true;
for _ in 0..*count {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
match v.packed_size(ctx.wb, ctx.fb) {
Some(n) => computed = computed.saturating_add(n),
None => all_known = false,
}
}
if matches!(kind, CompositeKind::Enum) {
if !ctx.enum_body_sizes.is_empty()
&& !ctx.enum_body_sizes.contains(&u32::from(*byte_size))
{
return Err(TypedError::EnumBodySizeMismatch {
ip,
baked: u32::from(*byte_size),
});
}
} else if all_known && computed != u32::from(*byte_size) {
return Err(TypedError::NewCompositeSizeMismatch {
ip,
baked: u32::from(*byte_size),
computed,
});
}
stack.push(AbsVal::Flat {
kind: *kind,
size: u32::from(*byte_size),
});
}
Op::GetField(StructField::Flat { offset, kind }) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_scalar(&v, *offset, *kind, ctx.wb, ctx.fb, ip)?;
stack.push(AbsVal::Scalar(*kind));
}
Op::GetField(StructField::FlatNested {
offset,
size,
variant,
}) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_nested(&v, *offset, *size, ip)?;
stack.push(AbsVal::Flat {
kind: *variant,
size: u32::from(*size),
});
}
Op::GetTupleField(TupleField::Flat { offset, kind }) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_scalar(&v, *offset, *kind, ctx.wb, ctx.fb, ip)?;
stack.push(AbsVal::Scalar(*kind));
}
Op::GetTupleField(TupleField::FlatNested {
offset,
size,
variant,
}) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_nested(&v, *offset, *size, ip)?;
stack.push(AbsVal::Flat {
kind: *variant,
size: u32::from(*size),
});
}
Op::GetEnumField(EnumField::Flat { offset, kind }) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_scalar(&v, *offset, *kind, ctx.wb, ctx.fb, ip)?;
stack.push(AbsVal::Scalar(*kind));
}
Op::GetEnumField(EnumField::FlatNested {
offset,
size,
variant,
}) => {
let v = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_nested(&v, *offset, *size, ip)?;
stack.push(AbsVal::Flat {
kind: *variant,
size: u32::from(*size),
});
}
Op::GetIndex(ArrayElem::Flat { kind }) => {
let _index = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
let arr = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
let elem = kind.size_in_bytes(ctx.wb, ctx.fb) as u32;
check_flat_array_stride(&arr, elem, ip)?;
stack.push(AbsVal::Scalar(*kind));
}
Op::GetIndex(ArrayElem::FlatNested { size, variant }) => {
let _index = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
let arr = stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
check_flat_array_stride(&arr, u32::from(*size), ip)?;
stack.push(AbsVal::Flat {
kind: *variant,
size: u32::from(*size),
});
}
Op::Call(callee, n) => {
let argc = *n as usize;
let mut args: Vec<AbsVal> = Vec::with_capacity(argc);
for _ in 0..argc {
args.push(stack.pop().ok_or(TypedError::StackUnderflow { ip })?);
}
args.reverse();
match ctx.module_sigs.get(*callee as usize) {
Some(callee_sig) => {
for (arg, declared) in callee_sig.params.iter().enumerate() {
let expected = AbsVal::from_wire(declared);
if let Some(actual) = args.get(arg)
&& !shapes_compatible(actual, &expected)
{
return Err(TypedError::CallArgMismatch {
ip,
callee: *callee as usize,
arg,
});
}
}
stack.push(AbsVal::from_wire(&callee_sig.ret));
}
None => stack.push(AbsVal::Top),
}
}
Op::CallVerifiedNative(idx, n) | Op::CallExternalNative(idx, n) => {
let argc = (*n & 0x7F) as usize;
for _ in 0..argc {
stack.pop().ok_or(TypedError::StackUnderflow { ip })?;
}
let value = ctx
.native_return_shapes
.get(*idx as usize)
.map(AbsVal::from_wire)
.unwrap_or(AbsVal::Top);
stack.push(value);
if *n & 0x80 != 0 {
stack.push(AbsVal::Scalar(ScalarKind::Int));
}
}
_ => {
for _ in 0..req {
stack.pop();
}
let produced = net + req;
for _ in 0..produced.max(0) {
stack.push(AbsVal::Top);
}
}
}
Ok(())
}
fn check_flat_scalar(
v: &AbsVal,
offset: u16,
kind: ScalarKind,
wb: usize,
fb: usize,
ip: usize,
) -> Result<(), TypedError> {
match v {
AbsVal::Flat { size, .. } => {
let need = usize::from(offset) + kind.size_in_bytes(wb, fb);
if need > *size as usize {
return Err(TypedError::OffsetOutOfBounds {
ip,
offset: usize::from(offset),
need,
size: *size,
});
}
Ok(())
}
AbsVal::Scalar(_) => Err(TypedError::ExpectedComposite { ip }),
AbsVal::Top => Ok(()),
}
}
fn check_flat_nested(v: &AbsVal, offset: u16, size: u16, ip: usize) -> Result<(), TypedError> {
match v {
AbsVal::Flat { size: parent, .. } => {
let need = usize::from(offset) + usize::from(size);
if need > *parent as usize {
return Err(TypedError::OffsetOutOfBounds {
ip,
offset: usize::from(offset),
need,
size: *parent,
});
}
Ok(())
}
AbsVal::Scalar(_) => Err(TypedError::ExpectedComposite { ip }),
AbsVal::Top => Ok(()),
}
}
fn check_flat_array_stride(arr: &AbsVal, element_size: u32, ip: usize) -> Result<(), TypedError> {
match arr {
AbsVal::Flat { size, .. } => {
if element_size != 0 && *size % element_size != 0 {
return Err(TypedError::ArrayStrideMismatch {
ip,
element_size,
array_size: *size,
});
}
Ok(())
}
AbsVal::Scalar(_) => Err(TypedError::ExpectedComposite { ip }),
AbsVal::Top => Ok(()),
}
}
fn shapes_compatible(value: &AbsVal, declared: &AbsVal) -> bool {
match (value, declared) {
(AbsVal::Top, _) | (_, AbsVal::Top) => true,
(AbsVal::Scalar(_), AbsVal::Scalar(_)) => true,
(AbsVal::Flat { kind: k1, size: s1 }, AbsVal::Flat { kind: k2, size: s2 }) => {
k1 == k2 && s1 == s2
}
_ => false,
}
}
fn const_abs(cv: Option<&ConstValue>) -> AbsVal {
match cv {
Some(ConstValue::Unit) => AbsVal::Scalar(ScalarKind::Unit),
Some(ConstValue::Bool(_)) => AbsVal::Scalar(ScalarKind::Bool),
Some(ConstValue::Int(_)) => AbsVal::Scalar(ScalarKind::Int),
Some(ConstValue::Byte(_)) => AbsVal::Scalar(ScalarKind::Byte),
Some(ConstValue::Fixed(_)) => AbsVal::Scalar(ScalarKind::Fixed),
#[cfg(feature = "floats")]
Some(ConstValue::Float(_)) => AbsVal::Scalar(ScalarKind::Float),
Some(ConstValue::StaticStr(_)) => AbsVal::Scalar(ScalarKind::Text),
_ => AbsVal::Top,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::{BlockType, Chunk};
use alloc::string::String;
use alloc::vec;
fn chunk(ops: Vec<Op>, constants: Vec<ConstValue>) -> Chunk {
Chunk {
name: String::from("t"),
ops,
constants,
struct_templates: Vec::new(),
local_count: 8,
param_count: 0,
block_type: BlockType::Func,
param_types: Vec::new(),
debug_pool: None,
}
}
fn two_int_struct(byte_size: u16) -> Vec<Op> {
vec![
Op::Const(0),
Op::Const(1),
Op::NewComposite(NewCompositeOperand::Flat {
kind: CompositeKind::Struct,
count: 2,
byte_size,
}),
]
}
fn ints() -> Vec<ConstValue> {
vec![ConstValue::Int(0), ConstValue::Int(0)]
}
fn module(chunks: Vec<Chunk>, signatures: Vec<ChunkSignature>) -> Module {
Module {
chunks,
signatures,
native_return_shapes: Vec::new(),
native_names: Vec::new(),
entry_point: None,
data_layout: None,
word_bits_log2: 6,
addr_bits_log2: 6,
float_bits_log2: 6,
wcet_cycles: 0,
wcmu_bytes: 0,
aux_arena_bytes: 0,
persistent_composite_bytes: 0,
flags: 0,
shared_data_bytes: 0,
private_data_bytes: 0,
schema_hash: 0,
enum_layouts: Vec::new(),
}
}
#[test]
fn valid_flat_field_access_accepts() {
let mut ops = two_int_struct(16);
ops.push(Op::GetField(StructField::Flat {
offset: 8,
kind: ScalarKind::Int,
}));
ops.push(Op::PopN(1));
assert!(typed_check_chunk(&chunk(ops, ints()), 8, 8).is_ok());
}
#[test]
fn out_of_bounds_flat_field_offset_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::GetField(StructField::Flat {
offset: 12,
kind: ScalarKind::Int,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn nested_flat_field_out_of_bounds_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::GetField(StructField::FlatNested {
offset: 8,
size: 12,
variant: CompositeKind::Struct,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn newcomposite_size_mismatch_rejects() {
assert!(matches!(
typed_check_chunk(&chunk(two_int_struct(24), ints()), 8, 8),
Err(TypedError::NewCompositeSizeMismatch { .. })
));
}
#[test]
fn tuple_field_out_of_bounds_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::GetTupleField(TupleField::Flat {
offset: 12,
kind: ScalarKind::Int,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn tuple_field_in_bounds_accepts() {
let mut ops = two_int_struct(16);
ops.push(Op::GetTupleField(TupleField::Flat {
offset: 8,
kind: ScalarKind::Int,
}));
assert!(typed_check_chunk(&chunk(ops, ints()), 8, 8).is_ok());
}
#[test]
fn enum_field_out_of_bounds_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::GetEnumField(EnumField::Flat {
offset: 12,
kind: ScalarKind::Int,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn enum_nested_field_out_of_bounds_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::GetEnumField(EnumField::FlatNested {
offset: 8,
size: 12,
variant: CompositeKind::Struct,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn array_index_valid_stride_accepts() {
let mut ops = two_int_struct(16); ops.push(Op::Const(0)); ops.push(Op::GetIndex(ArrayElem::Flat {
kind: ScalarKind::Int, }));
assert!(typed_check_chunk(&chunk(ops, ints()), 8, 8).is_ok());
}
#[test]
fn array_index_bad_stride_rejects() {
let ops = vec![
Op::GetLocal(0),
Op::Const(0),
Op::GetIndex(ArrayElem::FlatNested {
size: 8,
variant: CompositeKind::Struct,
}),
];
let sig = ChunkSig {
locals: vec![AbsVal::Flat {
kind: CompositeKind::Array,
size: 12,
}],
resume: None,
};
assert!(matches!(
typed_check_chunk_with_sig(&chunk(ops, ints()), &sig, 8, 8),
Err(TypedError::ArrayStrideMismatch { .. })
));
}
#[test]
fn static_str_const_is_scalar_text() {
let ops = vec![
Op::Const(0),
Op::GetField(StructField::Flat {
offset: 0,
kind: ScalarKind::Int,
}),
];
let consts = vec![ConstValue::StaticStr(String::from("hi"))];
assert!(matches!(
typed_check_chunk(&chunk(ops, consts), 8, 8),
Err(TypedError::ExpectedComposite { .. })
));
}
#[test]
fn shared_slot_out_of_bounds_rejects() {
use crate::bytecode::{DataLayout, DataSlot, SharedSlotLayout, SlotVisibility};
let mut m = module(vec![chunk(vec![Op::Return], ints())], vec![]);
m.shared_data_bytes = 16;
m.data_layout = Some(DataLayout {
slots: vec![DataSlot {
name: String::from("s"),
visibility: SlotVisibility::Shared,
}],
shared_layout: vec![SharedSlotLayout {
offset: 12,
kind: ScalarKind::Int.to_tag(),
len: 0,
}],
private_composite_layout: Vec::new(),
});
assert!(matches!(
typed_check_module(&m, 8, 8),
Err(TypedError::SharedSlotOutOfBounds { .. })
));
}
#[test]
fn shared_slot_in_bounds_accepts() {
use crate::bytecode::{DataLayout, DataSlot, SharedSlotLayout, SlotVisibility};
let mut m = module(vec![chunk(vec![Op::Return], ints())], vec![]);
m.shared_data_bytes = 16;
m.data_layout = Some(DataLayout {
slots: vec![DataSlot {
name: String::from("s"),
visibility: SlotVisibility::Shared,
}],
shared_layout: vec![SharedSlotLayout {
offset: 8, kind: ScalarKind::Int.to_tag(),
len: 0,
}],
private_composite_layout: Vec::new(),
});
assert!(typed_check_module(&m, 8, 8).is_ok());
}
#[test]
fn shared_layout_count_mismatch_rejects() {
use crate::bytecode::{DataLayout, DataSlot, SharedSlotLayout, SlotVisibility};
let mut m = module(vec![chunk(vec![Op::Return], ints())], vec![]);
m.shared_data_bytes = 16;
m.data_layout = Some(DataLayout {
slots: vec![
DataSlot {
name: String::from("a"),
visibility: SlotVisibility::Shared,
},
DataSlot {
name: String::from("b"),
visibility: SlotVisibility::Shared,
},
],
shared_layout: vec![SharedSlotLayout {
offset: 0,
kind: ScalarKind::Int.to_tag(),
len: 0,
}],
private_composite_layout: Vec::new(),
});
assert!(matches!(
typed_check_module(&m, 8, 8),
Err(TypedError::SharedLayoutCountMismatch { .. })
));
}
#[test]
fn shared_slots_not_a_prefix_rejects() {
use crate::bytecode::{DataLayout, DataSlot, SharedSlotLayout, SlotVisibility};
let mut m = module(vec![chunk(vec![Op::Return], ints())], vec![]);
m.shared_data_bytes = 16;
m.data_layout = Some(DataLayout {
slots: vec![
DataSlot {
name: String::from("p"),
visibility: SlotVisibility::Private,
},
DataSlot {
name: String::from("s"),
visibility: SlotVisibility::Shared,
},
],
shared_layout: vec![SharedSlotLayout {
offset: 0,
kind: ScalarKind::Int.to_tag(),
len: 0,
}],
private_composite_layout: Vec::new(),
});
assert!(matches!(
typed_check_module(&m, 8, 8),
Err(TypedError::SharedLayoutCountMismatch { .. })
));
}
#[test]
fn private_composite_layout_unsorted_rejects() {
use crate::bytecode::{DataLayout, DataSlot, PrivateCompositeSlot, SlotVisibility};
let slots = vec![
DataSlot {
name: alloc::string::String::from("a"),
visibility: SlotVisibility::Private,
},
DataSlot {
name: alloc::string::String::from("b"),
visibility: SlotVisibility::Private,
},
];
let mk = |table: Vec<PrivateCompositeSlot>| {
let mut m = module(vec![chunk(vec![Op::Return], ints())], vec![]);
m.persistent_composite_bytes = 64;
m.data_layout = Some(DataLayout {
slots: slots.clone(),
shared_layout: Vec::new(),
private_composite_layout: table,
});
m
};
assert!(matches!(
typed_check_module(
&mk(vec![
PrivateCompositeSlot { slot: 1, offset: 0 },
PrivateCompositeSlot {
slot: 0,
offset: 16
},
]),
8,
8
),
Err(TypedError::PrivateCompositeLayoutInvalid { .. })
));
assert!(matches!(
typed_check_module(&mk(vec![PrivateCompositeSlot { slot: 5, offset: 0 }]), 8, 8),
Err(TypedError::PrivateCompositeLayoutInvalid { .. })
));
assert!(matches!(
typed_check_module(
&mk(vec![PrivateCompositeSlot {
slot: 0,
offset: 64
}]),
8,
8
),
Err(TypedError::PrivateCompositeLayoutInvalid { .. })
));
assert!(
typed_check_module(
&mk(vec![
PrivateCompositeSlot { slot: 0, offset: 0 },
PrivateCompositeSlot {
slot: 1,
offset: 16
},
]),
8,
8
)
.is_ok()
);
}
fn enum_construct_chunk(byte_size: u16) -> Chunk {
chunk(
vec![
Op::Const(0), Op::Const(1), Op::NewComposite(NewCompositeOperand::Flat {
kind: CompositeKind::Enum,
count: 2,
byte_size,
}),
Op::Return,
],
ints(),
)
}
fn module_with_enum(byte_size: u16, min_payload: u32) -> Module {
use crate::bytecode::{EnumLayout, EnumVariantDisc};
let mut m = module(vec![enum_construct_chunk(byte_size)], vec![]);
m.enum_layouts = vec![EnumLayout {
type_name: String::from("E"),
variants: vec![EnumVariantDisc {
name: String::from("A"),
disc: 0,
}],
min_payload,
}];
m
}
#[test]
fn enum_body_size_matches_layout_accepts() {
assert!(typed_check_module(&module_with_enum(16, 8), 8, 8).is_ok());
}
#[test]
fn enum_body_size_mismatch_rejects() {
assert!(matches!(
typed_check_module(&module_with_enum(24, 8), 8, 8),
Err(TypedError::EnumBodySizeMismatch { .. })
));
}
#[test]
fn mutated_min_payload_rejects_construction() {
assert!(matches!(
typed_check_module(&module_with_enum(16, 4), 8, 8),
Err(TypedError::EnumBodySizeMismatch { .. })
));
}
#[test]
fn field_access_on_scalar_rejects() {
let ops = vec![
Op::Const(0),
Op::GetField(StructField::Flat {
offset: 0,
kind: ScalarKind::Int,
}),
];
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::ExpectedComposite { .. })
));
}
#[test]
fn stack_underflow_rejects() {
let ops = vec![Op::GetField(StructField::Flat {
offset: 0,
kind: ScalarKind::Int,
})];
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::StackUnderflow { .. })
));
}
#[test]
fn seeded_local_composite_offset_checks() {
let ops = vec![
Op::GetLocal(0),
Op::GetField(StructField::Flat {
offset: 8,
kind: ScalarKind::Int,
}),
];
let c = chunk(ops, Vec::new());
assert!(typed_check_chunk(&c, 8, 8).is_ok());
let sig = ChunkSig {
locals: vec![AbsVal::Flat {
kind: CompositeKind::Struct,
size: 16,
}],
resume: None,
};
assert!(typed_check_chunk_with_sig(&c, &sig, 8, 8).is_ok());
let bad = chunk(
vec![
Op::GetLocal(0),
Op::GetField(StructField::Flat {
offset: 12,
kind: ScalarKind::Int,
}),
],
Vec::new(),
);
assert!(matches!(
typed_check_chunk_with_sig(&bad, &sig, 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn seeded_local_scalar_field_access_rejects() {
let ops = vec![
Op::GetLocal(0),
Op::GetField(StructField::Flat {
offset: 0,
kind: ScalarKind::Int,
}),
];
let sig = ChunkSig {
locals: vec![AbsVal::Scalar(ScalarKind::Int)],
resume: None,
};
assert!(matches!(
typed_check_chunk_with_sig(&chunk(ops, Vec::new()), &sig, 8, 8),
Err(TypedError::ExpectedComposite { .. })
));
}
#[test]
fn yield_resume_shape_is_seeded() {
let sig = ChunkSig {
locals: Vec::new(),
resume: Some(AbsVal::Flat {
kind: CompositeKind::Struct,
size: 16,
}),
};
let good = chunk(
vec![
Op::Const(0),
Op::Yield,
Op::GetField(StructField::Flat {
offset: 8,
kind: ScalarKind::Int,
}),
],
ints(),
);
assert!(typed_check_chunk_with_sig(&good, &sig, 8, 8).is_ok());
let bad = chunk(
vec![
Op::Const(0),
Op::Yield,
Op::GetField(StructField::Flat {
offset: 12,
kind: ScalarKind::Int,
}),
],
ints(),
);
assert!(matches!(
typed_check_chunk_with_sig(&bad, &sig, 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn setlocal_shape_mismatch_rejects() {
let ops = vec![Op::Const(0), Op::SetLocal(0)];
let sig = ChunkSig {
locals: vec![AbsVal::Flat {
kind: CompositeKind::Struct,
size: 16,
}],
resume: None,
};
assert!(matches!(
typed_check_chunk_with_sig(&chunk(ops, ints()), &sig, 8, 8),
Err(TypedError::LocalTypeMismatch { .. })
));
}
#[test]
fn setlocal_matching_shape_accepts() {
let ops = vec![
Op::Const(0),
Op::Const(1),
Op::NewComposite(NewCompositeOperand::Flat {
kind: CompositeKind::Struct,
count: 2,
byte_size: 16,
}),
Op::SetLocal(0),
];
let sig = ChunkSig {
locals: vec![AbsVal::Flat {
kind: CompositeKind::Struct,
size: 16,
}],
resume: None,
};
assert!(typed_check_chunk_with_sig(&chunk(ops, ints()), &sig, 8, 8).is_ok());
}
#[test]
fn tracked_local_composite_offset_out_of_bounds_rejects() {
let mut ops = two_int_struct(16);
ops.push(Op::SetLocal(0));
ops.push(Op::GetLocal(0));
ops.push(Op::GetField(StructField::Flat {
offset: 12,
kind: ScalarKind::Int,
}));
assert!(matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn tracked_local_composite_valid_offset_accepts() {
let mut ops = two_int_struct(16);
ops.push(Op::SetLocal(0));
ops.push(Op::GetLocal(0));
ops.push(Op::GetField(StructField::Flat {
offset: 8,
kind: ScalarKind::Int,
}));
assert!(typed_check_chunk(&chunk(ops, ints()), 8, 8).is_ok());
}
fn loop_carried_local_chunk(field_offset: u16) -> Chunk {
let mut ops = two_int_struct(16); ops.push(Op::SetLocal(0)); ops.push(Op::Loop(14)); ops.push(Op::GetLocal(0)); ops.push(Op::GetField(StructField::Flat {
offset: field_offset,
kind: ScalarKind::Int,
})); ops.push(Op::PopN(1)); ops.extend(two_int_struct(16)); ops.push(Op::SetLocal(0)); ops.push(Op::Break(14)); ops.push(Op::EndLoop(5)); ops.push(Op::Return); chunk(ops, ints())
}
#[test]
fn loop_carried_local_stable_shape_is_proven() {
assert!(
typed_check_chunk(&loop_carried_local_chunk(8), 8, 8).is_ok(),
"a stable loop-carried composite local must be proven and its field access accepted"
);
}
#[test]
fn loop_carried_local_out_of_bounds_rejected() {
assert!(matches!(
typed_check_chunk(&loop_carried_local_chunk(12), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn loop_non_neutral_by_shape_rejects() {
let ops = vec![
Op::GetLocal(0), Op::Loop(5), Op::PopN(1), Op::Const(0), Op::EndLoop(0), Op::Return, ];
let sig = ChunkSig {
locals: vec![AbsVal::Flat {
kind: CompositeKind::Struct,
size: 16,
}],
resume: None,
};
assert!(
matches!(
typed_check_chunk_with_sig(&chunk(ops, ints()), &sig, 8, 8),
Err(TypedError::LoopNotNeutral { .. })
),
"a loop back-edge that changes a slot's shape at equal height must be rejected"
);
}
#[test]
fn balanced_if_else_accepts() {
let ops = vec![
Op::Const(0), Op::If(4), Op::Const(0), Op::Else(5), Op::Const(0), Op::EndIf, Op::PopN(1), ];
assert!(
typed_check_chunk(&chunk(ops, ints()), 8, 8).is_ok(),
"balanced branches should verify"
);
}
#[test]
fn imbalanced_if_else_rejects() {
let ops = vec![
Op::Const(0), Op::If(4), Op::Const(0), Op::Else(4), Op::EndIf, Op::PopN(1), ];
assert!(
matches!(
typed_check_chunk(&chunk(ops, ints()), 8, 8),
Err(TypedError::BranchHeightMismatch { .. })
),
"imbalanced branches must be rejected"
);
}
#[cfg(feature = "compile")]
#[test]
fn accepts_balanced_real_programs() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let programs = [
"struct P { x: Word, y: Word }\n\
fn main() -> Word { let p = P { x: 1, y: 2 }; p.x + p.y }",
"fn add(a: Word, b: Word) -> Word { a + b }\n\
fn main() -> Word { add(3, 4) }",
"fn main() -> Word { let x = 3; if x > 0 { x } else { 0 - x } }",
"fn main() -> Word { for i in 0..4 { i } 0 }",
"loop main(i: Word) -> Word { let n = yield i * 2; n }",
];
for src in programs {
let module =
compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
let wb = (1usize << module.word_bits_log2) / 8;
let fb = (1usize << module.float_bits_log2) / 8;
for c in &module.chunks {
let r = typed_check_chunk(c, wb, fb);
assert!(
r.is_ok(),
"balanced real chunk `{}` from `{}` should verify, got {:?}",
c.name,
src,
r
);
}
}
}
#[cfg(feature = "compile")]
#[test]
fn typed_check_module_accepts_seeded_real_programs() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let programs = [
"struct P { x: Word, y: Word }\n\
fn sum(p: P) -> Word { p.x + p.y }\n\
fn main() -> Word { sum(P { x: 1, y: 2 }) }",
"struct P { x: Word, y: Word }\n\
fn mk(a: Word) -> P { P { x: a, y: a } }\n\
fn main() -> Word { mk(3).y }",
"enum E { A(Word), B }\n\
fn f(e: E) -> Word { match e { E::A(v) => v, E::B => 0 } }\n\
fn main() -> Word { f(E::A(5)) + f(E::B) }",
"struct Q { p: (Word, Word), z: Word }\n\
fn g(q: Q) -> Word { q.z }\n\
fn main() -> Word { g(Q { p: (1, 2), z: 3 }) }",
"fn main() -> Word { let t = (10, 20); t.0 + t.1 }",
"fn main() -> Word { let a = [1, 2, 3, 4]; a[0] + a[3] }",
"loop main(i: Word) -> Word { let n = yield i * 2; n }",
];
for src in programs {
let module =
compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
let wb = (1usize << module.word_bits_log2) / 8;
let fb = (1usize << module.float_bits_log2) / 8;
let r = typed_check_module(&module, wb, fb);
assert!(
r.is_ok(),
"seeded module for `{}` should verify, got {:?}",
src,
r
);
}
}
#[cfg(feature = "compile")]
#[test]
fn balanced_match_verifies_after_b5_fix() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let src = "enum E { A(Word), B }\n\
fn f(e: E) -> Word { match e { E::A(v) => v, E::B => 0 } }\n\
fn main() -> Word { f(E::A(5)) }";
let module =
compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
let wb = (1usize << module.word_bits_log2) / 8;
let fb = (1usize << module.float_bits_log2) / 8;
for c in &module.chunks {
let r = typed_check_chunk(c, wb, fb);
assert!(
r.is_ok(),
"match chunk `{}` should verify after the B5 balancing fix, got {:?}",
c.name,
r
);
}
}
#[cfg(feature = "compile")]
#[test]
fn balanced_enum_to_word_cast_verifies_after_b5_fix() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let src = "enum Color { Red, Green, Blue }\n\
fn main() -> Word { Color::Blue as Word }";
let module =
compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
let wb = (1usize << module.word_bits_log2) / 8;
let fb = (1usize << module.float_bits_log2) / 8;
for c in &module.chunks {
let r = typed_check_chunk(c, wb, fb);
assert!(
r.is_ok(),
"enum-to-word cast chunk `{}` should verify after the B5 balancing fix, got {:?}",
c.name,
r
);
}
}
#[cfg(feature = "compile")]
#[test]
fn cross_call_composite_access_validates() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let src = "struct P { x: Word, y: Word }\n\
fn mk() -> P { P { x: 1, y: 2 } }\n\
fn main() -> Word { mk().x }";
let m = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
let wb = (1usize << m.word_bits_log2) / 8;
let fb = (1usize << m.float_bits_log2) / 8;
assert!(
typed_check_module(&m, wb, fb).is_ok(),
"a cross-call composite field access should verify under signature seeding"
);
assert_eq!(
m.signatures.len(),
m.chunks.len(),
"the compiler must emit one signature per chunk"
);
assert!(
m.signatures
.iter()
.any(|s| matches!(s.ret, WireShape::Flat { .. })),
"`mk`'s struct return must be recorded as a flat shape"
);
}
#[test]
fn call_arg_shape_mismatch_rejects() {
let callee = chunk(vec![Op::Const(0), Op::Return], ints());
let caller = chunk(vec![Op::Const(0), Op::Call(0, 1), Op::Return], ints());
let sigs = vec![
ChunkSignature {
params: vec![WireShape::Flat {
kind: CompositeKind::Struct.to_tag(),
size: 16,
}],
ret: WireShape::Scalar {
kind: ScalarKind::Int.to_tag(),
},
resume: WireShape::Top,
},
ChunkSignature::default(),
];
let m = module(vec![callee, caller], sigs);
assert!(
matches!(
typed_check_module(&m, 8, 8),
Err(TypedError::CallArgMismatch {
callee: 0,
arg: 0,
..
})
),
"a scalar argument to a struct parameter must be rejected"
);
}
#[test]
fn call_arg_shape_match_accepts() {
let callee = chunk(vec![Op::Const(0), Op::Return], ints());
let mut ops = two_int_struct(16);
ops.push(Op::Call(0, 1));
ops.push(Op::Return);
let caller = chunk(ops, ints());
let sigs = vec![
ChunkSignature {
params: vec![WireShape::Flat {
kind: CompositeKind::Struct.to_tag(),
size: 16,
}],
ret: WireShape::Scalar {
kind: ScalarKind::Int.to_tag(),
},
resume: WireShape::Top,
},
ChunkSignature::default(),
];
let m = module(vec![callee, caller], sigs);
assert!(typed_check_module(&m, 8, 8).is_ok());
}
fn native_struct_return_module(field_offset: u16) -> Module {
let ops = vec![
Op::CallVerifiedNative(0, 0), Op::GetField(StructField::Flat {
offset: field_offset,
kind: ScalarKind::Int,
}),
Op::Return,
];
let mut m = module(vec![chunk(ops, ints())], vec![]);
m.native_return_shapes = vec![WireShape::Flat {
kind: CompositeKind::Struct.to_tag(),
size: 16,
}];
m
}
#[test]
fn native_result_composite_field_access_validates() {
assert!(typed_check_module(&native_struct_return_module(8), 8, 8).is_ok());
}
#[test]
fn native_result_out_of_bounds_rejected() {
assert!(matches!(
typed_check_module(&native_struct_return_module(12), 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[test]
fn native_result_with_error_arm_seeds_value_beneath_flag() {
let ops = vec![
Op::CallVerifiedNative(0, 0x80), Op::PopN(1), Op::GetField(StructField::Flat {
offset: 12,
kind: ScalarKind::Int,
}), Op::Return,
];
let mut m = module(vec![chunk(ops, ints())], vec![]);
m.native_return_shapes = vec![WireShape::Flat {
kind: CompositeKind::Struct.to_tag(),
size: 16,
}];
assert!(matches!(
typed_check_module(&m, 8, 8),
Err(TypedError::OffsetOutOfBounds { .. })
));
}
#[cfg(feature = "compile")]
#[test]
fn native_result_seeding_end_to_end() {
use crate::compiler::compile;
use crate::lexer::tokenize;
use crate::parser::parse;
let src = "use make_point() -> Point\n\
struct Point { x: Word, y: Word }\n\
fn main() -> Word { let p = make_point(); p.x + p.y }";
let m = compile(&parse(&tokenize(src).expect("lex")).expect("parse")).expect("compile");
assert!(
m.native_return_shapes
.iter()
.any(|s| matches!(s, WireShape::Flat { .. })),
"the native's struct return must be recorded as a flat shape"
);
let wb = (1usize << m.word_bits_log2) / 8;
let fb = (1usize << m.float_bits_log2) / 8;
assert!(typed_check_module(&m, wb, fb).is_ok());
}
#[test]
fn loop_neutrality() {
let neutral = vec![
Op::Loop(5), Op::Const(0), Op::PopN(1), Op::Break(5), Op::EndLoop(0), Op::Return, ];
assert!(
typed_check_chunk(&chunk(neutral, ints()), 8, 8).is_ok(),
"a stack-neutral loop body should verify"
);
let non_neutral = vec![
Op::Loop(3), Op::Const(0), Op::EndLoop(0), Op::Return, ];
assert!(
matches!(
typed_check_chunk(&chunk(non_neutral, ints()), 8, 8),
Err(TypedError::LoopNotNeutral { .. })
),
"a non-neutral loop body must be rejected"
);
}
}