libgm 0.5.1

A tool for modding, unpacking and decompiling GameMaker games
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
//! Everything related to GML instructions is in here,
//! including the important [`Instruction`] type.

mod asset_reference;
mod category;
mod code_variable;
mod comparison_type;
mod data_type;
mod instance_type;
mod push_value;
mod variable_type;

pub use asset_reference::AssetReference;
pub use category::Category;
pub use code_variable::CodeVariable;
pub use comparison_type::ComparisonType;
pub use data_type::DataType;
pub use instance_type::InstanceType;
pub use push_value::PushValue;
pub use variable_type::VariableType;

use crate::wad::elements::function::GMFunction;
use crate::wad::reference::GMRef;

/// A GameMaker VM Instruction.
///
/// This is the most important data type for GML.
///
/// For more information on GML, see the [module level documentation].
///
/// [module level documentation]: crate::gml
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
    /// Converts the top of the stack from one type to another.
    ///
    /// Sometimes it may be necessary to convert between "actual data types" and
    /// [`DataType::Variable`] (note: not sure if necessary, but the
    /// YoYoGames compiler generates it). For example, when calling a
    /// function, all arguments need (?) to of data type [`DataType::Variable`].
    /// So if you want to call `foo(41)`:
    /// ```text
    /// pushim 41
    /// conv.i.v
    /// call foo(argc=1)
    /// ```
    Convert { from: DataType, to: DataType },

    /// Pops two values from the stack, **multiplies** them, and pushes the
    /// result.
    Multiply {
        multiplicand: DataType,
        multiplier: DataType,
    },

    /// Pops two values from the stack, **divides** them, and pushes the result.
    /// The second popped value (`dividend`) is divided by the first popped
    /// value (`divisor`).
    Divide {
        dividend: DataType,
        divisor: DataType,
    },

    /// Pops two values from the stack, performs a GML `div` operation (division
    /// with remainder), and pushes the result. The second popped value
    /// (`dividend`) is divided (with remainder) by the first popped value
    /// (`divisor`).
    ///
    /// This operation is similar to [`Instruction::Modulus`], except it behaves
    /// differently for negative values. For example: `-19 rem 12 == -7`
    /// (not 5).
    Remainder {
        dividend: DataType,
        divisor: DataType,
    },

    /// Pops two values from the stack, performs a GML `mod` operation (`%`),
    /// and pushes the result. The second popped value is modulo'd against
    /// the first popped value.
    ///
    /// This operation is similar to [`Instruction::Remainder`], except it
    /// behaves differently for negative values. This `modulus` operation performs [Euclidean division](https://en.wikipedia.org/wiki/Euclidean_division).
    /// For example: `-19 rem 12 == 5` (not -7).
    Modulus {
        dividend: DataType,
        divisor: DataType,
    },

    /// Pops two values from the stack, **adds** them, and pushes the result.
    Add { augend: DataType, addend: DataType },

    /// Pops two values from the stack, **subtracts** them, and pushes the
    /// result. The second popped value is subtracted by the first popped
    /// value.
    Subtract {
        minuend: DataType,
        subtrahend: DataType,
    },

    /// Pops two values from the stack, performs an **AND** operation, and
    /// pushes the result. This can be done bitwise or logically, depending
    /// on the data type(s).
    And { lhs: DataType, rhs: DataType },

    /// Pops two values from the stack, performs an **OR** operation, and pushes
    /// the result. This can be done bitwise or logically, depending on the
    /// data type(s).
    Or { lhs: DataType, rhs: DataType },

    /// Pops two values from the stack, performs an **XOR** operation, and
    /// pushes the result. This can be done bitwise or logically, depending
    /// on the data type(s).
    Xor { lhs: DataType, rhs: DataType },

    /// **Negates** the top value of the stack (as in, multiplies it with -1).
    Negate { data_type: DataType },

    /// Pops one value from the stack, performs a **NOT** operation, and pushes
    /// the result. This can be done bitwise or logically, depending on the
    /// data type(s).
    Not { data_type: DataType },

    /// Pops two values from the stack, performs a bitwise **left shift**
    /// operation (`<<`), and pushes the result. The second popped value
    /// (`value`) is shifted left by the first popped value (`shift_amount`).
    ShiftLeft {
        value: DataType,
        shift_amount: DataType,
    },

    /// Pops two values from the stack, performs a bitwise **right shift**
    /// operation (`>>`), and pushes the result. The second popped value
    /// (`value`) is shifted right by the first popped value (`shift_amount`).
    ShiftRight {
        value: DataType,
        shift_amount: DataType,
    },

    /// Pops two values from the stack, **compares** them using a
    /// [`ComparisonType`], and pushes a boolean result
    /// ([`DataType::Boolean`]).
    Compare {
        lhs: DataType,
        rhs: DataType,
        comparison_type: ComparisonType,
    },

    /// Pops a value from the stack, and generally **stores it in a variable**,
    /// array, or otherwise.
    ///
    /// Generally, `type1` signifies the type of the value on the stack (the one
    /// to pop) and `type2` will be [`DataType::Variable`].
    /// However, there are exceptions to this. For example, when the variable
    /// reference mode is not [`VariableType::Normal`], then `type2` may be
    /// [`DataType::Int32`] instead.
    ///
    /// There is an alternate instruction/mode with the same opcode that swaps
    /// values around on the stack. This operation is known as
    /// [`Instruction::PopSwap`]. Note that this does not apply to this enum
    /// variant ([`Instruction::Pop`].
    Pop {
        variable: CodeVariable,
        type1: DataType,
        type2: DataType,
    },

    /// **Swaps** values around on the stack.
    ///
    /// This instruction has the same opcode as [`Instruction::Push`].
    ///
    /// For "normal mode" (`is_array = false`), it does the following:
    /// 1) Pops value A
    /// 2) Pops value B
    /// 3) Pops value C
    /// 4) Pushes value A
    /// 5) Pushes value C
    /// 6) Pushes value B
    ///
    /// This essentially turns the three stacktop
    /// elements [A, B, C, ...] into [B, C, A, ...].
    ///
    /// For array mode, it does the following:
    /// 1) Pops value A
    /// 2) Pops value B
    /// 3) Pops value C
    /// 4) Pops value D
    /// 5) Pushes value A
    /// 6) Pushes value D
    /// 7) Pushes value C
    /// 8) Pushes value B
    ///
    /// This essentially turns four stacktop elements
    /// [A, B, C, D, ...] into [B, C, D, A, ...].
    PopSwap { is_array: bool },

    /// **Duplicates** values on the stack.
    ///
    /// The specified `size` is the total size of all
    /// elements that should be duplicated.
    ///
    /// The specified data type hints what the stacktop data type is.
    /// However, it does (most likely?) not explicitly have to match.
    /// It only influences a multiplication factor of how many bytes to
    /// clone, since different data types have different sizes on the stack.
    ///
    /// To get the duplication value *count* (instead of byte size) for a
    /// homogenous stack data type, use `instr.size /
    /// instr.data_type.size()`. For safety, it is probably a good idea to
    /// verify that the remainder is zero.
    Duplicate { data_type: DataType, size: u8 },

    /// **Swaps** values around on the stack.
    ///
    /// First, elements with a total size of `size1` are popped into a temporary
    /// "top stack". Then, elements with a total size of `size2` are popped
    /// into a temporary "bottom stack". Afterward, the "bottom stack" is
    /// pushed. And lastly, the "top stack" is pushed.
    ///
    /// For information on the data type, see [`Instruction::Duplicate`].
    DuplicateSwap {
        data_type: DataType,
        size1: u8,
        size2: u8,
    },

    /// Pops a value from the stack, and **returns** from the current
    /// function/script with that value as the return value.
    ///
    /// This instruction always has the data type [`DataType::Variable`],
    /// which is omitted from this enum data since it is redundant.
    /// However, you may still need to convert your stack value to
    /// [`DataType::Variable`] using [`Instruction::Convert`] before returning.
    Return,

    /// **Returns** from the current function/script/event with no return value.
    ///
    /// This instruction always has the data type [`DataType::Int32`] for
    /// whatever reason. Since this data type carries no meaningful
    /// information, it is not stored in this enum variant.
    Exit,

    /// **Pops** a value from the stack, and **discards** it.
    ///
    /// This is similar to [`Instruction::Pop`], except it does not store the
    /// result in any variable.
    ///
    /// This instruction is commonly used to clean up unused return values of
    /// function calls.
    PopDiscard { data_type: DataType },

    /// Unconditionally **branches** (jumps) to another instruction in the code
    /// entry.
    ///
    /// Also known as `B`, `Jump`, `jmp`.
    ///
    /// The jump offset may be negative and is expressed in multiples of 4
    /// bytes. For example, a jump offset of 2 may skip `push.s`, a jump
    /// offset of 5 may skip `push.d`. Most of the time, this will skip
    /// multiple instructions at the same time.
    Branch { jump_offset: i32 },

    /// Pops a boolean/int32 value from the stack.
    /// If true/nonzero, **branches** (jumps) to another instruction in the code
    /// entry.
    ///
    /// Also known as `BranchTrue`, `bt`.
    ///
    /// The jump offset is explained in [`Instruction::Branch`].
    BranchIf { jump_offset: i32 },

    /// Pops a boolean/int32 value from the stack.
    /// If false/zero, **branches** (jumps) to another instruction in the code
    /// entry.
    ///
    /// Also known as `BranchFalse`, `bf`.
    ///
    /// The jump offset is explained in [`Instruction::Branch`].
    BranchUnless { jump_offset: i32 },

    /// Pushes a `with` context* used for GML `with` statements,
    /// to the VM environment/self instance stack.
    ///
    /// This does not push any value to the value stack (like
    /// [`Instruction::Push`]). It is rather classified as branch
    /// instruction. The specified jump offset will be branched to when the
    /// `with` loop is done. The branch target leads to the code after the
    /// `with` block.
    ///
    /// The jump offset is further explained in [`Instruction::Branch`].
    PushWithContext { jump_offset: i32 },

    /// Pops/ends a `with` context, used for GML `with` statements,
    /// from the VM environment/self instance stack.
    /// This instruction will branch to its encoded address until no longer
    /// iterating instances, where the context will finally be gone for good.
    ///
    /// There is a different mode for this instruction with the same opcode:
    /// [`Instruction::PopWithContextExit`].
    ///
    /// The jump offset is further explained in [`Instruction::Branch`].
    PopWithContext { jump_offset: i32 },

    /// A variation of [`Instruction::PopWithContext`].
    /// This variation exits the `with` loop context without branching anywhere.
    /// Since the instruction pointer is malformed afterward, this instruction
    /// is only seen before a [`Instruction::Exit`],
    /// other [`Instruction::PopWithContextExit`]s or perhaps
    /// [`Instruction::PopDiscard`].
    PopWithContextExit,

    /// **Pushes** a constant value onto the stack.
    /// Can vary in size depending on value type.
    ///
    /// This instruction can also push variables (which copies the value),
    /// but I don't know the exact behavior / internal implementation.
    ///
    /// TODO(doc): Explain pushing of variables better
    Push { value: PushValue },

    /// Pushes a value stored in a local variable onto the stack.
    ///
    /// This is a specialization of the [`Instruction::Push`] instruction
    /// where `value` is [`PushValue::Variable`] whose instance type is
    /// [`InstanceType::Local`].
    ///
    /// This is only a minor optimization; using the standard push instruction
    /// also works fine.
    PushLocal { variable: CodeVariable },

    /// Pushes a value stored in a global variable onto the stack.
    ///
    /// This is a specialization of the [`Instruction::Push`] instruction
    /// where `value` is [`PushValue::Variable`] whose instance type is
    /// [`InstanceType::Global`].
    ///
    /// This is only a minor optimization; using the standard push instruction
    /// also works fine.
    PushGlobal { variable: CodeVariable },

    /// Pushes a value stored in a GameMaker builtin variable onto the stack.
    ///
    /// This is a specialization of the [`Instruction::Push`] instruction
    /// where `value` is [`PushValue::Variable`] whose instance type is
    /// [`InstanceType::Builtin`].
    ///
    /// This is only a minor optimization; using the standard push instruction
    /// also works fine.
    PushBuiltin { variable: CodeVariable },

    /// Pushes an immediate signed 32-bit integer value onto the stack, encoded
    /// as a signed 16-bit integer.
    ///
    /// The data type of this instruction is always [`DataType::Int16`],
    /// which is not stored here to avoid redundancy.
    ///
    /// Please note that [`DataType::Int16`] is only a valid data type in
    /// instructions when pushing. Using it anywhere else is wrong, because
    /// `Int16`s immediately get converted to `Int32`s when pushed on the
    /// stack. The data type `Int16` *does not exist* on the stack.
    PushImmediate { integer: i16 },

    /// Calls a GML script/function, using its ID (index).
    /// Arguments are prepared prior to this instruction, in reverse order.
    ///
    /// Argument count is encoded in this instruction.
    /// Arguments are popped off of the stack.
    ///
    /// Every function call is allowed to have an arbitary number of arguments.
    /// Certain builtin functions are designed to handle any
    /// number of arguments, such as `ds_list_add`.
    /// For custom functions (aka. scripts), the remaining values will be filled
    /// with specified default values or `undefined` (?).
    /// TODO(doc): I'm not sure what happens in WAD<15.
    /// TODO(doc): I'm not sure what happens when too many arguments are
    /// specified (probably nothing?).
    Call {
        function: GMRef<GMFunction>,
        argument_count: u16,
    },

    /// Pops two values off of the stack, and then calls a
    /// GML script/function using those values, representing
    /// the "self" instance to be used when calling,
    /// as well as the reference to the function being called.
    ///
    /// This instruction pops two values off the stack and then calls a
    /// function, dynamically:
    /// 1) The function reference is popped (should be a [`DataType::Variable`]
    ///    value storing a function ID).
    /// 2) The instance type is popped. I'm not very sure how this works, but I
    ///    assume it is a raw [`InstanceType`] value stored in the stack?
    /// 3) `argument_count` arguments are popped.
    ///
    /// For more information on calling functions, see [`Instruction::Call`].
    CallVariable { argument_count: u16 },

    /// Verifies an array index is within proper
    /// bounds, typically for multidimensional arrays.
    ///
    /// TODO(doc): How does this work? What does it actually do?
    CheckArrayIndex,

    /// Pops two values from the stack, those being an index and an array
    /// reference. Then, pushes the value stored at the passed-in array at
    /// the desired index.
    ///
    /// This is a very similar to [`Instruction::PushArrayContainer`] (see this
    /// for more info), except that this instruction is used only at the end
    /// of an accessor chain. Only relevant for the final/last index
    /// operation of a multidimensional array access.
    PushArrayFinal,

    /// Pops three values from the stack, those being an index, an array
    /// reference, and a value. Then, assigns the value to the array at the
    /// specified index.
    ///
    /// Concrete Steps:
    /// 1) Pops an index from the stack
    /// 2) Pops array reference from stack ([`DataType::Variable`])
    /// 3) Pops value from stack and moves it into the specified array item
    PopArrayFinal,

    /// Pushes a multidimensional array:
    /// 1) Pops an index from the stack
    /// 2) Pops array reference from stack ([`DataType::Variable`])
    /// 3) Pushes a new array reference from the passed-in array at the desired
    ///    index
    ///
    /// This instruction is used for all multidimensional
    /// index pushes from the second through the second to last.
    /// The final/last index operation will be done using
    /// [`Instruction::PushArrayFinal`].
    PushArrayContainer,

    /// Sets a global variable in the VM (popped from stack), designated for
    /// tracking the now-deprecated array copy-on-write functionality in GML.
    ///
    /// The value used is specific to certain locations in scripts.
    /// When array copy-on-write functionality is disabled, this extended opcode
    /// is not used.
    ///
    /// This instruction will pop one value (`Int32`) off the stack, indicating
    /// the array owner ID.
    SetArrayOwner,

    /// Pushes a boolean value to the stack, indicating whether static
    /// initialization has already occurred for this function (true), or
    /// otherwise false.
    ///
    /// This is typically used in conjunction with
    /// [`Instruction::SetStaticInitialized`] and branch instructions.
    HasStaticInitialized,

    /// Marks the current function to no longer be able to enter its own static
    /// initialization.
    ///
    /// This can either occur at the beginning or end of a static block,
    /// depending on whether `AllowReentrantStatic` is enabled by a game's
    /// developer (enabled by default before GameMaker 2024.11; disabled by
    /// default otherwise).
    ///
    /// This is typically used in conjunction with
    /// [`Instruction::HasStaticInitialized`] and branch instructions.
    SetStaticInitialized,

    /// Keeps track of an array reference temporarily.
    /// Used in multidimensional array compound assignment statements
    /// ([`Instruction::PushArrayFinal`] etc.).
    ///
    /// Presumed to be used for garbage collection purposes.
    SaveArrayReference,

    /// Restores a previously-tracked array reference.
    /// Used in multidimensional array compound assignment statements
    /// ([`Instruction::PushArrayFinal`] etc.).
    ///
    /// Presumed to be used for garbage collection purposes.
    RestoreArrayReference,

    /// Pops a value from the stack, and pushes a boolean result.
    /// The result is true if a "nullish" value, such as `undefined` or GML's
    /// `pointer_null`.
    IsNullishValue,

    /// Pushes an asset reference to the stack, encoded in an integer.
    /// Includes asset type and index.
    ///
    /// This instruction is preferred over normal [`Instruction::Push`] with
    /// [`DataType::Int32`], since the intent is clearer that this is an
    /// encoded asset reference, not an actual integer.
    ///
    /// This instruction is used in more modern versions of GameMaker.
    PushReference { asset_reference: AssetReference },
}

