diff --git a/crates/plotnik-lib/src/bytecode/ir.rs b/crates/plotnik-lib/src/bytecode/ir.rs index e42485dc..8a7b624c 100644 --- a/crates/plotnik-lib/src/bytecode/ir.rs +++ b/crates/plotnik-lib/src/bytecode/ir.rs @@ -2,14 +2,16 @@ //! //! Pre-layout instructions use `Label` for symbolic references. //! After layout, labels are resolved to `StepId` for serialization. +//! Member indices use deferred resolution via `MemberRef`. use std::collections::BTreeMap; use std::num::NonZeroU16; -use super::effects::EffectOp; +use super::effects::{EffectOp, EffectOpcode}; use super::ids::StepId; use super::instructions::{Call, Match, Return, select_match_opcode}; use super::nav::Nav; +use crate::query::type_check::TypeId; /// Symbolic reference, resolved to StepId at layout time. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -34,6 +36,77 @@ impl Label { } } +/// Symbolic reference to a struct field or enum variant. +/// Resolved to absolute member index during bytecode emission. +#[derive(Clone, Copy, Debug)] +pub enum MemberRef { + /// Already resolved to absolute index (for cases where it's known). + Absolute(u16), + /// Deferred resolution: (struct/enum type, relative field/variant index). + Deferred { type_id: TypeId, relative_index: u16 }, +} + +impl MemberRef { + /// Create an absolute reference. + pub fn absolute(index: u16) -> Self { + Self::Absolute(index) + } + + /// Create a deferred reference. + pub fn deferred(type_id: TypeId, relative_index: u16) -> Self { + Self::Deferred { type_id, relative_index } + } + + /// Resolve this reference using a member base lookup function. + pub fn resolve(self, get_member_base: F) -> u16 + where + F: Fn(TypeId) -> Option, + { + match self { + Self::Absolute(n) => n, + Self::Deferred { type_id, relative_index } => { + get_member_base(type_id).unwrap_or(0) + relative_index + } + } + } +} + +/// Effect operation with symbolic member references. +/// Used during compilation; resolved to EffectOp during emission. +#[derive(Clone, Debug)] +pub struct EffectIR { + pub opcode: EffectOpcode, + /// Payload for effects that don't use member indices. + pub payload: usize, + /// Member reference for Set/E effects (None for other effects). + pub member_ref: Option, +} + +impl EffectIR { + /// Create a simple effect without member reference. + pub fn simple(opcode: EffectOpcode, payload: usize) -> Self { + Self { opcode, payload, member_ref: None } + } + + /// Create an effect with a member reference. + pub fn with_member(opcode: EffectOpcode, member_ref: MemberRef) -> Self { + Self { opcode, payload: 0, member_ref: Some(member_ref) } + } + + /// Resolve this IR effect to a concrete EffectOp. + pub fn resolve(&self, get_member_base: F) -> EffectOp + where + F: Fn(TypeId) -> Option, + { + let payload = if let Some(member_ref) = self.member_ref { + member_ref.resolve(&get_member_base) as usize + } else { + self.payload + }; + EffectOp { opcode: self.opcode, payload } + } +} + /// Pre-layout instruction with symbolic references. #[derive(Clone, Debug)] pub enum Instruction { @@ -71,9 +144,12 @@ impl Instruction { } /// Resolve labels and serialize to bytecode bytes. - pub fn resolve(&self, map: &BTreeMap) -> Vec { + pub fn resolve(&self, map: &BTreeMap, get_member_base: F) -> Vec + where + F: Fn(TypeId) -> Option, + { match self { - Self::Match(m) => m.resolve(map), + Self::Match(m) => m.resolve(map, get_member_base), Self::Call(c) => c.resolve(map).to_vec(), Self::Return(r) => r.resolve().to_vec(), } @@ -92,11 +168,11 @@ pub struct MatchIR { /// Field constraint (None = wildcard). pub node_field: Option, /// Effects to execute before match attempt. - pub pre_effects: Vec, + pub pre_effects: Vec, /// Fields that must NOT be present on the node. pub neg_fields: Vec, /// Effects to execute after successful match. - pub post_effects: Vec, + pub post_effects: Vec, /// Successor labels (empty = accept, 1 = linear, 2+ = branch). pub successors: Vec