use prometeu_bytecode::Value; use prometeu_hal::HostContext; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BuiltinTypeKey { pub name: &'static str, pub version: u16, } impl BuiltinTypeKey { pub const fn new(name: &'static str, version: u16) -> Self { Self { name, version } } pub const fn key(self) -> (&'static str, u16) { (self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BuiltinConstKey { pub target: &'static str, pub name: &'static str, pub version: u16, } impl BuiltinConstKey { pub const fn new(target: &'static str, name: &'static str, version: u16) -> Self { Self { target, name, version } } pub const fn key(self) -> (&'static str, &'static str, u16) { (self.target, self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct IntrinsicKey { pub owner: &'static str, pub name: &'static str, pub version: u16, } impl IntrinsicKey { pub const fn new(owner: &'static str, name: &'static str, version: u16) -> Self { Self { owner, name, version } } pub const fn key(self) -> (&'static str, &'static str, u16) { (self.owner, self.name, self.version) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinScalarType { Int, Float, Bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AbiType { Scalar(BuiltinScalarType), Builtin(BuiltinTypeKey), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BuiltinLayoutType { Scalar(BuiltinScalarType), Builtin(BuiltinTypeKey), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BuiltinTypeShape { Scalar, Aggregate, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuiltinFieldMeta { pub name: &'static str, pub start_slot: u16, pub field_type: BuiltinLayoutType, pub flat_slot_width: u16, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct BuiltinTypeMeta { pub id: u32, pub name: &'static str, pub version: u16, pub shape: BuiltinTypeShape, pub fields: &'static [BuiltinFieldMeta], pub flat_slot_layout: &'static [AbiType], pub flat_slot_width: u16, } impl BuiltinTypeMeta { pub const fn key(&self) -> BuiltinTypeKey { BuiltinTypeKey::new(self.name, self.version) } pub fn field(&self, name: &str) -> Option<&'static BuiltinFieldMeta> { self.fields.iter().find(|field| field.name == name) } pub fn flatten_layout(&self) -> Vec { let mut out = Vec::with_capacity(self.flat_slot_width as usize); self.flatten_into(&mut out); 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), BuiltinTypeShape::Aggregate => { for field in self.fields { flatten_layout_type(field.field_type, out); } } } } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum BuiltinConstSlotValue { Int32(i32), Int64(i64), Float(f64), Bool(bool), NominalBuiltin { builtin: BuiltinTypeKey, carrier: i32 }, } pub type BuiltinConstHook = fn() -> Vec; #[derive(Debug, Clone, Copy)] pub enum BuiltinConstMaterializer { Direct(&'static [BuiltinConstSlotValue]), Hook(BuiltinConstHook), } #[derive(Debug, Clone, Copy)] pub struct BuiltinConstMeta { pub key: BuiltinConstKey, pub flat_slot_layout: &'static [AbiType], pub flat_slot_width: u16, pub materializer: BuiltinConstMaterializer, } impl BuiltinConstMeta { pub fn direct_slots(&self) -> Option<&'static [BuiltinConstSlotValue]> { match self.materializer { BuiltinConstMaterializer::Direct(slots) => Some(slots), 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)] pub enum IntrinsicExecutionError { ArityMismatch { expected: usize, got: usize }, TypeMismatch { index: usize, expected: AbiType }, InvalidBuiltinCarrier { owner: &'static str, name: &'static str, carrier: i64 }, HardwareUnavailable, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum BuiltinValueError { ArityMismatch { expected: usize, got: usize }, TypeMismatch { index: usize, expected: AbiType }, } pub type IntrinsicImplementation = fn(&[Value], &mut HostContext<'_>) -> Result, IntrinsicExecutionError>; #[derive(Debug, Clone, Copy)] pub struct IntrinsicMeta { pub id: u32, pub owner: &'static str, pub name: &'static str, pub version: u16, pub arg_layout: &'static [AbiType], pub ret_layout: &'static [AbiType], pub deterministic: bool, pub may_allocate: bool, pub implementation: IntrinsicImplementation, } impl IntrinsicMeta { pub const fn key(&self) -> IntrinsicKey { IntrinsicKey::new(self.owner, self.name, self.version) } pub fn arg_slots(&self) -> usize { self.arg_layout.len() } pub fn ret_slots(&self) -> usize { self.ret_layout.len() } pub fn validate_result_values(&self, values: &[Value]) -> Result<(), IntrinsicExecutionError> { if values.len() != self.ret_slots() { return Err(IntrinsicExecutionError::ArityMismatch { expected: self.ret_slots(), got: values.len(), }); } for (index, (value, expected)) in values.iter().zip(self.ret_layout.iter()).enumerate() { if !value_matches_abi_type(value, *expected) { return Err(IntrinsicExecutionError::TypeMismatch { index, expected: *expected }); } } Ok(()) } } 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); const INPUT_PAD: BuiltinTypeKey = BuiltinTypeKey::new("input.pad", 1); const INPUT_TOUCH: BuiltinTypeKey = BuiltinTypeKey::new("input.touch", 1); const INPUT_BUTTON: BuiltinTypeKey = BuiltinTypeKey::new("input.button", 1); const COLOR_LAYOUT: [AbiType; 1] = [AbiType::Builtin(COLOR)]; const VEC2_LAYOUT: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const PIXEL_LAYOUT: [AbiType; 3] = [ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Builtin(COLOR), ]; const INPUT_PAD_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const INPUT_TOUCH_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const INPUT_BUTTON_LAYOUT: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const VEC2_FIELDS: [BuiltinFieldMeta; 2] = [ BuiltinFieldMeta { name: "x", start_slot: 0, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Float), flat_slot_width: 1, }, BuiltinFieldMeta { name: "y", start_slot: 1, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Float), flat_slot_width: 1, }, ]; const PIXEL_FIELDS: [BuiltinFieldMeta; 3] = [ BuiltinFieldMeta { name: "x", start_slot: 0, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Int), flat_slot_width: 1, }, BuiltinFieldMeta { name: "y", start_slot: 1, field_type: BuiltinLayoutType::Scalar(BuiltinScalarType::Int), flat_slot_width: 1, }, BuiltinFieldMeta { name: "color", start_slot: 2, field_type: BuiltinLayoutType::Builtin(COLOR), flat_slot_width: 1, }, ]; const BUILTIN_TYPES: [BuiltinTypeMeta; 6] = [ BuiltinTypeMeta { id: 0x0001, name: COLOR.name, version: COLOR.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &COLOR_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0002, name: VEC2.name, version: VEC2.version, shape: BuiltinTypeShape::Aggregate, fields: &VEC2_FIELDS, flat_slot_layout: &VEC2_LAYOUT, flat_slot_width: 2, }, BuiltinTypeMeta { id: 0x0003, name: PIXEL.name, version: PIXEL.version, shape: BuiltinTypeShape::Aggregate, fields: &PIXEL_FIELDS, flat_slot_layout: &PIXEL_LAYOUT, flat_slot_width: 3, }, BuiltinTypeMeta { id: 0x0100, name: INPUT_PAD.name, version: INPUT_PAD.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_PAD_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0101, name: INPUT_TOUCH.name, version: INPUT_TOUCH.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_TOUCH_LAYOUT, flat_slot_width: 1, }, BuiltinTypeMeta { id: 0x0102, name: INPUT_BUTTON.name, version: INPUT_BUTTON.version, shape: BuiltinTypeShape::Scalar, fields: &[], flat_slot_layout: &INPUT_BUTTON_LAYOUT, flat_slot_width: 1, }, ]; const VEC2_ZERO_LAYOUT: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const VEC2_ZERO_SLOTS: [BuiltinConstSlotValue; 2] = [BuiltinConstSlotValue::Float(0.0), BuiltinConstSlotValue::Float(0.0)]; const BUILTIN_CONSTS: [BuiltinConstMeta; 1] = [BuiltinConstMeta { key: BuiltinConstKey::new("vec2", "zero", 1), flat_slot_layout: &VEC2_ZERO_LAYOUT, flat_slot_width: 2, materializer: BuiltinConstMaterializer::Direct(&VEC2_ZERO_SLOTS), }]; const VEC2_DOT_ARGS: [AbiType; 4] = [ AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float), ]; const SINGLE_FLOAT_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Float)]; const VEC2_LENGTH_ARGS: [AbiType; 2] = [AbiType::Scalar(BuiltinScalarType::Float), AbiType::Scalar(BuiltinScalarType::Float)]; const NO_ARGS: [AbiType; 0] = []; const PAD_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const TOUCH_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const BUTTON_RET: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const PAD_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_PAD)]; const TOUCH_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_TOUCH)]; const BUTTON_ARGS: [AbiType; 1] = [AbiType::Builtin(INPUT_BUTTON)]; const BOOL_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Bool)]; const INT_RET: [AbiType; 1] = [AbiType::Scalar(BuiltinScalarType::Int)]; const INTRINSICS: [IntrinsicMeta; 23] = [ IntrinsicMeta { id: 0x1000, owner: "vec2", name: "dot", version: 1, arg_layout: &VEC2_DOT_ARGS, ret_layout: &SINGLE_FLOAT_RET, deterministic: true, may_allocate: false, implementation: vec2_dot, }, IntrinsicMeta { id: 0x1001, owner: "vec2", name: "length", version: 1, arg_layout: &VEC2_LENGTH_ARGS, ret_layout: &SINGLE_FLOAT_RET, deterministic: true, may_allocate: false, implementation: vec2_length, }, IntrinsicMeta { id: 0x2000, owner: "input", name: "pad", version: 1, arg_layout: &NO_ARGS, ret_layout: &PAD_RET, deterministic: true, may_allocate: false, implementation: input_pad, }, IntrinsicMeta { id: 0x2001, owner: "input", name: "touch", version: 1, arg_layout: &NO_ARGS, ret_layout: &TOUCH_RET, deterministic: true, may_allocate: false, implementation: input_touch, }, IntrinsicMeta { id: 0x2010, owner: "input.pad", name: "up", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_up, }, IntrinsicMeta { id: 0x2011, owner: "input.pad", name: "down", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_down, }, IntrinsicMeta { id: 0x2012, owner: "input.pad", name: "left", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_left, }, IntrinsicMeta { id: 0x2013, owner: "input.pad", name: "right", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_right, }, IntrinsicMeta { id: 0x2014, owner: "input.pad", name: "a", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_a, }, IntrinsicMeta { id: 0x2015, owner: "input.pad", name: "b", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_b, }, IntrinsicMeta { id: 0x2016, owner: "input.pad", name: "x", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_x, }, IntrinsicMeta { id: 0x2017, owner: "input.pad", name: "y", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_y, }, IntrinsicMeta { id: 0x2018, owner: "input.pad", name: "l", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_l, }, IntrinsicMeta { id: 0x2019, owner: "input.pad", name: "r", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_r, }, IntrinsicMeta { id: 0x201A, owner: "input.pad", name: "start", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_start, }, IntrinsicMeta { id: 0x201B, owner: "input.pad", name: "select", version: 1, arg_layout: &PAD_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_pad_select, }, IntrinsicMeta { id: 0x2020, owner: "input.touch", name: "button", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &BUTTON_RET, deterministic: true, may_allocate: false, implementation: input_touch_button, }, IntrinsicMeta { id: 0x2021, owner: "input.touch", name: "x", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_touch_x, }, IntrinsicMeta { id: 0x2022, owner: "input.touch", name: "y", version: 1, arg_layout: &TOUCH_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_touch_y, }, IntrinsicMeta { id: 0x2030, owner: "input.button", name: "pressed", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_pressed, }, IntrinsicMeta { id: 0x2031, owner: "input.button", name: "released", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_released, }, IntrinsicMeta { id: 0x2032, owner: "input.button", name: "down", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &BOOL_RET, deterministic: true, may_allocate: false, implementation: input_button_down, }, IntrinsicMeta { id: 0x2033, owner: "input.button", name: "hold", version: 1, arg_layout: &BUTTON_ARGS, ret_layout: &INT_RET, deterministic: true, may_allocate: false, implementation: input_button_hold, }, ]; pub fn lookup_builtin_type(name: &str, version: u16) -> Option<&'static BuiltinTypeMeta> { BUILTIN_TYPES.iter().find(|meta| meta.name == name && meta.version == version) } pub fn lookup_builtin_constant( target: &str, name: &str, version: u16, ) -> Option<&'static BuiltinConstMeta> { BUILTIN_CONSTS.iter().find(|meta| { meta.key.target == target && meta.key.name == name && meta.key.version == version }) } 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() .find(|meta| meta.owner == owner && meta.name == name && meta.version == version) } pub fn lookup_intrinsic_by_id(id: u32) -> Option<&'static IntrinsicMeta> { INTRINSICS.iter().find(|meta| meta.id == id) } fn flatten_layout_type(layout_type: BuiltinLayoutType, out: &mut Vec) { match layout_type { BuiltinLayoutType::Scalar(scalar) => out.push(AbiType::Scalar(scalar)), BuiltinLayoutType::Builtin(key) => { let meta = lookup_builtin_type(key.name, key.version).unwrap_or_else(|| { panic!("missing builtin metadata for {}@{}", key.name, key.version) }); meta.flatten_into(out); } } } fn expect_float_arg(args: &[Value], index: usize) -> Result { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; value.as_float().ok_or(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Float), }) } fn value_matches_abi_type(value: &Value, expected: AbiType) -> bool { match expected { AbiType::Scalar(BuiltinScalarType::Int) => value.as_integer().is_some(), AbiType::Scalar(BuiltinScalarType::Float) => value.as_float().is_some(), AbiType::Scalar(BuiltinScalarType::Bool) => matches!(value, Value::Boolean(_)), AbiType::Builtin(_) => value.as_integer().is_some(), } } 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(()) } const PAD_HANDLE: i64 = 1; const TOUCH_HANDLE: i64 = 1; const PAD_BUTTON_BASE: i64 = 0x100; const TOUCH_BUTTON_CARRIER: i64 = 0x200; fn vec2_dot( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { if args.len() != 4 { return Err(IntrinsicExecutionError::ArityMismatch { expected: 4, got: args.len() }); } let ax = expect_float_arg(args, 0)?; let ay = expect_float_arg(args, 1)?; let bx = expect_float_arg(args, 2)?; let by = expect_float_arg(args, 3)?; Ok(vec![Value::Float((ax * bx) + (ay * by))]) } fn vec2_length( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { if args.len() != 2 { return Err(IntrinsicExecutionError::ArityMismatch { expected: 2, got: args.len() }); } let x = expect_float_arg(args, 0)?; let y = expect_float_arg(args, 1)?; Ok(vec![Value::Float((x * x + y * y).sqrt())]) } fn input_pad( _args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(PAD_HANDLE)]) } fn input_touch( _args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { Ok(vec![Value::Int64(TOUCH_HANDLE)]) } fn input_pad_up( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "up")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE)]) } fn input_pad_down( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "down")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 1)]) } fn input_pad_left( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "left")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 2)]) } fn input_pad_right( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "right")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 3)]) } fn input_pad_a( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "a")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 4)]) } fn input_pad_b( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "b")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 5)]) } fn input_pad_x( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "x")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 6)]) } fn input_pad_y( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "y")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 7)]) } fn input_pad_l( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "l")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 8)]) } fn input_pad_r( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "r")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 9)]) } fn input_pad_start( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "start")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 10)]) } fn input_pad_select( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_pad_handle(args, "select")?; Ok(vec![Value::Int64(PAD_BUTTON_BASE + 11)]) } fn input_touch_button( args: &[Value], _ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "button")?; Ok(vec![Value::Int64(TOUCH_BUTTON_CARRIER)]) } fn input_touch_x( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "x")?; let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; Ok(vec![Value::Int64(hw.touch().x() as i64)]) } fn input_touch_y( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { expect_touch_handle(args, "y")?; let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; Ok(vec![Value::Int64(hw.touch().y() as i64)]) } fn input_button_pressed( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "pressed", ctx)?; Ok(vec![Value::Boolean(button.pressed)]) } fn input_button_released( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "released", ctx)?; Ok(vec![Value::Boolean(button.released)]) } fn input_button_down( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "down", ctx)?; Ok(vec![Value::Boolean(button.down)]) } fn input_button_hold( args: &[Value], ctx: &mut HostContext<'_>, ) -> Result, IntrinsicExecutionError> { let button = resolve_button(args, "hold", ctx)?; Ok(vec![Value::Int64(button.hold_frames as i64)]) } fn expect_pad_handle(args: &[Value], name: &'static str) -> Result<(), IntrinsicExecutionError> { let carrier = expect_builtin_carrier(args, 0)?; if carrier != PAD_HANDLE { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.pad", name, carrier, }); } Ok(()) } fn expect_touch_handle(args: &[Value], name: &'static str) -> Result<(), IntrinsicExecutionError> { let carrier = expect_builtin_carrier(args, 0)?; if carrier != TOUCH_HANDLE { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.touch", name, carrier, }); } Ok(()) } fn expect_builtin_carrier(args: &[Value], index: usize) -> Result { let value = args .get(index) .ok_or(IntrinsicExecutionError::ArityMismatch { expected: index + 1, got: args.len() })?; value.as_integer().ok_or(IntrinsicExecutionError::TypeMismatch { index, expected: AbiType::Scalar(BuiltinScalarType::Int), }) } fn resolve_button( args: &[Value], name: &'static str, ctx: &mut HostContext<'_>, ) -> Result { let carrier = expect_builtin_carrier(args, 0)?; let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?; if carrier == TOUCH_BUTTON_CARRIER { return Ok(*hw.touch().f()); } let Some(index) = carrier.checked_sub(PAD_BUTTON_BASE) else { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.button", name, carrier, }); }; let button = match index { 0 => *hw.pad().up(), 1 => *hw.pad().down(), 2 => *hw.pad().left(), 3 => *hw.pad().right(), 4 => *hw.pad().a(), 5 => *hw.pad().b(), 6 => *hw.pad().x(), 7 => *hw.pad().y(), 8 => *hw.pad().l(), 9 => *hw.pad().r(), 10 => *hw.pad().start(), 11 => *hw.pad().select(), _ => { return Err(IntrinsicExecutionError::InvalidBuiltinCarrier { owner: "input.button", name, carrier, }); } }; Ok(button) } #[cfg(test)] mod tests { use super::*; #[test] fn builtin_lookup_uses_canonical_identity() { let color = lookup_builtin_type("color", 1).expect("color builtin must exist"); assert_eq!(color.key(), COLOR); assert!(lookup_builtin_type("Color", 1).is_none()); assert!(lookup_builtin_type("color", 2).is_none()); } #[test] fn builtin_layout_flattening_matches_expected_widths() { let color = lookup_builtin_type("color", 1).expect("color builtin must exist"); assert_eq!(color.flatten_layout(), vec![AbiType::Builtin(COLOR)]); assert_eq!(color.flat_slot_width, 1); let vec2 = lookup_builtin_type("vec2", 1).expect("vec2 builtin must exist"); assert_eq!(vec2.flatten_layout(), vec![AbiType::Scalar(BuiltinScalarType::Float); 2]); assert_eq!(vec2.flat_slot_width, 2); let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); assert_eq!( pixel.flatten_layout(), vec![ AbiType::Scalar(BuiltinScalarType::Int), AbiType::Scalar(BuiltinScalarType::Int), AbiType::Builtin(COLOR), ] ); assert_eq!(pixel.flat_slot_width, 3); } #[test] fn builtin_field_offsets_are_stable() { let vec2 = lookup_builtin_type("vec2", 1).expect("vec2 builtin must exist"); assert_eq!(vec2.field("x").map(|field| field.start_slot), Some(0)); assert_eq!(vec2.field("y").map(|field| field.start_slot), Some(1)); let pixel = lookup_builtin_type("pixel", 1).expect("pixel builtin must exist"); let color_field = pixel.field("color").expect("pixel.color must exist"); assert_eq!(color_field.start_slot, 2); assert_eq!(color_field.flat_slot_width, 1); } #[test] fn builtin_constant_registry_is_separate_from_intrinsics() { let zero = lookup_builtin_constant("vec2", "zero", 1).expect("vec2.zero must exist"); assert_eq!(zero.key, BuiltinConstKey::new("vec2", "zero", 1)); assert_eq!(zero.flat_slot_width, 2); assert_eq!(zero.flat_slot_layout, &VEC2_ZERO_LAYOUT); assert_eq!(zero.direct_slots(), Some(&VEC2_ZERO_SLOTS[..])); 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"); assert_eq!(dot.arg_layout.len(), 4); assert_eq!(dot.ret_layout.len(), 1); assert!(dot.deterministic); assert!(!dot.may_allocate); let length = lookup_intrinsic("vec2", "length", 1).expect("vec2.length must exist"); assert_eq!(length.arg_layout.len(), 2); assert_eq!(length.ret_layout.len(), 1); assert_eq!(lookup_intrinsic_by_id(length.id).map(|meta| meta.key()), Some(length.key())); let hold = lookup_intrinsic("input.button", "hold", 1).expect("input.button.hold must exist"); assert_eq!(hold.arg_layout.len(), 1); assert_eq!(hold.ret_layout.len(), 1); assert!(hold.deterministic); assert!(!hold.may_allocate); } #[test] fn intrinsic_implementations_are_registered_without_syscalls() { let dot = lookup_intrinsic("vec2", "dot", 1).expect("vec2.dot must exist"); let mut ctx = HostContext::new(None); let result = (dot.implementation)( &[Value::Float(1.0), Value::Float(2.0), Value::Float(3.0), Value::Float(4.0)], &mut ctx, ) .expect("dot implementation must execute"); assert_eq!(result, vec![Value::Float(11.0)]); let length = lookup_intrinsic("vec2", "length", 1).expect("vec2.length must exist"); let result = (length.implementation)(&[Value::Float(3.0), Value::Float(4.0)], &mut ctx) .expect("length implementation must execute"); assert_eq!(result, vec![Value::Float(5.0)]); } #[test] fn input_intrinsic_requires_hardware_context() { let mut ctx = HostContext::new(None); let touch_x = lookup_intrinsic("input.touch", "x", 1).expect("input.touch.x must exist"); let err = (touch_x.implementation)(&[Value::Int64(TOUCH_HANDLE)], &mut ctx) .expect_err("touch.x must fail without hardware context"); assert_eq!(err, IntrinsicExecutionError::HardwareUnavailable); } #[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)); } }