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))
}
}