luau-bytecode 0.732.0

Luau bytecode model, builder, serializer, and dumper
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
use super::*;

impl FunctionGraphBuilder {
    pub(crate) fn operands_for(
        &mut self,
        id: BytecodeInstructionId,
        pc: InstructionPc,
        instruction: Instruction,
        stream: BytecodeInstructionStream<'_>,
        block_by_start: &HashMap<usize, BytecodeBlockId>,
        blocks: &[BytecodeBlock],
    ) -> Result<Vec<BytecodeOperand>, BytecodeReadError> {
        let opcode = unsafe { instruction.opcode_unchecked() };
        let mut operands = Vec::new();

        match opcode {
            Opcode::LoadNil => {
                self.produce(instruction.a(), id);
            }
            Opcode::LoadB => {
                operands.push(self.immediate(BytecodeImmediate::Boolean(instruction.b() != 0)));
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
                self.produce(instruction.a(), id);
            }
            Opcode::LoadN => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.d()))));
                self.produce(instruction.a(), id);
            }
            Opcode::LoadK => {
                operands.push(BytecodeOperand::VmConstant(i32::from(instruction.d())));
                self.produce(instruction.a(), id);
            }
            Opcode::Move => {
                operands.push(self.producer(blocks, instruction.b())?);
                self.produce(instruction.a(), id);
            }
            Opcode::GetGlobal => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
                self.produce(instruction.a(), id);
            }
            Opcode::GetImport => {
                operands.push(BytecodeOperand::VmConstant(i32::from(instruction.d())));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(self.import_immediate(aux.word()));
                }
                self.produce(instruction.a(), id);
            }
            Opcode::SetGlobal => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
            }
            Opcode::GetUpval => {
                operands.push(BytecodeOperand::VmUpvalue(u32::from(instruction.b())));
                self.produce(instruction.a(), id);
            }
            Opcode::SetUpval => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(BytecodeOperand::VmUpvalue(u32::from(instruction.b())));
            }
            Opcode::GetTable => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.producer(blocks, instruction.c())?);
                self.produce(instruction.a(), id);
            }
            Opcode::SetTable => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.producer(blocks, instruction.c())?);
            }
            Opcode::GetTableKs | Opcode::GetUDataKs => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
                self.produce(instruction.a(), id);
            }
            Opcode::SetTableKs | Opcode::SetUDataKs => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
            }
            Opcode::GetTableN => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands
                    .push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()) + 1)));
                self.produce(instruction.a(), id);
            }
            Opcode::SetTableN => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.b())?);
                operands
                    .push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()) + 1)));
            }
            Opcode::NewClosure => {
                operands.push(BytecodeOperand::VmProto(instruction.d() as u32));
                self.produce(instruction.a(), id);
            }
            Opcode::NameCall | Opcode::NameCallUData => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
                self.registers
                    .insert(BytecodeOperand::Instruction(id), instruction.a());
                self.produce_projection(instruction.a(), id, 0);
                self.produce_projection(instruction.a() + 1, id, 1);
            }
            Opcode::Call | Opcode::CallFb => {
                let parameter_count = i32::from(instruction.b()) - 1;
                let result_count = i32::from(instruction.c()) - 1;
                operands.push(self.immediate(BytecodeImmediate::Int(parameter_count)));
                operands.push(self.immediate(BytecodeImmediate::Int(result_count)));
                if opcode == Opcode::CallFb
                    && let Some(aux) = stream.graph_aux_word(pc)
                {
                    operands.push(self.immediate(BytecodeImmediate::Int(aux.word() as i32)));
                }
                operands.push(self.producer(blocks, instruction.a())?);
                if parameter_count >= 0 {
                    for offset in 1..=parameter_count as u8 {
                        operands.push(self.producer(blocks, instruction.a() + offset)?);
                    }
                } else {
                    operands.extend(self.producers_up_to_top(blocks, instruction.a() + 1));
                }
                self.registers
                    .insert(BytecodeOperand::Instruction(id), instruction.a());
                self.apply_call(id, instruction.a(), result_count);
                if result_count >= 0 {
                    for offset in 0..result_count as u8 {
                        self.produce_projection(instruction.a() + offset, id, u32::from(offset));
                    }
                }
            }
            Opcode::NewClassMember => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.c())?);
                if let Some(aux) = stream.graph_aux_word(pc) {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
            }
            Opcode::Return => {
                let result_count = i32::from(instruction.b()) - 1;
                operands.push(self.immediate(BytecodeImmediate::Int(result_count)));
                if result_count >= 0 {
                    for offset in 0..result_count as u8 {
                        operands.push(self.producer(blocks, instruction.a() + offset)?);
                    }
                } else {
                    operands.extend(self.producers_up_to_top(blocks, instruction.a()));
                }
                if result_count == 0 {
                    operands.push(BytecodeOperand::VmRegister(instruction.a()));
                }
            }
            Opcode::Jump | Opcode::JumpBack | Opcode::JumpX => {
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
            }
            Opcode::CmpProto => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(
                    self.immediate(BytecodeImmediate::Int(
                        stream
                            .graph_aux_word(pc)
                            .map(InstructionAux::word)
                            .unwrap_or_default() as i32,
                    )),
                );
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
            }
            Opcode::JumpIf
            | Opcode::JumpIfNot
            | Opcode::JumpIfEq
            | Opcode::JumpIfLe
            | Opcode::JumpIfLt
            | Opcode::JumpIfNotEq
            | Opcode::JumpIfNotLe
            | Opcode::JumpIfNotLt
            | Opcode::ForNPrep
            | Opcode::ForNLoop => {
                match opcode {
                    Opcode::JumpIf | Opcode::JumpIfNot => {
                        operands.push(self.producer(blocks, instruction.a())?);
                    }
                    Opcode::JumpIfEq
                    | Opcode::JumpIfLe
                    | Opcode::JumpIfLt
                    | Opcode::JumpIfNotEq
                    | Opcode::JumpIfNotLe
                    | Opcode::JumpIfNotLt => {
                        operands.push(self.producer(blocks, instruction.a())?);
                        operands.push(
                            self.producer(
                                blocks,
                                stream
                                    .graph_aux_word(pc)
                                    .map(InstructionAux::a)
                                    .unwrap_or_default(),
                            )?,
                        );
                    }
                    Opcode::ForNPrep => {
                        operands.push(self.producer(blocks, instruction.a())?);
                        operands.push(self.producer(blocks, instruction.a() + 1)?);
                        operands.push(self.producer(blocks, instruction.a() + 2)?);
                        self.registers
                            .insert(BytecodeOperand::Instruction(id), instruction.a());
                        self.produce_projection(instruction.a(), id, 0);
                        self.produce_projection(instruction.a() + 1, id, 1);
                        self.produce_projection(instruction.a() + 2, id, 2);
                    }
                    Opcode::ForNLoop => {
                        operands.push(self.producer(blocks, instruction.a())?);
                        operands.push(self.producer(blocks, instruction.a() + 1)?);
                        operands.push(self.producer(blocks, instruction.a() + 2)?);
                    }
                    _ => unreachable!("non-jump opcode in jump operand branch"),
                }

                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
            }
            Opcode::JumpXEqKNil | Opcode::JumpXEqKB | Opcode::JumpXEqKN | Opcode::JumpXEqKS => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(
                    self.immediate(BytecodeImmediate::Boolean(
                        stream
                            .graph_aux_word(pc)
                            .map(InstructionAux::is_negated)
                            .unwrap_or(false),
                    )),
                );
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
                match opcode {
                    Opcode::JumpXEqKB => operands.push(
                        self.immediate(BytecodeImmediate::Boolean(
                            stream
                                .graph_aux_word(pc)
                                .map(InstructionAux::kb)
                                .unwrap_or(false),
                        )),
                    ),
                    Opcode::JumpXEqKN | Opcode::JumpXEqKS => {
                        operands.push(BytecodeOperand::VmConstant(
                            stream
                                .graph_aux_word(pc)
                                .map(InstructionAux::kv)
                                .map(constant_index)
                                .unwrap_or_default(),
                        ));
                    }
                    _ => {}
                }
            }
            Opcode::Add
            | Opcode::Sub
            | Opcode::Mul
            | Opcode::Div
            | Opcode::Mod
            | Opcode::Pow
            | Opcode::And
            | Opcode::Or
            | Opcode::IDiv => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(self.producer(blocks, instruction.c())?);
                self.produce(instruction.a(), id);
            }
            Opcode::AddK
            | Opcode::SubK
            | Opcode::MulK
            | Opcode::DivK
            | Opcode::ModK
            | Opcode::PowK
            | Opcode::AndK
            | Opcode::OrK
            | Opcode::IDivK => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(BytecodeOperand::VmConstant(i32::from(instruction.c())));
                self.produce(instruction.a(), id);
            }
            Opcode::SubRK | Opcode::DivRK => {
                operands.push(BytecodeOperand::VmConstant(i32::from(instruction.b())));
                operands.push(self.producer(blocks, instruction.c())?);
                self.produce(instruction.a(), id);
            }
            Opcode::Concat => {
                for register in instruction.b()..=instruction.c() {
                    operands.push(self.producer(blocks, register)?);
                }
                self.produce(instruction.a(), id);
            }
            Opcode::Not | Opcode::Minus | Opcode::Length => {
                operands.push(self.producer(blocks, instruction.b())?);
                self.produce(instruction.a(), id);
            }
            Opcode::NewTable => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.b()))));
                operands.push(
                    self.immediate(BytecodeImmediate::Int(
                        stream
                            .graph_aux_word(pc)
                            .map(InstructionAux::word)
                            .unwrap_or_default() as i32,
                    )),
                );
                self.produce(instruction.a(), id);
            }
            Opcode::DupTable | Opcode::DupClosure | Opcode::LoadKx => {
                let constant = if opcode == Opcode::LoadKx {
                    stream
                        .graph_aux_word(pc)
                        .map(InstructionAux::word)
                        .map(constant_index)
                        .unwrap_or_default()
                } else {
                    i32::from(instruction.d())
                };
                operands.push(BytecodeOperand::VmConstant(constant));
                self.produce(instruction.a(), id);
            }
            Opcode::NewClass => {
                operands.push(self.producer(blocks, instruction.b())?);
                operands.push(BytecodeOperand::VmConstant(
                    stream
                        .graph_aux_word(pc)
                        .map(InstructionAux::word)
                        .map(constant_index)
                        .unwrap_or_default(),
                ));
                self.produce(instruction.a(), id);
            }
            Opcode::SetList => {
                let count = i32::from(instruction.c()) - 1;
                operands.push(
                    self.immediate(BytecodeImmediate::Int(
                        stream
                            .graph_aux_word(pc)
                            .map(InstructionAux::word)
                            .unwrap_or_default() as i32,
                    )),
                );
                operands.push(self.immediate(BytecodeImmediate::Int(count)));
                operands.push(self.producer(blocks, instruction.a())?);
                if count >= 0 {
                    for offset in 0..count as u8 {
                        operands.push(self.producer(blocks, instruction.b() + offset)?);
                    }
                } else {
                    operands.extend(self.producers_up_to_top(blocks, instruction.b()));
                }
            }
            Opcode::FastCall
            | Opcode::FastCall1
            | Opcode::FastCall2
            | Opcode::FastCall2K
            | Opcode::FastCall3 => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.a()))));
                if matches!(
                    opcode,
                    Opcode::FastCall1 | Opcode::FastCall2 | Opcode::FastCall2K | Opcode::FastCall3
                ) {
                    operands.push(self.producer(blocks, instruction.b())?);
                }
                if matches!(opcode, Opcode::FastCall2 | Opcode::FastCall3)
                    && let Some(aux) = stream.graph_aux_word(pc).map(InstructionAux::a)
                {
                    operands.push(self.producer(blocks, aux)?);
                }
                if opcode == Opcode::FastCall3
                    && let Some(aux) = stream.graph_aux_word(pc).map(InstructionAux::b)
                {
                    operands.push(self.producer(blocks, aux)?);
                }
                if opcode == Opcode::FastCall2K
                    && let Some(aux) = stream.graph_aux_word(pc)
                {
                    operands.push(BytecodeOperand::VmConstant(constant_index(aux.word())));
                }
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
            }
            Opcode::ForGPrep | Opcode::ForGPrepInext | Opcode::ForGPrepNext => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.a() + 1)?);
                operands.push(self.producer(blocks, instruction.a() + 2)?);
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
                let variable_count = opcode
                    .is_jump_d()
                    .then(|| stream.graph_jump_target(pc.index()))
                    .flatten()
                    .and_then(|target| stream.aux_word(InstructionPc::new(target as usize)))
                    .map(InstructionAux::a)
                    .unwrap_or_default()
                    .max(2);
                for offset in 0..=variable_count {
                    self.produce_projection(
                        instruction.a() + 2 + offset,
                        id,
                        u32::from(2 + offset),
                    );
                }
            }
            Opcode::ForGLoop => {
                operands.push(self.producer(blocks, instruction.a())?);
                operands.push(self.producer(blocks, instruction.a() + 1)?);
                operands.push(self.producer(blocks, instruction.a() + 2)?);
                operands.push(
                    self.immediate(BytecodeImmediate::Boolean(
                        stream
                            .graph_aux_word(pc)
                            .map(InstructionAux::is_negated)
                            .unwrap_or(false),
                    )),
                );
                let variable_count = stream
                    .graph_aux_word(pc)
                    .map(InstructionAux::a)
                    .unwrap_or_default();
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(variable_count))));
                if let Some(target) = self.jump_target_operand(pc, stream, block_by_start) {
                    operands.push(target);
                }
            }
            Opcode::GetVarargs => {
                operands.push(BytecodeOperand::VmRegister(instruction.a()));
                let count = i32::from(instruction.b()) - 1;
                operands.push(self.immediate(BytecodeImmediate::Int(count)));
                self.registers
                    .insert(BytecodeOperand::Instruction(id), instruction.a());
                if count >= 0 {
                    for offset in 0..count as u8 {
                        self.produce_projection(instruction.a() + offset, id, u32::from(offset));
                    }
                } else {
                    let block_producers = &mut self.producers.blocks[self.current_block.index()];
                    block_producers.multi_return = Some(BytecodeOperand::Instruction(id));
                    block_producers.multi_return_start = instruction.a();
                    block_producers.invalid_after = 255;
                }
            }
            Opcode::PrepVarargs => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.a()))));
            }
            Opcode::Coverage => {
                operands.push(self.immediate(BytecodeImmediate::Int(instruction.e())));
            }
            Opcode::Capture => {
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.a()))));
                if instruction.a() <= 1 {
                    operands.push(self.producer(blocks, instruction.b())?);
                } else {
                    operands.push(BytecodeOperand::VmUpvalue(u32::from(instruction.b())));
                }
                operands.push(self.immediate(BytecodeImmediate::Int(i32::from(instruction.c()))));
            }
            Opcode::CloseUpvals => {
                operands.push(BytecodeOperand::VmRegister(instruction.a()));
            }
            Opcode::Nop | Opcode::Break | Opcode::NativeCall => {}
        }

        Ok(operands)
    }

    pub(super) fn jump_target_operand(
        &self,
        pc: InstructionPc,
        stream: BytecodeInstructionStream<'_>,
        block_by_start: &HashMap<usize, BytecodeBlockId>,
    ) -> Option<BytecodeOperand> {
        let target = stream
            .graph_jump_target(pc.index())
            .filter(|target| *target >= 0)? as usize;
        block_by_start
            .get(&target)
            .copied()
            .map(BytecodeOperand::Block)
    }

    pub(super) fn immediate(&mut self, immediate: BytecodeImmediate) -> BytecodeOperand {
        let index = match self
            .immediates
            .iter()
            .position(|existing| *existing == immediate)
        {
            Some(index) => index,
            None => {
                self.immediates.push(immediate);
                self.immediates.len() - 1
            }
        };
        BytecodeOperand::Immediate(BytecodeImmediateId::new(index))
    }

    pub(super) fn import_immediate(&mut self, value: u32) -> BytecodeOperand {
        self.immediates.push(BytecodeImmediate::Import(value));
        BytecodeOperand::Immediate(BytecodeImmediateId::new(self.immediates.len() - 1))
    }
}