impl Instruction {
    /// Gets the instruction size in multiples of 4 bytes.
    /// This unit is used by `jump_offset`s in branch instructions.
    ///
    /// For example, [`Instruction::Push`] with a [`PushValue::Int16`] has a
    /// size of 5.
    #[must_use]
    pub const fn size4(&self) -> u32 {
        match self {
            Self::Push { value } => match value {
                PushValue::Int16(_) => 1,
                PushValue::Int64(_) | PushValue::Double(_) => 3,
                _ => 2,
            },
            Self::Pop { .. }
            | Self::PushLocal { .. }
            | Self::PushGlobal { .. }
            | Self::PushBuiltin { .. }
            | Self::Call { .. }
            | Self::PushReference { .. } => 2,
            _ => 1,
        }
    }

    /// Gets the instruction size in bytes.
    /// This size includes extra data like integers, floats, variable
    /// references, etc.
    ///
    /// For example, [`Instruction::Push`] with a [`PushValue::Int16`] has a
    /// size of 20.
    #[must_use]
    pub const fn size(&self) -> u32 {
        self.size4() * 4
    }

    /// Attempts to extract a [`CodeVariable`] from  the instruction.
    ///
    /// This can succeed for `Push` and will always succeed for
    /// `PushGlobal`, `PushLocal`, `PushBuiltin` and `Pop`.
    #[must_use]
    pub const fn variable(&self) -> Option<&CodeVariable> {
        match self {
            Self::Pop { variable, .. }
            | Self::Push { value: PushValue::Variable(variable) }
            | Self::PushLocal { variable }
            | Self::PushGlobal { variable }
            | Self::PushBuiltin { variable } => Some(variable),
            _ => None,
        }
    }

