glulx_asm/
items.rs

1//! [`Item`] and related types.
2
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-Exception
4// Copyright 2024 Daniel Fox Franke.
5
6use bytes::{BufMut, Bytes};
7use core::fmt::Display;
8use core::num::NonZeroU32;
9
10use crate::{
11    cast::Overflow,
12    decoding_table::DecodeNode,
13    error::AssemblerError,
14    instr_def::Instr,
15    resolver::{ResolvedAddr, Resolver},
16    strings::{MysteryString, Utf32String},
17};
18
19/// A reference to a label plus an offset.
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21pub struct LabelRef<L>(pub L, pub i32);
22
23/// An item of top-level content in a story file assembly.
24#[derive(Debug, Clone)]
25pub enum Item<L> {
26    /// A label whose address can be dereferenced.
27    Label(L),
28    /// Generates padding such that the next item is aligned to a multiple of
29    /// the given `NonZeroU32`, which will likely be a power of two but
30    /// arbitrary values are accepted. Glulx itself never requires any item in
31    /// main memory to be aligned, but you can use this if you are generating
32    /// code which assumes some alignment.
33    Align(NonZeroU32),
34    /// A string decoding table.
35    DecodingTable(DecodeNode<L>),
36    /// A header for a function, specifying its calling convention and how many
37    /// locals it allocates. Since one- and two-byte locals have been deprecated
38    /// since 2010, this assembler does not support them and all locals are taken
39    /// to be four bytes.
40    FnHeader(CallingConvention, u32),
41    /// An instruction.
42    Instr(Instr<L>),
43    /// An `E0` string (usually Latin-1).
44    MysteryString(MysteryString),
45    /// An `E1` string of Huffman-coded data, decompressed via a decoding table.
46    /// No validity checks are performed.
47    CompressedString(Bytes),
48    /// An `E2` (Unicode) string.
49    Utf32String(Utf32String),
50    /// Some arbitrary bytes to be serialized verbatim.
51    Blob(Bytes),
52    /// Four bytes representing the absolute adddress of the given label+offset and right-shift.
53    LabelRef(LabelRef<L>, u8),
54}
55
56/// Placeholder for space in RAM that shoud be allocated at startup with
57/// initially-zeroed content.
58#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
59pub enum ZeroItem<L> {
60    /// A label whose address can be dereferenced.
61    Label(L),
62    /// Reserves the given amount of space, in bytes.
63    Space(u32),
64    /// Generates padding such that the next item is aligned to a multiple of
65    /// the given `NonZeroU32`, which will likely be a power of two but
66    /// arbitrary values are accepted. Glulx itself never requires any item in
67    /// main memory to be aligned, but you can use this if you are generating
68    /// code which assumes some alignment.
69    Align(NonZeroU32),
70}
71
72/// Specifies how a function receives its arguments.
73#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74pub enum CallingConvention {
75    /// Arguments are placed on the stack.
76    ArgsOnStack,
77    /// Arguments are placed in local variables.
78    ArgsInLocals,
79}
80
81impl<L> Item<L> {
82    /// Applies the given mapping function to all labels within the item.
83    pub fn map<F, M>(self, mut f: F) -> Item<M>
84    where
85        F: FnMut(L) -> M,
86    {
87        match self {
88            Item::Label(l) => Item::Label(f(l)),
89            Item::Align(a) => Item::Align(a),
90            Item::DecodingTable(t) => Item::DecodingTable(t.map(&mut f)),
91            Item::FnHeader(t, n) => Item::FnHeader(t, n),
92            Item::Instr(i) => Item::Instr(i.map(f)),
93            Item::MysteryString(s) => Item::MysteryString(s),
94            Item::Utf32String(s) => Item::Utf32String(s),
95            Item::CompressedString(s) => Item::CompressedString(s),
96            Item::Blob(b) => Item::Blob(b),
97            Item::LabelRef(l, shift) => Item::LabelRef(l.map(f), shift),
98        }
99    }
100}
101
102impl<L> Item<L>
103where
104    L: Clone,
105{
106    pub(crate) fn worst_len(&self) -> usize {
107        match self {
108            Item::Label(_) => 0,
109            Item::Align(_) => 0,
110            Item::DecodingTable(t) => 12 + t.len(),
111            Item::FnHeader(_, n) => {
112                let n_records: usize = n
113                    .div_ceil(255)
114                    .try_into()
115                    .expect("u32 should fit in a usize");
116                2 * n_records + 3
117            }
118            Item::Instr(i) => i.worst_len(),
119            Item::MysteryString(s) => s.len() + 2,
120            Item::Utf32String(s) => s.byte_len() + 8,
121            Item::CompressedString(s) => 1 + s.len(),
122            Item::Blob(b) => b.len(),
123            Item::LabelRef(_, _) => 4,
124        }
125    }
126
127    pub(crate) fn align(&self) -> u32 {
128        match self {
129            Item::Align(a) => (*a).into(),
130            _ => 1,
131        }
132    }
133
134    pub(crate) fn resolved_len<R>(
135        &self,
136        position: u32,
137        resolver: &R,
138    ) -> Result<usize, AssemblerError<L>>
139    where
140        R: Resolver<Label = L>,
141    {
142        Ok(match self {
143            Item::Instr(i) => i.resolve(position, resolver)?.len(),
144            _ => self.worst_len(),
145        })
146    }
147
148    pub(crate) fn serialize<R, B>(
149        &self,
150        position: u32,
151        resolver: &R,
152        mut buf: B,
153    ) -> Result<(), AssemblerError<L>>
154    where
155        R: Resolver<Label = L>,
156        B: BufMut,
157    {
158        match self {
159            Item::Label(_) => {}
160            Item::Align(x) => {
161                let align: u32 = (*x).into();
162                let modulus = position % align;
163                let padding = if modulus == 0 { 0 } else { align - modulus };
164                buf.put_bytes(
165                    0,
166                    padding
167                        .try_into()
168                        .expect("u32 to usize conversion should succeed"),
169                );
170            }
171            Item::DecodingTable(table) => {
172                let resolved = table.resolve(resolver)?;
173                let count = u32::try_from(resolved.count_nodes()).overflow()?;
174                let length =
175                    u32::try_from(resolved.len().checked_add(12).overflow()?).overflow()?;
176                let root = position.checked_add(12).overflow()?;
177                buf.put_u32(length);
178                buf.put_u32(count);
179                buf.put_u32(root);
180                resolved.serialize(0, &mut buf);
181            }
182            Item::FnHeader(cc, args) => {
183                match cc {
184                    CallingConvention::ArgsOnStack => buf.put_u8(0xc0),
185                    CallingConvention::ArgsInLocals => buf.put_u8(0xc1),
186                }
187
188                for _ in 0..(*args / 255) {
189                    buf.put_u8(4);
190                    buf.put_u8(255);
191                }
192
193                if *args % 255 != 0 {
194                    buf.put_u8(4);
195                    buf.put_u8(
196                        u8::try_from(*args % 255).expect("a number modulo 255 should fit in a u8"),
197                    );
198                }
199
200                buf.put_bytes(0, 2);
201            }
202            Item::Instr(instr) => {
203                let resolved = instr.resolve(position, resolver)?;
204                resolved.serialize(buf);
205            }
206            Item::MysteryString(s) => {
207                buf.put_u8(0xe0);
208                buf.put(s.to_bytes());
209                buf.put_u8(0);
210            }
211            Item::Utf32String(s) => {
212                buf.put_u32(0xe2000000);
213                buf.put(s.to_bytes());
214                buf.put_u32(0);
215            }
216            Item::CompressedString(bytes) => {
217                buf.put_u8(0xe1);
218                buf.put(bytes.clone());
219            }
220            Item::Blob(blob) => {
221                buf.put(blob.clone());
222            }
223            Item::LabelRef(l, shift) => {
224                let unshifted_addr = l.resolve_absolute(resolver)?;
225
226                if unshifted_addr.trailing_zeros() < (*shift).into() {
227                    return Err(AssemblerError::InsufficientAlignment {
228                        label: l.0.clone(),
229                        offset: l.1,
230                        shift: *shift,
231                    });
232                }
233
234                buf.put_u32(unshifted_addr >> *shift);
235            }
236        }
237        Ok(())
238    }
239}
240
241impl<L> Display for Item<L>
242where
243    L: Display,
244{
245    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
246        match self {
247            Item::Label(label) => write!(f, ".label {label}")?,
248            Item::Align(a) => write!(f, ".align {a}")?,
249            Item::DecodingTable(_) => write!(f, ".decoding_table")?,
250            Item::FnHeader(CallingConvention::ArgsInLocals, args) => write!(f, ".fnlocal {args}")?,
251            Item::FnHeader(CallingConvention::ArgsOnStack, args) => write!(f, ".fnstack {args}")?,
252            Item::Instr(instr) => write!(f, "\t{instr}")?,
253            Item::MysteryString(s) => write!(f, ".string {:?}", s)?,
254            Item::CompressedString(c) => write!(f, ".compressed_string {c:x}")?,
255            Item::Utf32String(s) => write!(f, ".unistring {:?}", s)?,
256            Item::Blob(b) => write!(f, ".blob {b:x}")?,
257            Item::LabelRef(LabelRef(label, offset), shift) => {
258                write!(f, ".labelref ({label}")?;
259                if *offset != 0 {
260                    write!(f, "{offset:+#x}")?;
261                }
262                if *shift != 0 {
263                    write!(f, ">>{shift}")?;
264                }
265                write!(f, ")")?;
266            }
267        }
268        Ok(())
269    }
270}
271
272impl<L> ZeroItem<L> {
273    /// Applies the given mapping function to the label, if any, within the zero-item.
274    pub fn map<F, M>(self, mut f: F) -> ZeroItem<M>
275    where
276        F: FnMut(L) -> M,
277    {
278        match self {
279            ZeroItem::Label(l) => ZeroItem::Label(f(l)),
280            ZeroItem::Space(x) => ZeroItem::Space(x),
281            ZeroItem::Align(a) => ZeroItem::Align(a),
282        }
283    }
284
285    pub(crate) fn len(&self) -> u32 {
286        match self {
287            ZeroItem::Label(_) => 0,
288            ZeroItem::Space(x) => *x,
289            ZeroItem::Align(_) => 0,
290        }
291    }
292
293    pub(crate) fn align(&self) -> u32 {
294        match self {
295            ZeroItem::Label(_) => 1,
296            ZeroItem::Space(_) => 1,
297            ZeroItem::Align(a) => (*a).into(),
298        }
299    }
300}
301
302impl<L> Display for ZeroItem<L>
303where
304    L: Display,
305{
306    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307        match self {
308            ZeroItem::Label(label) => write!(f, ".label {label}"),
309            ZeroItem::Space(x) => write!(f, ".space {x}"),
310            ZeroItem::Align(a) => write!(f, ".align {a}"),
311        }
312    }
313}
314
315impl<L> LabelRef<L> {
316    /// Applies the given mapping function to the label within the label reference.
317    pub fn map<F, M>(self, mut f: F) -> LabelRef<M>
318    where
319        F: FnMut(L) -> M,
320    {
321        LabelRef(f(self.0), self.1)
322    }
323
324    pub(crate) fn resolve<R>(&self, resolver: &R) -> Result<ResolvedAddr, AssemblerError<L>>
325    where
326        R: Resolver<Label = L>,
327    {
328        Ok(match resolver.resolve(&self.0)? {
329            ResolvedAddr::Rom(addr) => {
330                ResolvedAddr::Rom(addr.checked_add_signed(self.1).overflow()?)
331            }
332            ResolvedAddr::Ram(addr) => {
333                ResolvedAddr::Ram(addr.checked_add_signed(self.1).overflow()?)
334            }
335        })
336    }
337
338    pub(crate) fn resolve_absolute<R>(&self, resolver: &R) -> Result<u32, AssemblerError<L>>
339    where
340        R: Resolver<Label = L>,
341    {
342        resolver
343            .resolve_absolute(&self.0)?
344            .checked_add_signed(self.1)
345            .overflow()
346    }
347}