diff --git a/crates/console/prometeu-vm/src/builtins.rs b/crates/console/prometeu-vm/src/builtins.rs index b4f2184a..845761b7 100644 --- a/crates/console/prometeu-vm/src/builtins.rs +++ b/crates/console/prometeu-vm/src/builtins.rs @@ -109,6 +109,11 @@ impl BuiltinTypeMeta { out } + pub fn validate_flat_values(&self, values: &[Value]) -> Result<(), BuiltinValueError> { + let layout = self.flatten_layout(); + validate_values_against_layout(values, &layout) + } + fn flatten_into(&self, out: &mut Vec) { match self.shape { BuiltinTypeShape::Scalar => out.extend_from_slice(self.flat_slot_layout), @@ -153,6 +158,18 @@ impl BuiltinConstMeta { BuiltinConstMaterializer::Hook(_) => None, } } + + pub fn materialize(&self) -> Result, BuiltinValueError> { + let values = match self.materializer { + BuiltinConstMaterializer::Direct(slots) => { + slots.iter().copied().map(BuiltinConstSlotValue::into_value).collect() + } + BuiltinConstMaterializer::Hook(hook) => hook(), + }; + + validate_values_against_layout(&values, self.flat_slot_layout)?; + Ok(values) + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -161,6 +178,12 @@ pub enum IntrinsicExecutionError { TypeMismatch { index: usize, expected: AbiType }, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BuiltinValueError { + ArityMismatch { expected: usize, got: usize }, + TypeMismatch { index: usize, expected: AbiType }, +} + pub type IntrinsicImplementation = fn(&[Value]) -> Result, IntrinsicExecutionError>; #[derive(Debug, Clone, Copy)] @@ -207,6 +230,18 @@ impl IntrinsicMeta { } } +impl BuiltinConstSlotValue { + pub fn into_value(self) -> Value { + match self { + BuiltinConstSlotValue::Int32(value) => Value::Int32(value), + BuiltinConstSlotValue::Int64(value) => Value::Int64(value), + BuiltinConstSlotValue::Float(value) => Value::Float(value), + BuiltinConstSlotValue::Bool(value) => Value::Boolean(value), + BuiltinConstSlotValue::NominalBuiltin { carrier, .. } => Value::Int32(carrier), + } + } +} + const COLOR: BuiltinTypeKey = BuiltinTypeKey::new("color", 1); const VEC2: BuiltinTypeKey = BuiltinTypeKey::new("vec2", 1); const PIXEL: BuiltinTypeKey = BuiltinTypeKey::new("pixel", 1); @@ -345,6 +380,14 @@ pub fn lookup_builtin_constant( }) } +pub fn materialize_builtin_constant( + target: &str, + name: &str, + version: u16, +) -> Result>, BuiltinValueError> { + lookup_builtin_constant(target, name, version).map(BuiltinConstMeta::materialize).transpose() +} + pub fn lookup_intrinsic(owner: &str, name: &str, version: u16) -> Option<&'static IntrinsicMeta> { INTRINSICS .iter() @@ -386,6 +429,23 @@ fn value_matches_abi_type(value: &Value, expected: AbiType) -> bool { } } +fn validate_values_against_layout( + values: &[Value], + layout: &[AbiType], +) -> Result<(), BuiltinValueError> { + if values.len() != layout.len() { + return Err(BuiltinValueError::ArityMismatch { expected: layout.len(), got: values.len() }); + } + + for (index, (value, expected)) in values.iter().zip(layout.iter()).enumerate() { + if !value_matches_abi_type(value, *expected) { + return Err(BuiltinValueError::TypeMismatch { index, expected: *expected }); + } + } + + Ok(()) +} + fn vec2_dot(args: &[Value]) -> Result, IntrinsicExecutionError> { if args.len() != 4 { return Err(IntrinsicExecutionError::ArityMismatch { expected: 4, got: args.len() }); @@ -462,6 +522,18 @@ mod tests { assert!(lookup_intrinsic("vec2", "zero", 1).is_none()); } + #[test] + fn builtin_constant_materializes_vec2_zero() { + let zero = lookup_builtin_constant("vec2", "zero", 1).expect("vec2.zero must exist"); + let materialized = zero.materialize().expect("vec2.zero must materialize"); + assert_eq!(materialized, vec![Value::Float(0.0), Value::Float(0.0)]); + + let looked_up = materialize_builtin_constant("vec2", "zero", 1) + .expect("materialization lookup must succeed") + .expect("constant must exist"); + assert_eq!(looked_up, materialized); + } + #[test] fn intrinsic_metadata_exposes_arg_and_return_layouts() { let dot = lookup_intrinsic("vec2", "dot", 1).expect("vec2.dot must exist"); @@ -493,4 +565,13 @@ mod tests { .expect("length implementation must execute"); assert_eq!(result, vec![Value::Float(5.0)]); } + + #[test] + fn pixel_flat_values_validate_with_color_carrier_slot() { + let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); + let values = vec![Value::Int32(1), Value::Int32(2), Value::Int32(0xF800)]; + pixel.validate_flat_values(&values).expect("pixel flat values must validate"); + assert_eq!(pixel.flat_slot_width, 3); + assert_eq!(pixel.field("color").map(|field| field.start_slot), Some(2)); + } } diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index aff34942..22489a4d 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -17,9 +17,9 @@ mod vm_init_error; pub use builtins::{ AbiType, BuiltinConstKey, BuiltinConstMaterializer, BuiltinConstMeta, BuiltinConstSlotValue, BuiltinFieldMeta, BuiltinLayoutType, BuiltinScalarType, BuiltinTypeKey, BuiltinTypeMeta, - BuiltinTypeShape, IntrinsicExecutionError, IntrinsicImplementation, IntrinsicKey, - IntrinsicMeta, lookup_builtin_constant, lookup_builtin_type, lookup_intrinsic, - lookup_intrinsic_by_id, + BuiltinTypeShape, BuiltinValueError, IntrinsicExecutionError, IntrinsicImplementation, + IntrinsicKey, IntrinsicMeta, lookup_builtin_constant, lookup_builtin_type, lookup_intrinsic, + lookup_intrinsic_by_id, materialize_builtin_constant, }; pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};