    /// Attempts to extract a `GMRef<GMFunction>` from the instruction.
    ///
    /// This can succeed for `Push` and `PushReference` and will always succeed
    /// for `Call`.
    #[must_use]
    pub const fn function(&self) -> Option<GMRef<GMFunction>> {
        match self {
            Self::Push { value: PushValue::Function(function) }
            | Self::Call { function, .. }
            | Self::PushReference {
                asset_reference: AssetReference::Function(function),
            } => Some(*function),
            _ => None,
        }
    }

    /// Attempts to extract a jump offset in bytes from the instruction.
    ///
    /// This will always succeed for `Branch`, `BranchIf`,
    /// `BranchUnless`, `PushWithContext` and `PopWithContext`.
    #[must_use]
    pub const fn jump_offset(&self) -> Option<i32> {
        match self {
            Self::Branch { jump_offset }
            | Self::BranchIf { jump_offset }
            | Self::BranchUnless { jump_offset }
            | Self::PushWithContext { jump_offset }
            | Self::PopWithContext { jump_offset } => Some(*jump_offset),
            _ => None,
        }
    }

    /// Attempts to extract a jump offset in bytes from the instruction.
    ///
    /// This will always succeed for `Branch`, `BranchIf`,
    /// `BranchUnless`, `PushWithContext` and `PopWithContext`.
    #[must_use]
    pub const fn jump_offset_mut(&mut self) -> Option<&mut i32> {
        match self {
            Self::Branch { jump_offset }
            | Self::BranchIf { jump_offset }
            | Self::BranchUnless { jump_offset }
            | Self::PushWithContext { jump_offset }
            | Self::PopWithContext { jump_offset } => Some(jump_offset),
            _ => None,
        }
    }

