diff --git a/Cargo.lock b/Cargo.lock index feee725e639..5b91c312964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3367,6 +3367,7 @@ dependencies = [ "sway-core 0.70.3", "sway-error 0.70.3", "sway-features 0.70.3", + "sway-ir 0.70.3", "sway-types 0.70.3", "sway-utils 0.70.3", "sysinfo 0.29.11", diff --git a/forc-pkg/Cargo.toml b/forc-pkg/Cargo.toml index 6d4c799ef76..ebbff769d87 100644 --- a/forc-pkg/Cargo.toml +++ b/forc-pkg/Cargo.toml @@ -37,6 +37,7 @@ serde_with.workspace = true sway-core.workspace = true sway-error.workspace = true sway-features.workspace = true +sway-ir.workspace = true sway-types.workspace = true sway-utils.workspace = true tar.workspace = true diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index 592c3760943..406cbb7cedb 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -47,7 +47,10 @@ use sway_core::{ write_dwarf, BuildTarget, Engines, FinalizedEntry, LspConfig, }; use sway_core::{namespace::Package, Observer}; -use sway_core::{set_bytecode_configurables_offset, DbgGeneration, IrCli, PrintAsm}; +use sway_core::{ + set_bytecode_configurables_offset, BuildConfig, DbgGeneration, IrCli, PanicOccurrences, + PanickingCallOccurrences, PrintAsm, +}; use sway_error::{error::CompileError, handler::Handler, warning::CompileWarning}; use sway_features::ExperimentalFeatures; use sway_types::{Ident, ProgramId, Span, Spanned}; @@ -1940,6 +1943,84 @@ pub fn compile( Ok(compiled_package) } +/// Compiles the given Sway-IR script. +pub fn compile_ir( + ir_file: &Path, + engines: &Engines, + build_config: Option<&BuildConfig>, + source_map: &mut SourceMap, + experimental: ExperimentalFeatures, +) -> Result { + let source = fs::read_to_string(ir_file) + .with_context(|| format!("Failed to read Sway-IR file: {}", ir_file.display()))?; + + let sway_ir = sway_ir::parser::parse( + &source, + engines.se(), + experimental, + build_config + .map(|config| config.backtrace.into()) + .unwrap_or_default(), + )?; + + let handler = Handler::default(); + let asm = sway_core::asm_generation::from_ir::compile_ir_context_to_finalized_asm( + &handler, + &sway_ir, + build_config, + ); + + let fail = |handler: Handler| { + let (errors, warnings, infos) = handler.consume(); + print_on_failure(engines.se(), false, &infos, &warnings, &errors, false); + bail!("Failed to compile {}", ir_file.display()); + }; + + let finalized_asm = match asm { + Err(_) => return fail(handler), + Ok(asm) => asm, + }; + + let mut compiled_asm = sway_core::CompiledAsm { + finalized_asm, + panic_occurrences: PanicOccurrences::default(), + panicking_call_occurrences: PanickingCallOccurrences::default(), + }; + + let entries = compiled_asm + .finalized_asm + .entries + .iter() + .map(|finalized_entry| PkgEntry::from_finalized_entry(finalized_entry, engines)) + .collect::>()?; + + let bytecode_res = sway_core::asm_to_bytecode( + &handler, + &mut compiled_asm, + source_map, + engines.se(), + build_config.unwrap_or(&BuildConfig::dummy_for_asm_generation()), + ); + + let errored = handler.has_errors(); + + let compiled = match bytecode_res { + Ok(compiled) if !errored => compiled, + _ => return fail(handler), + }; + + let (_, _warnings, _infos) = handler.consume(); + // TODO: Print infos and warnings? + // TODO: Set configurables offset metadata if needed. + + let bytecode = BuiltPackageBytecode { + bytes: compiled.bytecode, + entries, + }; + + Ok(bytecode) +} + /// Reports assembly information for a compiled package to an external `dyno` process through `stdout`. fn report_assembly_information( compiled_asm: &sway_core::CompiledAsm, @@ -1982,6 +2063,8 @@ fn report_assembly_information( } } + sway_core::asm_generation::Datum::WordArray(words) => (words.len() * 8) as u64, + sway_core::asm_generation::Datum::Collection(items) => { items.iter().map(calculate_entry_size).sum() } diff --git a/sway-core/src/asm_generation/evm/evm_asm_builder.rs b/sway-core/src/asm_generation/evm/evm_asm_builder.rs index 7ac58a2d4e9..d6be5b966c6 100644 --- a/sway-core/src/asm_generation/evm/evm_asm_builder.rs +++ b/sway-core/src/asm_generation/evm/evm_asm_builder.rs @@ -319,6 +319,11 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> { } => { self.compile_conditional_branch(handler, cond_value, true_block, false_block)? } + InstOp::Switch { + discriminant, + cases, + default, + } => self.compile_switch(handler, instr_val, discriminant, cases, default)?, InstOp::ContractCall { params, coins, @@ -444,6 +449,17 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> { todo!(); } + fn compile_switch( + &mut self, + handler: &Handler, + instr_val: &Value, + discriminant: &Value, + cases: &[(u64, BranchToWithArgs)], + default: &Option, + ) -> Result<(), ErrorEmitted> { + todo!(); + } + fn compile_branch_to_phi_value(&mut self, to_block: &BranchToWithArgs) { todo!(); } diff --git a/sway-core/src/asm_generation/finalized_asm.rs b/sway-core/src/asm_generation/finalized_asm.rs index fd289585ae5..bbc23fede5c 100644 --- a/sway-core/src/asm_generation/finalized_asm.rs +++ b/sway-core/src/asm_generation/finalized_asm.rs @@ -11,6 +11,7 @@ use crate::BuildConfig; use etk_asm::asm::Assembler; use fuel_vm::fuel_asm::{Imm06, Imm12, Imm18, Imm24, Instruction, RegId}; +use itertools::Itertools; use sway_error::error::CompileError; use sway_error::handler::{ErrorEmitted, Handler}; use sway_types::span::Span; @@ -323,6 +324,15 @@ fn to_bytecode_mut( } println!("\""); } + Datum::WordArray(ws) => { + print!(".words as hex ("); + Itertools::intersperse( + ws.iter().map(|w| format!("{:02X?}", w.to_be_bytes())), + ", ".to_string(), + ) + .for_each(|item| print!("{item}")); + println!("), len i{}", ws.len()); + } Datum::Slice(bs) => { print!(".slice as hex ({bs:02X?}), len i{}, as ascii \"", bs.len()); diff --git a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs index e8f76595c03..d1835372fd5 100644 --- a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs @@ -285,56 +285,128 @@ impl AllocatedAbstractInstructionSet { owning_span, comment, }), - Either::Right(org_op) => match org_op { - ControlFlowOp::Jump { to, type_ } => { - let target_offset = label_offsets.get(&to).unwrap().offs; - let ops = if matches!(type_, JumpType::Call) { - compile_call( - data_section, - curr_offset, - target_offset, - far_jump_sizes.get(&op_idx).copied(), - comment, - owning_span, - ) - } else { - compile_jump( - data_section, - curr_offset, - target_offset, - match type_ { - JumpType::NotZero(cond) => Some(cond), - _ => None, - }, - far_jump_sizes.contains_key(&op_idx), - comment, + Either::Right(org_op) => { + match org_op { + ControlFlowOp::Jump { to, type_ } => { + let target_offset = label_offsets.get(&to).unwrap().offs; + let ops = if matches!(type_, JumpType::Call) { + compile_call( + data_section, + curr_offset, + target_offset, + far_jump_sizes.get(&op_idx).copied(), + comment, + owning_span, + ) + } else { + compile_jump( + data_section, + curr_offset, + target_offset, + match type_ { + JumpType::NotZero(cond) => Some(cond), + _ => None, + }, + far_jump_sizes.contains_key(&op_idx), + comment, + owning_span, + ) + }; + debug_assert_eq!(ops.len() as u64, op_size); + realized_ops.extend(ops); + } + ControlFlowOp::Switch { + discriminant, + cases, + } => { + let target_offsets = cases + .iter() + .map(|label| { + let label_offset = label_offsets + .get(label) + .map(|b| b.offs) + .expect("Switch case label not found in label_offsets"); + label_offset.checked_sub(curr_offset).expect( + "Switch case target offset is before switch instruction", + ).checked_sub(op_size).expect("Switch case target offset right at switch instruction") + }) + .collect::>(); + // Insert a data section entry for the switch targets. + let data_id = data_section.insert_data_value(Entry::new_word_array( + target_offsets, + EntryName::NonConfigurable, + None, + )); + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::AddrDataId( + AllocatedRegister::Constant(ConstantRegister::Scratch), + data_id, + ), + owning_span: owning_span.clone(), + comment: "[switch] load switch targets table base address".into(), + }); + // Multiply discriminant by 8 (since each address is 8 bytes) and add to the base address. + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::SLLI( + discriminant.clone(), + discriminant.clone(), + VirtualImmediate12::new(3), + ), + owning_span: owning_span.clone(), + comment: + "[switch] get discriminant's target offset (discriminant * 8)" + .into(), + }); + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::ADD( + AllocatedRegister::Constant(ConstantRegister::Scratch), + discriminant, + AllocatedRegister::Constant(ConstantRegister::Scratch), + ), + owning_span: owning_span.clone(), + comment: "[switch] add discriminant's target offset to targets base address".into(), + }); + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::LW( + AllocatedRegister::Constant(ConstantRegister::Scratch), + AllocatedRegister::Constant(ConstantRegister::Scratch), + VirtualImmediate12::new(0), + ), + owning_span: owning_span.clone(), + comment: "[switch] load discriminant's target address".into(), + }); + // Finally, jump to the loaded address. + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::JMPF( + AllocatedRegister::Constant(ConstantRegister::Scratch), + VirtualImmediate18::new(0), + ), owning_span, - ) - }; - debug_assert_eq!(ops.len() as u64, op_size); - realized_ops.extend(ops); - } - ControlFlowOp::DataSectionOffsetPlaceholder => { - realized_ops.push(RealizedOp { - opcode: AllocatedInstruction::DataSectionOffsetPlaceholder, - owning_span: None, - comment: String::new(), - }); - } - ControlFlowOp::ConfigurablesOffsetPlaceholder => { - realized_ops.push(RealizedOp { - opcode: AllocatedInstruction::ConfigurablesOffsetPlaceholder, - owning_span: None, - comment: String::new(), - }); - } - ControlFlowOp::Comment => continue, - ControlFlowOp::Label(..) => continue, + comment: "[switch] jump to discriminant's target address".into(), + }); + } + ControlFlowOp::DataSectionOffsetPlaceholder => { + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::DataSectionOffsetPlaceholder, + owning_span: None, + comment: String::new(), + }); + } + ControlFlowOp::ConfigurablesOffsetPlaceholder => { + realized_ops.push(RealizedOp { + opcode: AllocatedInstruction::ConfigurablesOffsetPlaceholder, + owning_span: None, + comment: String::new(), + }); + } + ControlFlowOp::Comment => continue, + ControlFlowOp::Label(..) => continue, - ControlFlowOp::PushAll(_) | ControlFlowOp::PopAll(_) => { - unreachable!("still don't belong in organisational ops") + ControlFlowOp::PushAll(_) | ControlFlowOp::PopAll(_) => { + unreachable!("still don't belong in organisational ops") + } } - }, + } }; curr_offset += op_size; } @@ -355,10 +427,10 @@ impl AllocatedAbstractInstructionSet { /// instructions to be added, and so on. /// /// For this reason, we take a two-pass approach. On the first pass, we pessimistically assume - /// that all jumps may require take two opcodes, and use this assumption to calculate the + /// that all jumps may require two opcodes, and use this assumption to calculate the /// offsets of labels. Then we see which jumps actually require two opcodes and mark them as such. /// This approach is not optimal as it sometimes requires more opcodes than necessary, - /// but it is simple and quite works well in practice. + /// but it is simple and works quite well in practice. fn resolve_labels(&mut self, data_section: &mut DataSection) -> LabeledBlocks { let far_jump_indices = self.collect_far_jumps(); self.map_label_offsets(data_section, &far_jump_indices) @@ -397,6 +469,10 @@ impl AllocatedAbstractInstructionSet { JumpType::Call => 3, }, + // A switch expands to AddrDataId (2 opcodes) + scale discriminant by word size (1 opcode) + // + add discriminant (1 opcode) + load (1 opcode) + jump (1 opcode) = 6 opcodes + Either::Right(Switch { .. }) => 6, + Either::Right(Comment) => 0, Either::Right(DataSectionOffsetPlaceholder) => { @@ -454,6 +530,10 @@ impl AllocatedAbstractInstructionSet { // Far jumps must be handled separately, as they require two instructions. Either::Right(Jump { .. }) => 1, + // A switch expands to AddrDataId (2 opcodes) + scale discriminant by word size (1 opcode) + // + add discriminant (1 opcode) + load (1 opcode) + jump (1 opcode) = 6 opcodes + Either::Right(Switch { .. }) => 6, + Either::Right(Comment) => 0, Either::Right(DataSectionOffsetPlaceholder) => { @@ -492,7 +572,10 @@ impl AllocatedAbstractInstructionSet { for (op_idx, op) in self.ops.iter().enumerate() { // If we're seeing a control flow op then it's the end of the block. - if let Either::Right(ControlFlowOp::Label(_) | ControlFlowOp::Jump { .. }) = op.opcode { + if let Either::Right( + ControlFlowOp::Label(_) | ControlFlowOp::Jump { .. } | ControlFlowOp::Switch { .. }, + ) = op.opcode + { if let Some((lab, _idx, offs)) = cur_basic_block { // Insert the previous basic block. labelled_blocks.insert(lab, BasicBlock { offs }); @@ -575,7 +658,10 @@ impl AllocatedAbstractInstructionSet { for (op_idx, op) in self.ops.iter().enumerate() { // If we're seeing a control flow op then it's the end of the block. - if let Either::Right(ControlFlowOp::Label(_) | ControlFlowOp::Jump { .. }) = op.opcode { + if let Either::Right( + ControlFlowOp::Label(_) | ControlFlowOp::Jump { .. } | ControlFlowOp::Switch { .. }, + ) = op.opcode + { if let Some((lab, _idx, offs)) = cur_basic_block { // Insert the previous basic block. labelled_blocks.insert(lab, BasicBlock { offs }); diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index 592d4f54238..41e4a4389a7 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use rustc_hash::FxHashMap; use sway_ir::{ size_bytes_round_up_to_word_alignment, ConstantContent, ConstantValue, Context, Padding, @@ -34,6 +35,7 @@ pub enum Datum { Byte(u8), Word(u64), ByteArray(Vec), + WordArray(Vec), Slice(Vec), Collection(Vec), } @@ -67,6 +69,18 @@ impl Entry { } } + pub(crate) fn new_word_array( + words: Vec, + name: EntryName, + padding: Option, + ) -> Entry { + Entry { + padding: padding.unwrap_or(Padding::default_for_word_array(&words)), + value: Datum::WordArray(words), + name, + } + } + pub(crate) fn new_slice(bytes: Vec, name: EntryName, padding: Option) -> Entry { Entry { padding: padding.unwrap_or(Padding::default_for_byte_array(&bytes)), @@ -173,6 +187,7 @@ impl Entry { .copied() .take((bytes.len() + 7) & 0xfffffff8_usize) .collect(), + Datum::WordArray(words) => words.iter().flat_map(|w| w.to_be_bytes()).collect(), Datum::Collection(items) => items.iter().flat_map(|el| el.to_bytes()).collect(), }; @@ -400,6 +415,7 @@ impl fmt::Display for DataSection { Datum::Word(w) => format!(".word {w}"), Datum::ByteArray(bs) => display_bytes_for_data_section(bs, ".bytes"), Datum::Slice(bs) => display_bytes_for_data_section(bs, ".slice"), + Datum::WordArray(ws) => display_words_for_data_section(ws), Datum::Collection(els) => format!( ".collection {{ {} }}", els.iter() @@ -439,3 +455,11 @@ fn display_bytes_for_data_section(bs: &Vec, prefix: &str) -> String { } format!("{prefix}[{}] {hex_str} {chr_str}", bs.len()) } + +fn display_words_for_data_section(ws: &[u64]) -> String { + let ws_str = String::from_iter(Itertools::intersperse( + ws.iter().map(|w| w.to_string()), + " ".to_string(), + )); + format!(".word_array[{}] {ws_str}", ws.len()) +} diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index 0da9f494842..9aaf3262ef0 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -402,6 +402,11 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { true_block, false_block, } => self.compile_conditional_branch(cond_value, true_block, false_block), + InstOp::Switch { + discriminant, + cases, + default, + } => self.compile_switch(instr_val, discriminant, cases, default), InstOp::ContractCall { params, coins, @@ -1071,6 +1076,180 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { Ok(()) } + fn compile_switch( + &mut self, + instr_val: &Value, + discriminant: &Value, + cases: &[(u64, BranchToWithArgs)], + default: &Option, + ) -> Result<(), CompileError> { + let inst_span = self.md_mgr.val_to_span(self.context, *instr_val); + + let target_blocks = cases + .iter() + .map(|(_, bb)| &bb.block) + .chain(default.as_ref().map(|d| &d.block)); + + // Check if any two target blocks are the same with args. + let mut seen_blocks = rustc_hash::FxHashSet::default(); + for tb in target_blocks { + if !seen_blocks.insert(tb) && tb.num_args(self.context) > 0 { + return Err(CompileError::Internal( + "Cannot compile switch with multiple cases going to same dest block", + inst_span.unwrap_or_else(Span::dummy), + )); + } + } + + if let Some(default) = default { + self.compile_branch_to_phi_value(default)?; + } + for dest_block in cases.iter().map(|(_, bb)| bb) { + self.compile_branch_to_phi_value(dest_block)?; + } + + let default_label = default + .as_ref() + .map(|default| self.block_to_label(&default.block)); + if cases.is_empty() { + // No cases. + if let Some(default_label) = default_label { + // There's a default, just jump to it. + self.cur_bytecode.push(Op::jump_to_label(default_label)); + return Ok(()); + } + // What now? There's no cases and no default, so just do nothing? + panic!("Switch with no cases and no default"); + } + + // Sort the cases by their values to make range checking easier. + let mut sorted_cases = cases.to_vec(); + sorted_cases.sort_by_key(|(val, _)| *val); + + let min_case_value = sorted_cases.first().unwrap().0; + let discrim_reg = self.value_to_register(discriminant)?; + + // If the discriminant is smaller than the lowest case value, jump to default. + // This is only needed for non-exhaustive switches (i.e., there's a default_label). + if let Some(default_label) = default_label { + let cond_reg = self.reg_seqr.next(); + self.immediate_to_reg( + min_case_value, + cond_reg.clone(), + None, + "[switch] get `min_case_value` for range check", + inst_span.clone(), + ); + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::LT( + cond_reg.clone(), + discrim_reg.clone(), + cond_reg.clone(), + )), + comment: "[switch] check if discriminant < `min_case_value`".into(), + owning_span: inst_span.clone(), + }); + self.cur_bytecode.push(Op::jump_if_not_zero_comment( + cond_reg, + default_label, + "[switch] jump to default if discriminant < `min_case_value`", + )); + } + + // If the lowest case value isn't 0, we subtract the discriminant and each + // case value by that amount to make the lowest case 0. + if min_case_value > 0 { + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::SUBI( + discrim_reg.clone(), + discrim_reg.clone(), + VirtualImmediate12::new(min_case_value), + )), + comment: format!("[switch] adjust discriminant by subtracting `min_case_value` of {min_case_value}"), + owning_span: inst_span.clone(), + }); + sorted_cases + .iter_mut() + .for_each(|(val, _)| *val -= min_case_value); + } + + let sorted_cases: Vec<_> = sorted_cases + .into_iter() + .map(|(val, BranchToWithArgs { block, args: _ })| (val, self.block_to_label(&block))) + .collect(); + + assert!( + sorted_cases[0].0 == 0, + "Lowest case value must be zero after adjustment" + ); + + // TODO: Decide on a better limit. + const MAX_CASE_VALUE_LIMIT: u64 = 20; + let max_case_value = sorted_cases.last().unwrap().0; + assert!( + max_case_value < MAX_CASE_VALUE_LIMIT, + "Jump table too large to compile switch. Max case value was {max_case_value}, but the limit is {MAX_CASE_VALUE_LIMIT}." + ); + + // If the discriminant is greater than the highest case value, jump to default. + if let Some(default_label) = default_label { + let cond_reg = self.reg_seqr.next(); + self.immediate_to_reg( + max_case_value, + cond_reg.clone(), + None, + "[switch] get `max_case_value` for range check", + inst_span.clone(), + ); + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::GT( + cond_reg.clone(), + discrim_reg.clone(), + cond_reg.clone(), + )), + comment: "[switch] check if discriminant > `max_case_value`".into(), + owning_span: inst_span.clone(), + }); + self.cur_bytecode.push(Op::jump_if_not_zero_comment( + cond_reg, + default_label, + "[switch] jump to default if discriminant > `max_case_value`", + )); + } + + // For holes in the case values, i.e. non-contiguous case values, + // insert a jump to default for those. + let mut filled_sorted_cases = Vec::new(); + let mut next = 0; + for (case_value, case_label) in sorted_cases { + while next < case_value { + if let Some(default_label) = default_label { + filled_sorted_cases.push(default_label); + } else { + // We have a hole, and there's a no default case to jump to. + panic!("Switch with hole in case values and no default case (i.e., marked exhaustive)"); + } + next += 1; + } + filled_sorted_cases.push(case_label); + next += 1; + } + + // So far we've ensured that + // - The lowest case value is 0 (by subtracting min_case_value) + // - The highest case value is max_case_value (and jump to default if discrim > max) + // - Any holes in the case values jump to default + // Now we can emit the switch instruction. + let num_cases = filled_sorted_cases.len(); + self.cur_bytecode.push(Op::switch_comment( + discrim_reg, + filled_sorted_cases, + format!("[switch] switch to one of {num_cases} case(s)"), + )); + + Ok(()) + } + fn compile_branch_to_phi_value( &mut self, to_block: &BranchToWithArgs, diff --git a/sway-core/src/asm_generation/fuel/optimizations/constant_propagate.rs b/sway-core/src/asm_generation/fuel/optimizations/constant_propagate.rs index 9cc74575677..d5637a23498 100644 --- a/sway-core/src/asm_generation/fuel/optimizations/constant_propagate.rs +++ b/sway-core/src/asm_generation/fuel/optimizations/constant_propagate.rs @@ -217,6 +217,8 @@ impl AbstractInstructionSet { JumpType::Call => ResetKnown::Full, _ => ResetKnown::Defs, }, + // Same as non-call jumps. + ControlFlowOp::Switch { .. } => ResetKnown::Defs, // These ops mark their outputs properly and cause no control-flow effects ControlFlowOp::Comment | ControlFlowOp::ConfigurablesOffsetPlaceholder diff --git a/sway-core/src/asm_lang/mod.rs b/sway-core/src/asm_lang/mod.rs index 8a2f75fd761..6427cd3e536 100644 --- a/sway-core/src/asm_lang/mod.rs +++ b/sway-core/src/asm_lang/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod virtual_immediate; pub(crate) mod virtual_ops; pub(crate) mod virtual_register; use indexmap::IndexMap; +use itertools::Itertools; pub(crate) use virtual_immediate::*; pub(crate) use virtual_ops::*; pub(crate) use virtual_register::*; @@ -281,6 +282,32 @@ impl Op { } } + pub(crate) fn switch(discriminant: VirtualRegister, cases: Vec