Skip to main content

qcvm/progs/
functions.rs

1use std::ffi::CStr;
2use std::{fmt, ops::Range, sync::Arc};
3
4use crate::{FunctionRef, HashMap, Type, VectorField, function_args};
5use arc_slice::ArcSlice;
6use arrayvec::ArrayVec;
7#[cfg(feature = "reflect")]
8use bevy_reflect::Reflect;
9use bump_scope::{BumpAllocatorScopeExt, FixedBumpVec};
10use itertools::Itertools;
11use num::FromPrimitive as _;
12use num_derive::FromPrimitive;
13
14use crate::QCMemory;
15use crate::load::LoadFn;
16use crate::ops::Opcode;
17use crate::progs::{VmScalar, VmScalarType};
18
19/// The maximum number of arguments supported by the runtime.
20pub const MAX_ARGS: usize = 8;
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
23#[cfg_attr(feature = "reflect", derive(Reflect))]
24pub struct Statement {
25    pub opcode: Opcode,
26    pub arg1: i16,
27    pub arg2: i16,
28    pub arg3: i16,
29}
30
31impl Statement {
32    pub fn new(op: i16, arg1: i16, arg2: i16, arg3: i16) -> anyhow::Result<Statement> {
33        let opcode = Opcode::from_i16(op)
34            .ok_or_else(|| anyhow::Error::msg(format!("Bad opcode 0x{op:x}")))?;
35
36        Ok(Statement {
37            opcode,
38            arg1,
39            arg2,
40            arg3,
41        })
42    }
43}
44
45#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq, Eq)]
46#[cfg_attr(feature = "reflect", derive(Reflect))]
47#[repr(u8)]
48pub(crate) enum ArgSize {
49    Scalar = 1,
50    Vector = 3,
51}
52
53impl From<ArgSize> for Type {
54    fn from(value: ArgSize) -> Self {
55        match value {
56            ArgSize::Scalar => Type::AnyScalar,
57            ArgSize::Vector => Type::Vector,
58        }
59    }
60}
61
62impl std::fmt::Display for ArgSize {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::Scalar => write!(f, "1 (scalar)"),
66            Self::Vector => write!(f, "3 (vector)"),
67        }
68    }
69}
70
71#[derive(Debug)]
72pub(crate) struct FunctionExecutionCtx<'a> {
73    params: FixedBumpVec<'a, VmScalar>,
74    local_storage: FixedBumpVec<'a, VmScalar>,
75    /// If the progs try to access a value within this range, it will access `local_storage` instead of globals.
76    ///
77    /// > TODO: We should only define globals at all if they are specified in the `progdefs.h` equivalent.
78    local_range: Range<usize>,
79    statements: ArcSlice<[Statement]>,
80}
81
82impl FunctionExecutionCtx<'_> {
83    pub fn instr(&self, index: usize) -> anyhow::Result<Statement> {
84        Ok(*self
85            .statements
86            .get(index)
87            .ok_or_else(|| anyhow::Error::msg("Out-of-bounds instruction access"))?)
88    }
89}
90
91const LOCAL_STORAGE_ERR: &str =
92    "Programmer error: `local_storage` was too small for `local_range`. This is a bug!";
93
94impl QCMemory for FunctionExecutionCtx<'_> {
95    type Scalar = Option<VmScalar>;
96
97    fn get(&self, index: usize) -> anyhow::Result<Option<VmScalar>> {
98        if self.local_range.contains(&index) {
99            let index = index - self.local_range.start;
100
101            Ok(Some(
102                self.local_storage
103                    .get(index)
104                    .expect(LOCAL_STORAGE_ERR)
105                    .clone(),
106            ))
107        } else {
108            let Ok(index) = u16::try_from(index) else {
109                return Ok(None);
110            };
111
112            match function_args().find_map(|arg| arg.try_match_scalar(index)) {
113                Some((arg_index, ofs)) => {
114                    Ok(self.params.get(arg_index * 3 + ofs as usize).cloned())
115                }
116                _ => Ok(None),
117            }
118        }
119    }
120}
121
122impl FunctionExecutionCtx<'_> {
123    pub fn set(&mut self, index: usize, value: VmScalar) -> anyhow::Result<()> {
124        if value.type_() == VmScalarType::Void {
125            return Ok(());
126        }
127
128        if self.local_range.contains(&index) {
129            let index = index - self.local_range.start;
130
131            let local = self.local_storage.get_mut(index).expect(LOCAL_STORAGE_ERR);
132
133            *local = value;
134        } else {
135            let out_of_range = || anyhow::bail!("Global {index} is out of range");
136
137            let Ok(index) = u16::try_from(index) else {
138                return out_of_range();
139            };
140
141            match function_args().find_map(|arg| arg.try_match_scalar(index)) {
142                Some((arg_index, ofs)) => {
143                    if let Some(param) = self.params.get_mut(arg_index * 3 + ofs as usize) {
144                        *param = value;
145                    } else {
146                        return out_of_range();
147                    }
148                }
149                _ => {
150                    return out_of_range();
151                }
152            }
153        }
154
155        Ok(())
156    }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct QCFunctionBody {
161    /// `arg_start` + `locals` fields - we do not conflate locals and globals,
162    /// so every access needs to check the locals first.
163    pub locals: Range<usize>,
164    pub statements: ArcSlice<[Statement]>,
165}
166
167#[derive(Debug, Clone)]
168pub enum FunctionBody {
169    Progs(QCFunctionBody),
170    Builtin,
171}
172
173impl FunctionBody {
174    pub fn try_into_qc(self) -> Result<QCFunctionBody, Builtin> {
175        match self {
176            Self::Progs(quakec) => Ok(quakec),
177            Self::Builtin => Err(Builtin),
178        }
179    }
180}
181
182pub type QCFunctionDef = FunctionDef<QCFunctionBody>;
183
184/// Definition for a QuakeC function.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct FunctionDef<T = FunctionBody> {
187    /// The offset to the start of this function's statements for QuakeC
188    /// functions, the negative index of a builtin for functions provided
189    /// by the host.
190    pub offset: i32,
191    /// The name of this function.
192    pub name: Arc<CStr>,
193    /// The source file that this function is defined in.
194    pub source: Arc<CStr>,
195    /// First N args get copied to the local stack.
196    pub args: ArrayVec<Type, MAX_ARGS>,
197    /// If the function is defined in QuakeC, the function body.
198    pub body: T,
199}
200
201/// No body as the function is provided by the host.
202#[derive(Debug, Copy, Clone, PartialEq, Eq)]
203pub struct Builtin;
204
205/// A function definition for a function provided by the host.
206pub type BuiltinDef = FunctionDef<Builtin>;
207
208impl FunctionDef {
209    pub(crate) fn try_into_qc(self) -> Result<QCFunctionDef, FunctionDef<Builtin>> {
210        match self.body.try_into_qc() {
211            Ok(quakec) => Ok(QCFunctionDef {
212                offset: self.offset,
213                name: self.name,
214                source: self.source,
215                args: self.args,
216                body: quakec,
217            }),
218            Err(builtin) => Err(FunctionDef {
219                offset: self.offset,
220                name: self.name,
221                source: self.source,
222                args: self.args,
223                body: builtin,
224            }),
225        }
226    }
227}
228
229impl QCFunctionDef {
230    pub(crate) fn ctx<'scope>(
231        &self,
232        mut alloc: impl BumpAllocatorScopeExt<'scope>,
233    ) -> FunctionExecutionCtx<'scope> {
234        FunctionExecutionCtx {
235            params: FixedBumpVec::from_iter_exact_in(
236                std::iter::repeat_n(
237                    VmScalar::Void,
238                    function_args().len() * VectorField::FIELDS.len(),
239                ),
240                &mut alloc,
241            ),
242            local_storage: FixedBumpVec::from_iter_exact_in(
243                std::iter::repeat_n(VmScalar::Void, self.body.locals.len()),
244                &mut alloc,
245            ),
246            local_range: self.body.locals.clone(),
247            statements: self.body.statements.clone(),
248        }
249    }
250}
251
252/// The registry of functions known to the `progs.dat`.
253#[derive(Debug, Clone)]
254pub struct FunctionRegistry {
255    by_index: HashMap<i32, FunctionDef>,
256    by_name: HashMap<Arc<CStr>, FunctionDef>,
257}
258
259impl FunctionRegistry {
260    pub(crate) fn new(
261        statements: ArcSlice<[Statement]>,
262        definitions: &[LoadFn],
263    ) -> anyhow::Result<Self> {
264        let (by_index, by_name) = definitions
265            .iter()
266            .map(Some)
267            .chain(std::iter::once(None))
268            .tuple_windows()
269            .map(|(cur, next)| -> anyhow::Result<_> {
270                match (cur, next) {
271                    (Some(cur), next) => {
272                        let func_def = FunctionDef {
273                            name: cur.name.clone(),
274                            source: cur.source.clone(),
275                            args: cur.args.iter().copied().map(Into::into).collect(),
276                            offset: cur.offset,
277                            body: if cur.offset < 0 {
278                                debug_assert_eq!(cur.locals.len(), 0);
279
280                                FunctionBody::Builtin
281                            } else {
282                                let cur_offset = usize::try_from(cur.offset)?;
283                                let next_offset = next
284                                    .as_ref()
285                                    .map(|n| usize::try_from(n.offset))
286                                    .unwrap_or(Ok(statements.len()))?;
287
288                                FunctionBody::Progs(QCFunctionBody {
289                                    locals: cur.locals.clone(),
290                                    statements: statements.subslice(cur_offset..next_offset),
291                                })
292                            },
293                        };
294
295                        Ok(((cur.offset, func_def.clone()), (cur.name.clone(), func_def)))
296                    }
297                    (None, _) => unreachable!(),
298                }
299            })
300            .collect::<anyhow::Result<(_, _)>>()?;
301
302        Ok(Self { by_index, by_name })
303    }
304
305    /// Get a function by either an index or a name.
306    pub fn get<F>(&self, func: F) -> anyhow::Result<&FunctionDef>
307    where
308        F: Into<FunctionRef>,
309    {
310        match func.into() {
311            FunctionRef::Offset(i) => self.get_by_index(i),
312            FunctionRef::Name(name) => self.get_by_name(name),
313        }
314    }
315
316    /// Get a function by its index.
317    pub fn get_by_index<F>(&self, func: F) -> anyhow::Result<&FunctionDef>
318    where
319        F: TryInto<i32>,
320        F::Error: snafu::Error + Into<anyhow::Error> + Send + Sync + 'static,
321    {
322        let func = func.try_into()?;
323
324        self.by_index
325            .get(&func)
326            .ok_or_else(|| anyhow::format_err!("Function {func} does not exist"))
327    }
328
329    /// Get a function by its name.
330    pub fn get_by_name<F>(&self, func: F) -> anyhow::Result<&FunctionDef>
331    where
332        F: AsRef<CStr>,
333    {
334        let func = func.as_ref();
335
336        self.by_name
337            .get(func)
338            .ok_or_else(|| anyhow::format_err!("Function {func:?} does not exist"))
339    }
340}