    /// Attempts to extract the first (or the only) data type from the
    /// instruction.
    ///
    /// For binary operations, this will be RHS (the right hand side).
    ///
    /// NOTE: Right now, it's kind of arbitrary which instructions' data types
    /// are deemed "relevant enough" to return them and which don't really
    /// belong (does return: `PushLocal` - Variable, does not return:
    /// `PopSwap` - Int16). This will be changed in the future **if I get
    /// feedback pls**.
    #[must_use]
    pub const fn type1(&self) -> Option<DataType> {
        Some(match self {
            Self::Convert { from, .. } => *from,
            Self::Multiply { multiplier, .. } => *multiplier,
            Self::Divide { divisor, .. }
            | Self::Remainder { divisor, .. }
            | Self::Modulus { divisor, .. } => *divisor,
            Self::Add { addend, .. } => *addend,
            Self::Subtract { subtrahend, .. } => *subtrahend,
            Self::And { rhs, .. }
            | Self::Or { rhs, .. }
            | Self::Xor { rhs, .. }
            | Self::Compare { rhs, .. } => *rhs,
            Self::Negate { data_type }
            | Self::Not { data_type }
            | Self::Duplicate { data_type, .. }
            | Self::DuplicateSwap { data_type, .. }
            | Self::PopDiscard { data_type } => *data_type,
            Self::ShiftLeft { shift_amount, .. } | Self::ShiftRight { shift_amount, .. } => {
                *shift_amount
            }
            Self::Pop { type1, .. } => *type1,
            Self::Push { value } => value.data_type(),
            Self::PushLocal { .. } | Self::PushGlobal { .. } | Self::PushBuiltin { .. } => {
                DataType::Variable
            }
            Self::PushImmediate { .. } => DataType::Int16,
            Self::Call { .. } => DataType::Int32,
            Self::CallVariable { .. } => DataType::Variable,
            _ => return None,
        })
    }

    /// Attempts to return the second data type of this instruction.
    ///
    /// For binary operations, this will be LHS (the left hand side).
    #[must_use]
    pub const fn type2(&self) -> Option<DataType> {
        Some(match *self {
            Self::Convert { to, .. } => to,
            Self::Multiply { multiplicand, .. } => multiplicand,
            Self::Divide { dividend, .. }
            | Self::Remainder { dividend, .. }
            | Self::Modulus { dividend, .. } => dividend,
            Self::Add { augend, .. } => augend,
            Self::Subtract { minuend, .. } => minuend,
            Self::And { lhs, .. }
            | Self::Or { lhs, .. }
            | Self::Xor { lhs, .. }
            | Self::Compare { lhs, .. } => lhs,
            Self::ShiftLeft { value, .. } | Self::ShiftRight { value, .. } => value,
            Self::Pop { type2, .. } => type2,
            _ => return None,
        })
    }
}