Skip to main content

telltale_vm/
transfer_semantics.rs

1//! Shared transfer/delegation semantics used by cooperative and threaded VMs.
2
3use crate::coroutine::{Coroutine, Fault, Value};
4use crate::faults::{
5    transfer_fault_endpoint_not_owned, transfer_fault_expect_endpoint_register,
6    transfer_fault_expect_nat_target, transfer_fault_target_id_out_of_range,
7};
8use crate::instr::Endpoint;
9
10/// Decoded transfer request from one instruction instance.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct TransferRequest {
13    /// Endpoint to be transferred.
14    pub endpoint: Endpoint,
15    /// Destination coroutine id.
16    pub target_id: usize,
17}
18
19/// Decode transfer endpoint/target operands and enforce source ownership.
20///
21/// # Errors
22///
23/// Returns a `Fault` if registers are invalid, type mismatches occur, or
24/// the source coroutine does not own the endpoint.
25pub fn decode_transfer_request(
26    coro: &Coroutine,
27    role: &str,
28    endpoint_reg: u16,
29    target_reg: u16,
30) -> Result<TransferRequest, Fault> {
31    let endpoint_val = coro
32        .regs
33        .get(usize::from(endpoint_reg))
34        .ok_or(Fault::OutOfRegisters)?
35        .clone();
36    let endpoint = match endpoint_val {
37        Value::Endpoint(endpoint) => endpoint,
38        _ => return Err(transfer_fault_expect_endpoint_register(role)),
39    };
40
41    let target_val = coro
42        .regs
43        .get(usize::from(target_reg))
44        .ok_or(Fault::OutOfRegisters)?
45        .clone();
46    let target_id = match target_val {
47        Value::Nat(v) => {
48            usize::try_from(v).map_err(|_| transfer_fault_target_id_out_of_range(role))?
49        }
50        _ => return Err(transfer_fault_expect_nat_target(role)),
51    };
52
53    if !coro.owned_endpoints.contains(&endpoint) {
54        return Err(transfer_fault_endpoint_not_owned());
55    }
56
57    Ok(TransferRequest {
58        endpoint,
59        target_id,
60    })
61}
62
63/// Move endpoint ownership plus all endpoint-scoped progress/knowledge bundles.
64///
65/// If `target` is `None`, ownership is removed then restored on `source`.
66///
67/// # Errors
68///
69/// Returns a `Fault` if the source coroutine does not own the endpoint.
70pub fn move_endpoint_bundle(
71    endpoint: &Endpoint,
72    source: &mut Coroutine,
73    target: Option<&mut Coroutine>,
74) -> Result<(), Fault> {
75    if !source.owned_endpoints.contains(endpoint) {
76        return Err(transfer_fault_endpoint_not_owned());
77    }
78
79    let mut moved_tokens = Vec::new();
80    source.progress_tokens.retain(|token| {
81        if token.endpoint == *endpoint {
82            moved_tokens.push(token.clone());
83            false
84        } else {
85            true
86        }
87    });
88    let mut moved_knowledge = Vec::new();
89    source.knowledge_set.retain(|fact| {
90        if fact.endpoint == *endpoint {
91            moved_knowledge.push(fact.clone());
92            false
93        } else {
94            true
95        }
96    });
97    source.owned_endpoints.retain(|e| e != endpoint);
98
99    if let Some(target) = target {
100        target.owned_endpoints.push(endpoint.clone());
101        target.progress_tokens.extend(moved_tokens);
102        target.knowledge_set.extend(moved_knowledge);
103    } else {
104        source.owned_endpoints.push(endpoint.clone());
105        source.progress_tokens.extend(moved_tokens);
106        source.knowledge_set.extend(moved_knowledge);
107    }
108
109    Ok(())
110}
111
112/// Snapshot endpoint owners by coroutine id.
113#[must_use]
114pub fn endpoint_owner_ids(coroutines: &[Coroutine], endpoint: &Endpoint) -> Vec<usize> {
115    coroutines
116        .iter()
117        .filter_map(|coro| coro.owned_endpoints.contains(endpoint).then_some(coro.id))
118        .collect()
119}