Bläddra i källkod

The three-address code format CFG (#1577)

This PR links to the issue#923.

Here's a summary of the tasks related to the new three-address code
format CFG:

1. Define the data structure of the new three-address code format CFG by
using the following files:
  - `src/lir/cfg.rs`: defines the enum type `lir::LIR` and `lir::Block`
- `src/lir/instructions.rs`: defines the enum type
`lir::instructions::Instruction` for instructions
- `src/lir/expressions.rs`: defines the enum type
`lir::expressions::Expression`
  - `src/lir/ssa_type.rs`: defines the enum type `lir::lir_type::Type`

2. Implement a text dump function for the new CFG by using the following
files:
- `src/lir/printer/mod.rs`: defines the struct `lir::printer::Printer`,
which delegates some operations on `lir::vartable::Vartable`
- Other files in the folder `src/lir/printer/`: implement the
`lir::printer::Printer` for the new CFG

3. Convert the old CFG into the new CFG of three-address codes by using
the following files:
- `src/lir/converter/mod.rs`: defines the struct type
`lir::converter::Converter`, which delegates some operations on
`sema::ast::Namespace`
- `src/lir/vartable.rs`: defines the struct type
`lir::vartable::Vartable`, which will later be used in the
`lir::converter::Converter` to mainly keep track of the temporary
identifiers' number, name, and type
- Other files in the folder `src/lir/converter/`: implements the
`lir::converter::Converter`, which converts the
`codegen::cfg::ControlFlowGraph` to the `lir::LIR`

---------

Signed-off-by: FANYI ZHAO <euclideanrn@163.com>
Co-authored-by: Lucas Steuernagel <38472950+LucasSte@users.noreply.github.com>
FANYI ZHAO 1 år sedan
förälder
incheckning
14748311f4

+ 1 - 1
src/abi/anchor.rs

@@ -494,7 +494,7 @@ impl Deduplicate {
         }
     }
 
-    fn unique_name(&mut self, param: &Parameter) -> String {
+    fn unique_name(&mut self, param: &Parameter<Type>) -> String {
         if param.id.is_none() || param.id.as_ref().unwrap().name.is_empty() {
             self.try_prefix()
         } else {

+ 1 - 1
src/abi/ethereum.rs

@@ -54,7 +54,7 @@ impl Type {
 }
 
 pub fn gen_abi(contract_no: usize, ns: &Namespace) -> Vec<ABI> {
-    fn parameter_to_abi(param: &Parameter, ns: &Namespace) -> ABIParam {
+    fn parameter_to_abi(param: &Parameter<Type>, ns: &Namespace) -> ABIParam {
         let components = if let Some(n) = param.ty.is_struct_or_array_of_struct() {
             ns.structs[n]
                 .fields

+ 1 - 1
src/bin/languageserver/mod.rs

@@ -1330,7 +1330,7 @@ impl<'a> Builder<'a> {
     }
 
     // Constructs struct fields and stores it in the lookup table.
-    fn field(&mut self, id: usize, field_id: usize, field: &ast::Parameter) {
+    fn field(&mut self, id: usize, field_id: usize, field: &ast::Parameter<Type>) {
         if let Some(loc) = field.ty_loc {
             if let Some(dt) = get_type_definition(&field.ty) {
                 self.references.push((

+ 2 - 2
src/codegen/cfg.rs

@@ -407,8 +407,8 @@ pub struct BasicBlock {
 pub struct ControlFlowGraph {
     pub name: String,
     pub function_no: ASTFunction,
-    pub params: Arc<Vec<Parameter>>,
-    pub returns: Arc<Vec<Parameter>>,
+    pub params: Arc<Vec<Parameter<Type>>>,
+    pub returns: Arc<Vec<Parameter<Type>>>,
     pub vars: Vars,
     pub blocks: Vec<BasicBlock>,
     pub nonpayable: bool,

+ 1 - 0
src/lib.rs

@@ -12,6 +12,7 @@ pub mod standard_json;
 // In Sema, we use result unit for returning early
 // when code-misparses. The error will be added to the namespace diagnostics, no need to have anything but unit
 // as error.
+pub mod lir;
 pub mod sema;
 
 use file_resolver::FileResolver;

+ 1010 - 0
src/lir/converter/expression.rs

@@ -0,0 +1,1010 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use core::panic;
+
+use solang_parser::pt::Loc;
+
+use crate::codegen;
+use crate::lir::converter::Converter;
+use crate::lir::expressions::{BinaryOperator, Expression, Operand, UnaryOperator};
+use crate::lir::instructions::Instruction;
+use crate::lir::vartable::Vartable;
+use crate::sema::ast;
+
+impl Converter<'_> {
+    /**
+    <pre>
+    This function lowers an expression (tree) into a list of three-address code
+    instructions and assign the last expression with maxmum two operands to the
+    <code>dest</code> operand.
+
+    For example, suppose <code>a</code> and <code>b</code> and <code>c</code> all have <code>int32</code> type in the <code>Vartable</code>,
+    the expression <code>a + b + c</code> will be lowered into the following:
+    <code>int32 temp1 = a + b</code>
+    <code>int32 dest = temp1 + c</code>
+
+    Input:
+    <li><code>dest</code>: the destination operand for the last expression
+    <li><code>expr</code>: the expression to be lowered
+    <li><code>vartable</code>: the variable table storing all the variables and their types
+    <li><code>results</code>: the list of instructions to be appended with the lowered instructions
+    </pre>
+    */
+    pub(crate) fn lower_expression(
+        &self,
+        dest: &Operand,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        match expr {
+            codegen::Expression::Add {
+                loc,
+                ty,
+                overflowing,
+                left,
+                right,
+            } => {
+                let operator = BinaryOperator::Add {
+                    overflowing: *overflowing,
+                };
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::AllocDynamicBytes {
+                loc,
+                ty,
+                size,
+                initializer,
+                ..
+            } => self.alloc_dynamic_bytes(dest, loc, ty, size, initializer, vartable, results),
+            codegen::Expression::ArrayLiteral {
+                loc,
+                ty,
+                dimensions,
+                values,
+                ..
+            } => self.array_literal(dest, loc, ty, dimensions, values, vartable, results),
+            codegen::Expression::BitwiseAnd {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::BitAnd;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::BitwiseOr {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::BitOr;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::BitwiseXor {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::BitXor;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::BoolLiteral { loc, value, .. } => {
+                self.bool_literal(dest, loc, value, results)
+            }
+            codegen::Expression::Builtin {
+                loc, kind, args, ..
+            } => self.builtin(dest, loc, kind, args, vartable, results),
+            codegen::Expression::BytesCast { loc, expr, ty, .. } => {
+                self.byte_cast(dest, loc, expr, ty, vartable, results)
+            }
+            codegen::Expression::BytesLiteral { loc, ty, value, .. } => {
+                self.bytes_literal(dest, loc, ty, value, results)
+            }
+            codegen::Expression::Cast { loc, ty, expr, .. } => {
+                self.cast(dest, loc, ty, expr, vartable, results)
+            }
+            codegen::Expression::BitwiseNot { loc, expr, .. } => {
+                let operator = UnaryOperator::BitNot;
+                self.unary_operation(dest, loc, operator, expr, vartable, results)
+            }
+            codegen::Expression::ConstArrayLiteral {
+                loc,
+                ty,
+                dimensions,
+                values,
+                ..
+            } => self.const_array_literal(dest, loc, ty, dimensions, values, vartable, results),
+            codegen::Expression::UnsignedDivide {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::UDiv;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::SignedDivide {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::Div;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::Equal {
+                loc, left, right, ..
+            } => {
+                let operator = BinaryOperator::Eq;
+                self.binary_operation(
+                    dest,
+                    loc,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::FormatString { loc, args, .. } => {
+                self.format_string(dest, loc, args, vartable, results)
+            }
+            codegen::Expression::FunctionArg {
+                loc, ty, arg_no, ..
+            } => self.function_arg(dest, loc, ty, arg_no, vartable, results),
+            codegen::Expression::GetRef { loc, expr, .. } => {
+                self.get_ref(dest, loc, expr, vartable, results)
+            }
+            codegen::Expression::InternalFunctionCfg { cfg_no, .. } => {
+                self.internal_function_cfg(dest, cfg_no, results)
+            }
+            codegen::Expression::Keccak256 { loc, exprs, .. } => {
+                self.keccak256(dest, loc, exprs, vartable, results)
+            }
+            codegen::Expression::Less {
+                loc,
+                left,
+                right,
+                signed,
+                ..
+            } => {
+                let operator = if *signed {
+                    BinaryOperator::Lt
+                } else {
+                    BinaryOperator::ULt
+                };
+                self.binary_operation(
+                    dest,
+                    loc,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::LessEqual {
+                loc,
+                left,
+                right,
+                signed,
+                ..
+            } => {
+                let operator = if *signed {
+                    BinaryOperator::Lt
+                } else {
+                    BinaryOperator::ULt
+                };
+                self.binary_operation(
+                    dest,
+                    loc,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::Load { loc, expr, .. } => {
+                self.load(dest, loc, expr, vartable, results)
+            }
+            codegen::Expression::UnsignedModulo {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::UMod;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::SignedModulo {
+                loc,
+                left,
+                right,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::Mod;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::More {
+                left,
+                right,
+                signed,
+                ..
+            } => {
+                let operator = if *signed {
+                    BinaryOperator::Gt
+                } else {
+                    BinaryOperator::UGt
+                };
+                self.binary_operation(
+                    dest,
+                    &Loc::Codegen,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::MoreEqual {
+                left,
+                right,
+                signed,
+                ..
+            } => {
+                let operator = if *signed {
+                    BinaryOperator::Gte
+                } else {
+                    BinaryOperator::UGte
+                };
+                self.binary_operation(
+                    dest,
+                    &Loc::Codegen,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::Multiply {
+                loc,
+                left,
+                right,
+                overflowing,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::Mul {
+                    overflowing: *overflowing,
+                };
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::Not { loc, expr, .. } => {
+                let operator = UnaryOperator::Not;
+                self.unary_operation(dest, loc, operator, expr, vartable, results)
+            }
+            codegen::Expression::NotEqual {
+                loc, left, right, ..
+            } => {
+                let operator = BinaryOperator::Neq;
+                self.binary_operation(
+                    dest,
+                    loc,
+                    &ast::Type::Bool,
+                    operator,
+                    left,
+                    right,
+                    vartable,
+                    results,
+                )
+            }
+            codegen::Expression::NumberLiteral { loc, value, .. } => {
+                self.number_literal(dest, loc, value, results)
+            }
+            codegen::Expression::Poison => panic!("Poison expression shouldn't be here"),
+            codegen::Expression::Power {
+                loc,
+                base,
+                exp,
+                overflowing,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::Pow {
+                    overflowing: *overflowing,
+                };
+                self.binary_operation(dest, loc, ty, operator, base, exp, vartable, results)
+            }
+            codegen::Expression::RationalNumberLiteral { .. } => {
+                panic!("RationalNumberLiteral shouldn't be here")
+            }
+            codegen::Expression::ReturnData { loc, .. } => self.return_data(dest, loc, results),
+            codegen::Expression::SignExt { loc, ty, expr, .. } => {
+                self.sign_ext(dest, loc, ty, expr, vartable, results)
+            }
+            codegen::Expression::ShiftLeft {
+                loc,
+                ty,
+                left,
+                right,
+                ..
+            } => {
+                let operator = BinaryOperator::Shl;
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::ShiftRight {
+                loc,
+                left,
+                right,
+                signed,
+                ty,
+                ..
+            } => {
+                let operator = if *signed {
+                    BinaryOperator::Shr
+                } else {
+                    BinaryOperator::UShr
+                };
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::StorageArrayLength { loc, array, .. } => {
+                self.storage_array_length(dest, loc, array, vartable, results)
+            }
+            codegen::Expression::StringCompare {
+                loc, left, right, ..
+            } => self.string_compare(dest, loc, left, right, vartable, results),
+            codegen::Expression::StructLiteral {
+                loc, ty, values, ..
+            } => self.struct_literal(dest, loc, ty, values, vartable, results),
+            codegen::Expression::StructMember {
+                loc, expr, member, ..
+            } => self.struct_member(dest, loc, expr, member, vartable, results),
+            codegen::Expression::Subscript {
+                loc, expr, index, ..
+            } => self.subscript(dest, loc, expr, index, vartable, results),
+            codegen::Expression::Subtract {
+                loc,
+                left,
+                right,
+                overflowing,
+                ty,
+                ..
+            } => {
+                let operator = BinaryOperator::Sub {
+                    overflowing: *overflowing,
+                };
+                self.binary_operation(dest, loc, ty, operator, left, right, vartable, results)
+            }
+            codegen::Expression::Trunc { loc, ty, expr, .. } => {
+                self.trunc(dest, loc, ty, expr, vartable, results)
+            }
+            codegen::Expression::Negate {
+                loc,
+                expr,
+                overflowing,
+                ..
+            } => {
+                let operator = UnaryOperator::Neg {
+                    overflowing: *overflowing,
+                };
+                self.unary_operation(dest, loc, operator, expr, vartable, results)
+            }
+            codegen::Expression::Undefined { .. } => {
+                panic!("Undefined expression shouldn't be here")
+            }
+            codegen::Expression::Variable { loc, var_no, .. } => {
+                self.variable(dest, loc, var_no, results)
+            }
+            codegen::Expression::ZeroExt { loc, ty, expr, .. } => {
+                self.zero_ext(dest, loc, ty, expr, vartable, results)
+            }
+            codegen::Expression::AdvancePointer {
+                pointer,
+                bytes_offset,
+                ..
+            } => self.advance_pointer(dest, pointer, bytes_offset, vartable, results),
+        }
+    }
+
+    fn advance_pointer(
+        &self,
+        dest: &Operand,
+        pointer: &codegen::Expression,
+        bytes_offset: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let pointer_op = self.to_operand_and_insns(pointer, vartable, results);
+        let bytes_offset_op = self.to_operand_and_insns(bytes_offset, vartable, results);
+        results.push(Instruction::Set {
+            loc: Loc::Codegen,
+            res: dest.get_id_or_error(),
+            expr: Expression::AdvancePointer {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                pointer: Box::new(pointer_op),
+                bytes_offset: Box::new(bytes_offset_op),
+            },
+        });
+    }
+
+    fn zero_ext(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::ZeroExt {
+                loc: *loc,
+                operand: Box::new(from_op),
+                to_ty: self.lower_ast_type(ty),
+            },
+        });
+    }
+
+    fn trunc(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Trunc {
+                loc: *loc,
+                operand: Box::new(from_op),
+                to_ty: self.lower_ast_type(ty),
+            },
+        });
+    }
+
+    fn subscript(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        expr: &codegen::Expression,
+        index: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let array_op = self.to_operand_and_insns(expr, vartable, results);
+        let index_op = self.to_operand_and_insns(index, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Subscript {
+                loc: *loc,
+                arr: Box::new(array_op),
+                index: Box::new(index_op),
+            },
+        });
+    }
+
+    fn struct_member(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        expr: &codegen::Expression,
+        member: &usize,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let struct_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::StructMember {
+                loc: *loc,
+                operand: Box::new(struct_op),
+                member: *member,
+            },
+        });
+    }
+
+    fn struct_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        values: &[codegen::Expression],
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let value_ops = values
+            .iter()
+            .map(|value| self.to_operand_and_insns(value, vartable, results))
+            .collect::<Vec<Operand>>();
+
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::StructLiteral {
+                loc: *loc,
+                ty: self.lower_ast_type(ty),
+                values: value_ops,
+            },
+        });
+    }
+
+    fn string_compare(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        left: &ast::StringLocation<codegen::Expression>,
+        right: &ast::StringLocation<codegen::Expression>,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let left_string_loc = self.to_string_location_and_insns(left, vartable, results);
+        let right_string_loc = self.to_string_location_and_insns(right, vartable, results);
+
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::StringCompare {
+                loc: *loc,
+                left: left_string_loc,
+                right: right_string_loc,
+            },
+        });
+    }
+
+    fn storage_array_length(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        array: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let array_op = self.to_operand_and_insns(array, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::StorageArrayLength {
+                loc: *loc,
+                array: Box::new(array_op),
+            },
+        });
+    }
+
+    fn sign_ext(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let tmp = self.to_operand_and_insns(expr, vartable, results);
+        let sext = Expression::SignExt {
+            loc: *loc,
+            operand: Box::new(tmp),
+            to_ty: self.lower_ast_type(ty),
+        };
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: sext,
+        });
+    }
+
+    fn load(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Load {
+                loc: *loc,
+                operand: Box::new(from_op),
+            },
+        });
+    }
+
+    fn keccak256(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        exprs: &Vec<codegen::Expression>,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let mut expr_ops = vec![];
+        for expr in exprs {
+            let op = self.to_operand_and_insns(expr, vartable, results);
+            expr_ops.push(op);
+        }
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Keccak256 {
+                loc: *loc,
+                args: expr_ops,
+            },
+        });
+    }
+
+    fn get_ref(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::GetRef {
+                loc: *loc,
+                operand: Box::new(from_op),
+            },
+        });
+    }
+
+    fn function_arg(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        arg_no: &usize,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let arg_ty = self.lower_ast_type(ty);
+        let expr = Expression::FunctionArg {
+            loc: *loc,
+            ty: arg_ty,
+            arg_no: *arg_no,
+        };
+        let res = dest.get_id_or_error();
+        vartable.add_function_arg(*arg_no, res);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res,
+            expr,
+        });
+    }
+
+    fn format_string(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        args: &Vec<(ast::FormatArg, codegen::Expression)>,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let mut arg_ops = vec![];
+        for (format, arg) in args {
+            let op = self.to_operand_and_insns(arg, vartable, results);
+            arg_ops.push((*format, op));
+        }
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::FormatString {
+                loc: *loc,
+                args: arg_ops,
+            },
+        });
+    }
+
+    fn const_array_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        dimensions: &[u32],
+        values: &[codegen::Expression],
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let value_ops = values
+            .iter()
+            .map(|value| self.to_operand_and_insns(value, vartable, results))
+            .collect::<Vec<Operand>>();
+
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::ConstArrayLiteral {
+                loc: *loc,
+                ty: self.lower_ast_type(ty),
+                dimensions: dimensions.to_owned(),
+                values: value_ops,
+            },
+        });
+    }
+
+    fn cast(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Cast {
+                loc: *loc,
+                operand: Box::new(from_op),
+                to_ty: self.lower_ast_type(ty),
+            },
+        });
+    }
+
+    fn byte_cast(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        expr: &codegen::Expression,
+        ty: &ast::Type,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let from_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::BytesCast {
+                loc: *loc,
+                operand: Box::new(from_op),
+                to_ty: self.lower_ast_type(ty),
+            },
+        });
+    }
+
+    fn builtin(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        kind: &crate::codegen::Builtin,
+        args: &Vec<codegen::Expression>,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let mut arg_ops = vec![];
+        for arg in args {
+            let op = self.to_operand_and_insns(arg, vartable, results);
+            arg_ops.push(op);
+        }
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::Builtin {
+                loc: *loc,
+                kind: *kind,
+                args: arg_ops,
+            },
+        });
+    }
+
+    fn binary_operation(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        _: &ast::Type,
+        operator: BinaryOperator,
+        left: &codegen::Expression,
+        right: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let left_op = self.to_operand_and_insns(left, vartable, results);
+        let right_op = self.to_operand_and_insns(right, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::BinaryExpr {
+                loc: *loc,
+                operator,
+                left: Box::new(left_op),
+                right: Box::new(right_op),
+            },
+        });
+    }
+
+    fn unary_operation(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        operator: UnaryOperator,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let expr_op = self.to_operand_and_insns(expr, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::UnaryExpr {
+                loc: *loc,
+                operator,
+                right: Box::new(expr_op),
+            },
+        });
+    }
+
+    fn alloc_dynamic_bytes(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        size: &codegen::Expression,
+        initializer: &Option<Vec<u8>>,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let size_op = self.to_operand_and_insns(size, vartable, results);
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::AllocDynamicBytes {
+                loc: *loc,
+                ty: self.lower_ast_type(ty),
+                size: Box::new(size_op),
+                initializer: initializer.clone(),
+            },
+        });
+    }
+
+    fn bytes_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        value: &[u8],
+        results: &mut Vec<Instruction>,
+    ) {
+        let expr = Expression::BytesLiteral {
+            loc: *loc,
+            ty: self.lower_ast_type(ty),
+            value: value.to_owned(),
+        };
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr,
+        });
+    }
+
+    fn bool_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        value: &bool,
+        results: &mut Vec<Instruction>,
+    ) {
+        let expr = Expression::BoolLiteral {
+            loc: *loc,
+            value: *value,
+        };
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr,
+        });
+    }
+
+    fn number_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        value: &num_bigint::BigInt,
+        results: &mut Vec<Instruction>,
+    ) {
+        results.push(
+            // assign the constant value to the destination
+            Instruction::Set {
+                loc: *loc,
+                res: dest.get_id_or_error(),
+                expr: Expression::NumberLiteral {
+                    loc: *loc,
+                    value: value.clone(),
+                },
+            },
+        );
+    }
+
+    fn array_literal(
+        &self,
+        dest: &Operand,
+        loc: &Loc,
+        ty: &ast::Type,
+        dimensions: &[u32],
+        values: &[codegen::Expression],
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        let value_ops = values
+            .iter()
+            .map(|value| self.to_operand_and_insns(value, vartable, results))
+            .collect::<Vec<Operand>>();
+
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::ArrayLiteral {
+                loc: *loc,
+                ty: self.lower_ast_type(ty),
+                dimensions: dimensions.to_owned(),
+                values: value_ops,
+            },
+        });
+    }
+
+    fn internal_function_cfg(
+        &self,
+        dest: &Operand,
+        cfg_no: &usize,
+        results: &mut Vec<Instruction>,
+    ) {
+        let expr = Expression::InternalFunctionCfg { loc: /*missing from cfg*/ Loc::Codegen, cfg_no: *cfg_no };
+        results.push(Instruction::Set {
+            loc: Loc::Codegen,
+            res: dest.get_id_or_error(),
+            expr,
+        });
+    }
+
+    fn variable(&self, dest: &Operand, loc: &Loc, var_no: &usize, results: &mut Vec<Instruction>) {
+        let expr = Expression::Id {
+            loc: *loc,
+            id: *var_no,
+        };
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr,
+        });
+    }
+
+    fn return_data(&self, dest: &Operand, loc: &Loc, results: &mut Vec<Instruction>) {
+        results.push(Instruction::Set {
+            loc: *loc,
+            res: dest.get_id_or_error(),
+            expr: Expression::ReturnData { loc: *loc },
+        });
+    }
+}

+ 380 - 0
src/lir/converter/instruction.rs

@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use solang_parser::pt::Loc;
+
+use crate::codegen::cfg::Instr;
+use crate::lir::converter::Converter;
+use crate::lir::expressions::Operand;
+use crate::lir::instructions::Instruction;
+use crate::lir::vartable::Vartable;
+
+impl Converter<'_> {
+    /// lower the `codegen::cfg::Instr` into a list of `Instruction`s.
+    /// Input:
+    /// - `instr`: the `codegen::cfg::Instr` to be lowered.
+    /// - `vartable`: the `Vartable` that stores the variables and their types.
+    /// - `results`: the list of `Instruction`s that the lowered instructions will be appended to.
+    pub(crate) fn lower_instr(
+        &self,
+        instr: &Instr,
+        vartable: &mut Vartable,
+        results: &mut Vec<Instruction>,
+    ) {
+        match instr {
+            Instr::Nop => {
+                results.push(Instruction::Nop);
+            }
+            Instr::Set { res, expr, loc, .. } => {
+                // [t] a = b + c * d
+                // converts to:
+                //   1. [t1] tmp_1 = c * d;
+                //   2. [t2] tmp_2 = b + tmp_1
+                //   3. [t] a = tmp_2;
+                let dest_operand = vartable.get_operand(res, *loc);
+                self.lower_expression(&dest_operand, expr, vartable, results);
+            }
+            Instr::Store { dest, data } => {
+                // type checking the dest.ty() and data.ty()
+                let dest_op = self.to_operand_and_insns(dest, vartable, results);
+                let data_op = self.to_operand_and_insns(data, vartable, results);
+                results.push(Instruction::Store {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    dest: dest_op,
+                    data: data_op,
+                });
+            }
+            Instr::PushMemory {
+                res, array, value, ..
+            } => {
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                results.push(Instruction::PushMemory {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: *res,
+                    array: *array,
+                    value: value_op,
+                });
+            }
+            Instr::PopMemory {
+                res, array, loc, ..
+            } => {
+                results.push(Instruction::PopMemory {
+                    res: *res,
+                    array: *array,
+                    loc: *loc,
+                });
+            }
+
+            Instr::Branch { block } => {
+                results.push(Instruction::Branch {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    block: *block,
+                });
+            }
+            Instr::BranchCond {
+                cond,
+                true_block,
+                false_block,
+            } => {
+                let cond_op = self.to_operand_and_insns(cond, vartable, results);
+                results.push(Instruction::BranchCond {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    cond: cond_op,
+                    true_block: *true_block,
+                    false_block: *false_block,
+                });
+            }
+            Instr::Return { value } => {
+                let operands = value
+                    .iter()
+                    .map(|v| self.to_operand_and_insns(v, vartable, results))
+                    .collect::<Vec<Operand>>();
+                results.push(Instruction::Return {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    value: operands,
+                });
+            }
+            Instr::AssertFailure { encoded_args } => match encoded_args {
+                Some(args) => {
+                    let tmp = self.to_operand_and_insns(args, vartable, results);
+                    results.push(Instruction::AssertFailure {
+                        loc: /*missing from cfg*/ Loc::Codegen,
+                        encoded_args: Some(tmp),
+                    });
+                }
+                None => {
+                    results.push(Instruction::AssertFailure {
+                        loc: /*missing from cfg*/ Loc::Codegen,
+                        encoded_args: None,
+                    });
+                }
+            },
+            Instr::Call {
+                res, call, args, ..
+            } => {
+                // resolve the function
+                let callty = self.to_internal_call_ty_and_insns(call, vartable, results);
+
+                // resolve the arguments
+                let arg_ops = args
+                    .iter()
+                    .map(|arg| self.to_operand_and_insns(arg, vartable, results))
+                    .collect::<Vec<Operand>>();
+
+                results.push(Instruction::Call {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: res.clone(),
+                    call: callty,
+                    args: arg_ops,
+                });
+            }
+            Instr::Print { expr } => {
+                let tmp = self.to_operand_and_insns(expr, vartable, results);
+                results.push(Instruction::Print {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    operand: tmp,
+                });
+            }
+            Instr::LoadStorage { res, storage, .. } => {
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                results.push(Instruction::LoadStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: *res,
+                    storage: storage_op,
+                });
+            }
+            Instr::ClearStorage { storage, .. } => {
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                results.push(Instruction::ClearStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    storage: storage_op,
+                });
+            }
+            Instr::SetStorage { value, storage, .. } => {
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                results.push(Instruction::SetStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    value: value_op,
+                    storage: storage_op,
+                });
+            }
+            Instr::SetStorageBytes {
+                value,
+                storage,
+                offset,
+            } => {
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                let offset_op = self.to_operand_and_insns(offset, vartable, results);
+                results.push(Instruction::SetStorageBytes {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    value: value_op,
+                    storage: storage_op,
+                    offset: offset_op,
+                });
+            }
+            Instr::PushStorage {
+                res,
+                value,
+                storage,
+                ..
+            } => {
+                let value_op = self.to_operand_option_and_insns(value, vartable, results);
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                results.push(Instruction::PushStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: *res,
+                    value: value_op,
+                    storage: storage_op,
+                });
+            }
+            Instr::PopStorage { res, storage, .. } => {
+                let storage_op = self.to_operand_and_insns(storage, vartable, results);
+                results.push(Instruction::PopStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: *res,
+                    storage: storage_op,
+                });
+            }
+            Instr::ExternalCall {
+                loc,
+                success,
+                address,
+                accounts,
+                seeds,
+                payload,
+                value,
+                gas,
+                callty,
+                contract_function_no,
+                flags,
+            } => {
+                let address_op = self.to_operand_option_and_insns(address, vartable, results);
+                let accounts_op =
+                    self.to_external_call_accounts_and_insns(accounts, vartable, results);
+                let seeds_op = self.to_operand_option_and_insns(seeds, vartable, results);
+                let payload_op = self.to_operand_and_insns(payload, vartable, results);
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                let gas_op = self.to_operand_and_insns(gas, vartable, results);
+                let flags_op = self.to_operand_option_and_insns(flags, vartable, results);
+
+                results.push(Instruction::ExternalCall {
+                    loc: *loc,
+                    success: *success,
+                    address: address_op,
+                    accounts: accounts_op,
+                    seeds: seeds_op,
+                    payload: payload_op,
+                    value: value_op,
+                    gas: gas_op,
+                    callty: callty.clone(),
+                    contract_function_no: *contract_function_no,
+                    flags: flags_op,
+                });
+            }
+            Instr::ValueTransfer {
+                success,
+                address,
+                value,
+            } => {
+                let address_op = self.to_operand_and_insns(address, vartable, results);
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                results.push(Instruction::ValueTransfer {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    success: *success,
+                    address: address_op,
+                    value: value_op,
+                });
+            }
+            Instr::SelfDestruct { recipient } => {
+                let tmp = self.to_operand_and_insns(recipient, vartable, results);
+                results.push(Instruction::SelfDestruct {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    recipient: tmp,
+                });
+            }
+            Instr::EmitEvent {
+                event_no,
+                data,
+                topics,
+            } => {
+                let data_op = self.to_operand_and_insns(data, vartable, results);
+                let topic_ops = topics
+                    .iter()
+                    .map(|topic| self.to_operand_and_insns(topic, vartable, results))
+                    .collect::<Vec<Operand>>();
+                results.push(Instruction::EmitEvent {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    event_no: *event_no,
+                    data: data_op,
+                    topics: topic_ops,
+                });
+            }
+            Instr::WriteBuffer { buf, offset, value } => {
+                let buf_op = self.to_operand_and_insns(buf, vartable, results);
+                let offset_op = self.to_operand_and_insns(offset, vartable, results);
+                let value_op = self.to_operand_and_insns(value, vartable, results);
+                results.push(Instruction::WriteBuffer {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    buf: buf_op,
+                    offset: offset_op,
+                    value: value_op,
+                });
+            }
+            Instr::MemCopy {
+                source,
+                destination,
+                bytes,
+            } => {
+                let source_op = self.to_operand_and_insns(source, vartable, results);
+                let dest_op = self.to_operand_and_insns(destination, vartable, results);
+                let bytes_op = self.to_operand_and_insns(bytes, vartable, results);
+                results.push(Instruction::MemCopy {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    src: source_op,
+                    dest: dest_op,
+                    bytes: bytes_op,
+                });
+            }
+            Instr::Switch {
+                cond,
+                cases,
+                default,
+            } => {
+                let cond_op = self.to_operand_and_insns(cond, vartable, results);
+
+                let case_ops = cases
+                    .iter()
+                    .map(|(case, block_no)| {
+                        let case_op = self.to_operand_and_insns(case, vartable, results);
+                        (case_op, *block_no)
+                    })
+                    .collect::<Vec<(Operand, usize)>>();
+
+                results.push(Instruction::Switch {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    cond: cond_op,
+                    cases: case_ops,
+                    default: *default,
+                });
+            }
+            Instr::ReturnData { data, data_len } => {
+                let data_op = self.to_operand_and_insns(data, vartable, results);
+                let data_len_op = self.to_operand_and_insns(data_len, vartable, results);
+                results.push(Instruction::ReturnData {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    data: data_op,
+                    data_len: data_len_op,
+                });
+            }
+            Instr::ReturnCode { code } => {
+                results.push(Instruction::ReturnCode {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    code: code.clone(),
+                });
+            }
+            Instr::Unimplemented { .. } => unreachable!("Unimplemented should be removed"),
+            Instr::AccountAccess { .. } => {
+                unreachable!("AccountAccess should be replaced by Subscript")
+            }
+            Instr::Constructor {
+                success,
+                res,
+                contract_no,
+                constructor_no,
+                encoded_args,
+                value,
+                gas,
+                salt,
+                address,
+                seeds,
+                accounts,
+                loc,
+            } => {
+                let args_op = self.to_operand_and_insns(encoded_args, vartable, results);
+                let value_op = self.to_operand_option_and_insns(value, vartable, results);
+                let gas_op = self.to_operand_and_insns(gas, vartable, results);
+                let salt_op = self.to_operand_option_and_insns(salt, vartable, results);
+                let address_op = self.to_operand_option_and_insns(address, vartable, results);
+                let seeds_op = self.to_operand_option_and_insns(seeds, vartable, results);
+                let accounts =
+                    self.to_external_call_accounts_and_insns(accounts, vartable, results);
+
+                results.push(Instruction::Constructor {
+                    loc: *loc,
+                    success: *success,
+                    res: *res,
+                    contract_no: *contract_no,
+                    constructor_no: *constructor_no,
+                    encoded_args: args_op,
+                    value: value_op,
+                    gas: gas_op,
+                    salt: salt_op,
+                    address: address_op,
+                    seeds: seeds_op,
+                    accounts,
+                });
+            }
+        }
+    }
+}

+ 115 - 0
src/lir/converter/lir_type.rs

@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: Apache-2.0
+use super::Converter;
+use num_bigint::BigInt;
+
+use crate::lir::lir_type::{LIRType, StructType, Type};
+use crate::sema::ast::{self, ArrayLength};
+
+impl Converter<'_> {
+    /// lower the `ast::Type` into a `lir::lir_type::LIRType`.
+    pub fn lower_ast_type(&self, ty: &ast::Type) -> LIRType {
+        LIRType {
+            ast_type: ty.clone(),
+            lir_type: self.lower_ast_type_by_depth(ty, 0),
+        }
+    }
+
+    fn lower_ast_type_by_depth(&self, ty: &ast::Type, depth: u8) -> Type {
+        match ty {
+            ast::Type::Bool => Type::Bool,
+            ast::Type::Int(width) => Type::Int(*width),
+            ast::Type::Uint(width) => Type::Uint(*width),
+            ast::Type::Value => Type::Uint(self.value_length() as u16 * 8),
+            ast::Type::Address(_) | ast::Type::Contract(_) => Type::Array(
+                Box::new(Type::Uint(8)),
+                vec![ArrayLength::Fixed(BigInt::from(self.address_length()))],
+            ),
+            ast::Type::Bytes(width) => Type::Bytes(*width),
+            // String is equivalent to dynamic bytes
+            ast::Type::String | ast::Type::DynamicBytes => self.wrap_ptr_by_depth(
+                Type::Struct(StructType::Vector(Box::new(Type::Uint(8)))),
+                depth,
+            ),
+            ast::Type::Array(ty, len) => {
+                let ty = self.lower_ast_type_by_depth(ty.as_ref(), depth + 1);
+                let len = len
+                    .iter()
+                    .map(|len| match len {
+                        ast::ArrayLength::Fixed(len) => ArrayLength::Fixed(len.clone()),
+                        ast::ArrayLength::Dynamic => ArrayLength::Dynamic,
+                        ast::ArrayLength::AnyFixed => unreachable!(),
+                    })
+                    .collect();
+                self.wrap_ptr_by_depth(Type::Array(Box::new(ty), len), depth)
+            }
+            ast::Type::Enum(enum_no) => self.lower_enum_type(*enum_no),
+            ast::Type::Struct(struct_ty) => {
+                self.wrap_ptr_by_depth(Type::Struct(StructType::from(struct_ty)), depth)
+            }
+            ast::Type::Mapping(mapping) => {
+                let key = self.lower_ast_type_by_depth(&mapping.key, depth + 1);
+                let value = self.lower_ast_type_by_depth(&mapping.value, depth + 1);
+                Type::Mapping {
+                    key_ty: Box::new(key),
+                    value_ty: Box::new(value),
+                }
+            }
+            ast::Type::Ref(rty) => {
+                let ty = self.lower_ast_type_by_depth(rty.as_ref(), depth + 1);
+                Type::Ptr(Box::new(ty))
+            }
+            ast::Type::StorageRef(immutable, ty) => {
+                let ty = self.lower_ast_type_by_depth(ty.as_ref(), depth + 1);
+                Type::StoragePtr(*immutable, Box::new(ty))
+            }
+            ast::Type::BufferPointer => self.wrap_ptr_by_depth(Type::Uint(8), depth),
+            ast::Type::ExternalFunction { .. } => {
+                self.wrap_ptr_by_depth(Type::Struct(StructType::ExternalFunction), depth)
+            }
+            ast::Type::InternalFunction {
+                params, returns, ..
+            } => {
+                let params = params
+                    .iter()
+                    .map(|param| self.lower_ast_type_by_depth(param, depth + 1))
+                    .collect::<Vec<_>>();
+                let returns = returns
+                    .iter()
+                    .map(|ret| self.lower_ast_type_by_depth(ret, depth + 1))
+                    .collect::<Vec<_>>();
+                self.wrap_ptr_by_depth(Type::Function { params, returns }, depth)
+            }
+            ast::Type::UserType(_) => self.lower_user_type(ty),
+            ast::Type::Slice(ty) => {
+                let ty = self.lower_ast_type_by_depth(ty.as_ref(), depth + 1);
+                self.wrap_ptr_by_depth(Type::Slice(Box::new(ty)), depth)
+            }
+            ast::Type::FunctionSelector => Type::Uint(self.fn_selector_length() as u16 * 8),
+            ast::Type::Rational => unreachable!(),
+            ast::Type::Void => unreachable!(),
+            ast::Type::Unreachable => unreachable!(),
+            ast::Type::Unresolved => unreachable!(),
+        }
+    }
+
+    /// This function is used when only the first level of the type should be wrapped by a pointer.
+    fn wrap_ptr_by_depth(&self, ty: Type, depth: u8) -> Type {
+        if depth == 0 {
+            Type::Ptr(Box::new(ty))
+        } else {
+            ty
+        }
+    }
+
+    /// retrieve the enum type by enum_no and lower it into a lir::lir_type::Type.
+    fn lower_enum_type(&self, enum_no: usize) -> Type {
+        let ty = &self.ns.enums[enum_no].ty;
+        self.lower_ast_type_by_depth(ty, 0)
+    }
+
+    fn lower_user_type(&self, user_ty: &ast::Type) -> Type {
+        // clone happens here because function unwrap_user_type takes ownership
+        let real_ty = user_ty.clone().unwrap_user_type(self.ns);
+        self.lower_ast_type_by_depth(&real_ty, 0)
+    }
+}

+ 254 - 0
src/lir/converter/mod.rs

@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: Apache-2.0
+use crate::codegen::cfg::BasicBlock;
+use crate::lir::{Block, LIR};
+use crate::{
+    codegen::{
+        self,
+        cfg::{self, ControlFlowGraph},
+    },
+    sema::ast::{self, Namespace, Parameter, RetrieveType},
+};
+
+use super::lir_type::LIRType;
+use super::{
+    expressions::Operand, instructions::Instruction, lir_type::InternalCallTy, vartable::Vartable,
+};
+
+mod expression;
+mod instruction;
+mod lir_type;
+mod vartable;
+
+/// A Converter converts a control flow graph into a Lower Intermediate Representation
+/// that has three-address code format.
+pub struct Converter<'a> {
+    /// a reference to the Namespace is used to retrieve useful information
+    /// like enum types, address length, etc.
+    ns: &'a Namespace,
+    /// a reference to the ControlFlowGraph is used to retrieve the instructions.
+    cfg: &'a ControlFlowGraph,
+}
+
+impl<'input> Converter<'input> {
+    /// Create a new Converter with a reference to the Namespace and the ControlFlowGraph.
+    pub fn new(ns: &'input Namespace, cfg: &'input ControlFlowGraph) -> Self {
+        Self { ns, cfg }
+    }
+
+    /// get the selector length from the Namespace.
+    pub fn fn_selector_length(&self) -> u8 {
+        self.ns.target.selector_length()
+    }
+
+    /// get the address length from the Namespace.
+    pub fn address_length(&self) -> usize {
+        self.ns.address_length
+    }
+
+    /// get the value length from the Namespace.
+    pub fn value_length(&self) -> usize {
+        self.ns.value_length
+    }
+
+    /// if the expression is a variable or a literial value, return an Operand.
+    /// otherwise, return None.
+    pub fn to_operand(
+        &self,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+    ) -> Option<Operand> {
+        match expr {
+            codegen::Expression::NumberLiteral { ty, value, loc, .. } => {
+                let ssa_ty = self.lower_ast_type(ty);
+                Some(Operand::new_number_literal(value, ssa_ty, *loc))
+            }
+            codegen::Expression::BoolLiteral { value, loc, .. } => {
+                Some(Operand::new_bool_literal(*value, *loc))
+            }
+            codegen::Expression::Variable { loc, var_no, .. } => {
+                Some(Operand::new_id(*var_no, *loc))
+            }
+            codegen::Expression::FunctionArg { loc, arg_no, .. } => {
+                vartable.get_function_arg(*arg_no, *loc)
+            }
+            _ => None,
+        }
+    }
+
+    /**<pre>
+    This function will expand a single codegen::Expression into a list Instructions and an Operand.
+    For example:
+        celcius * 9 / 5 + 32;
+    will be expanded into something like:
+        int32 temp1 = celcius * 9;
+        int32 temp2 = temp1 / 5;
+        int32 temp3 = temp2 + 32;
+    and temp3 will be returned as an Operand.
+    </pre>
+    */
+    pub fn to_operand_and_insns(
+        &self,
+        expr: &codegen::Expression,
+        vartable: &mut Vartable,
+        result: &mut Vec<Instruction>,
+    ) -> Operand {
+        match self.to_operand(expr, vartable) {
+            Some(op) => op,
+            None => {
+                let ast_ty = expr.ty();
+                let tmp = vartable.new_temp(self.lower_ast_type(&ast_ty));
+                self.lower_expression(&tmp, expr, vartable, result);
+                tmp
+            }
+        }
+    }
+
+    /// this functio essentially does the same thing as to_operand_and_insns, but it returns a
+    /// Option<Operand> instead of Operand, because the expression might be None.
+    pub fn to_operand_option_and_insns(
+        &self,
+        expr: &Option<codegen::Expression>,
+        vartable: &mut Vartable,
+        result: &mut Vec<Instruction>,
+    ) -> Option<Operand> {
+        match expr {
+            Some(expr) => {
+                let tmp = self.to_operand_and_insns(expr, vartable, result);
+                Some(tmp)
+            }
+            None => None,
+        }
+    }
+
+    /// this function is similar to the to_operand_and_insns function,
+    /// but it takes a `ast::StringLocation<codegen::Expression>`
+    /// and returns a `ast::StringLocation<Operand>` instead.
+    pub fn to_string_location_and_insns(
+        &self,
+        location: &ast::StringLocation<codegen::Expression>,
+        vartable: &mut Vartable,
+        result: &mut Vec<Instruction>,
+    ) -> ast::StringLocation<Operand> {
+        match location {
+            ast::StringLocation::CompileTime(str) => {
+                ast::StringLocation::CompileTime(str.clone()) as ast::StringLocation<Operand>
+            }
+            ast::StringLocation::RunTime(expr) => {
+                let op = self.to_operand_and_insns(expr, vartable, result);
+                ast::StringLocation::RunTime(Box::new(op))
+            }
+        }
+    }
+
+    /// this function is similar to the to_operand_and_insns function,
+    /// but it takes a `ast::ExternalCallAccounts<codegen::Expression>`
+    /// and returns a `ast::ExternalCallAccounts<Operand>` instead.
+    pub fn to_external_call_accounts_and_insns(
+        &self,
+        accounts: &ast::ExternalCallAccounts<codegen::Expression>,
+        vartable: &mut Vartable,
+        result: &mut Vec<Instruction>,
+    ) -> ast::ExternalCallAccounts<Operand> {
+        match accounts {
+            ast::ExternalCallAccounts::Present(accounts) => {
+                let tmp = self.to_operand_and_insns(accounts, vartable, result);
+                ast::ExternalCallAccounts::Present(tmp)
+            }
+            ast::ExternalCallAccounts::NoAccount => {
+                ast::ExternalCallAccounts::NoAccount as ast::ExternalCallAccounts<Operand>
+            }
+            ast::ExternalCallAccounts::AbsentArgument => {
+                ast::ExternalCallAccounts::AbsentArgument as ast::ExternalCallAccounts<Operand>
+            }
+        }
+    }
+
+    /// this function is similar to the to_operand_and_insns function,
+    /// but it takes a `cfg::InternalCallTy`
+    /// and returns a `InternalCallTy` instead.
+    /// If the `cfg::InternalCallTy` is a `cfg::InternalCallTy::Dynamic`,
+    /// then the `cfg::Expression` will be expanded into a list of `Instruction`s and an `Operand`.
+    pub fn to_internal_call_ty_and_insns(
+        &self,
+        call: &cfg::InternalCallTy,
+        vartable: &mut Vartable,
+        result: &mut Vec<Instruction>,
+    ) -> InternalCallTy {
+        match call {
+            cfg::InternalCallTy::Builtin { ast_func_no } => InternalCallTy::Builtin {
+                ast_func_no: *ast_func_no,
+            },
+            cfg::InternalCallTy::Static { cfg_no } => InternalCallTy::Static { cfg_no: *cfg_no },
+            cfg::InternalCallTy::Dynamic(expr) => {
+                let tmp = self.to_operand_and_insns(expr, vartable, result);
+                InternalCallTy::Dynamic(tmp)
+            }
+        }
+    }
+
+    /// This is the entry point of the Converter.
+    /// It will lower the control flow graph into a Lower Intermediate Representation `LIR`.
+    pub fn get_lir(&self) -> LIR {
+        let mut vartable = self.to_vartable(&self.cfg.vars);
+
+        let blocks = self
+            .cfg
+            .blocks
+            .iter()
+            .map(|block| self.lower_basic_block(block, &mut vartable))
+            .collect::<Vec<Block>>();
+
+        let params = self
+            .cfg
+            .params
+            .iter()
+            .map(|p| self.to_lir_typed_parameter(p))
+            .collect::<Vec<Parameter<LIRType>>>();
+
+        let returns = self
+            .cfg
+            .returns
+            .iter()
+            .map(|p| self.to_lir_typed_parameter(p))
+            .collect::<Vec<Parameter<LIRType>>>();
+
+        LIR {
+            name: self.cfg.name.clone(),
+            function_no: self.cfg.function_no,
+            params,
+            returns,
+            vartable,
+            blocks,
+            nonpayable: self.cfg.nonpayable,
+            public: self.cfg.public,
+            ty: self.cfg.ty,
+            selector: self.cfg.selector.clone(),
+        }
+    }
+
+    fn lower_basic_block(&self, basic_block: &BasicBlock, vartable: &mut Vartable) -> Block {
+        let mut instructions = vec![];
+        for insn in &basic_block.instr {
+            self.lower_instr(insn, vartable, &mut instructions);
+        }
+
+        Block {
+            name: basic_block.name.clone(),
+            instructions,
+        }
+    }
+
+    fn to_lir_typed_parameter(&self, param: &Parameter<ast::Type>) -> Parameter<LIRType> {
+        Parameter {
+            loc: param.loc,
+            id: param.id.clone(),
+            ty: self.lower_ast_type(&param.ty),
+            ty_loc: param.ty_loc,
+            indexed: param.indexed,
+            readonly: param.readonly,
+            infinite_size: param.infinite_size,
+            recursive: param.recursive,
+            annotation: param.annotation.clone(),
+        }
+    }
+}

+ 35 - 0
src/lir/converter/vartable.rs

@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Apache-2.0
+use indexmap::IndexMap;
+
+use crate::{
+    codegen::vartable::Vars,
+    lir::vartable::{Var, Vartable},
+};
+
+use super::Converter;
+
+impl Converter<'_> {
+    /// Converts a `codegen::vartable::Vars` into a `lir::vartable::Vartable`.
+    /// The types are lowered into `lir::lir_type::LIRType`.
+    pub fn to_vartable(&self, tab: &Vars) -> Vartable {
+        let mut vars = IndexMap::new();
+        let mut max_id = 0;
+        for (id, var) in tab {
+            vars.insert(
+                *id,
+                Var {
+                    id: *id,
+                    ty: self.lower_ast_type(&var.ty),
+                    name: var.id.name.clone(),
+                },
+            );
+            max_id = max_id.max(*id);
+        }
+
+        Vartable {
+            vars,
+            args: IndexMap::new(),
+            next_id: max_id + 1,
+        }
+    }
+}

+ 315 - 0
src/lir/expressions.rs

@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::codegen;
+use crate::sema::ast::{FormatArg, StringLocation};
+use num_bigint::BigInt;
+use solang_parser::pt::Loc;
+use std::fmt;
+use std::fmt::Formatter;
+
+use super::lir_type::LIRType;
+
+/// Operand: including variables and literals
+#[derive(Clone, Debug)]
+pub enum Operand {
+    Id {
+        loc: Loc,
+        id: usize,
+    },
+    BoolLiteral {
+        loc: Loc,
+        value: bool,
+    },
+    NumberLiteral {
+        loc: Loc,
+        value: BigInt,
+        ty: LIRType,
+    },
+}
+
+/// Binary operators
+#[derive(Debug, Clone)]
+pub enum BinaryOperator {
+    Add { overflowing: bool },
+    Sub { overflowing: bool },
+    Mul { overflowing: bool },
+    Pow { overflowing: bool },
+
+    Div,
+    UDiv,
+
+    Mod,
+    UMod,
+
+    Eq,
+    Neq,
+
+    Lt,
+    ULt,
+
+    Lte,
+    ULte,
+
+    Gt,
+    UGt,
+
+    Gte,
+    UGte,
+
+    BitAnd,
+    BitOr,
+    BitXor,
+
+    Shl,
+    Shr,
+    UShr,
+}
+
+/// Unary operators
+#[derive(Debug, Clone)]
+pub enum UnaryOperator {
+    Not,
+    Neg { overflowing: bool },
+    BitNot,
+}
+
+/// Expressions
+#[derive(Debug, Clone)]
+pub enum Expression {
+    BinaryExpr {
+        loc: Loc,
+        operator: BinaryOperator,
+        left: Box<Operand>,
+        right: Box<Operand>,
+    },
+    UnaryExpr {
+        loc: Loc,
+        operator: UnaryOperator,
+        right: Box<Operand>,
+    },
+
+    Id {
+        loc: Loc,
+        id: usize,
+    },
+
+    BoolLiteral {
+        loc: Loc,
+        value: bool,
+    },
+    NumberLiteral {
+        loc: Loc,
+        value: BigInt,
+    },
+    ArrayLiteral {
+        loc: Loc,
+        ty: LIRType,
+        dimensions: Vec<u32>,
+        values: Vec<Operand>,
+    },
+    ConstArrayLiteral {
+        loc: Loc,
+        ty: LIRType,
+        dimensions: Vec<u32>,
+        values: Vec<Operand>,
+    },
+    BytesLiteral {
+        loc: Loc,
+        ty: LIRType,
+        value: Vec<u8>,
+    },
+    StructLiteral {
+        loc: Loc,
+        ty: LIRType,
+        values: Vec<Operand>,
+    },
+
+    Cast {
+        loc: Loc,
+        operand: Box<Operand>,
+        to_ty: LIRType,
+    },
+    BytesCast {
+        loc: Loc,
+        operand: Box<Operand>,
+        to_ty: LIRType,
+    },
+    /// Sign extending the length, only for signed int
+    SignExt {
+        loc: Loc,
+        operand: Box<Operand>,
+        to_ty: LIRType,
+    },
+    /// Extending the length, only for unsigned int
+    ZeroExt {
+        loc: Loc,
+        operand: Box<Operand>,
+        to_ty: LIRType,
+    },
+    // Truncating integer into a shorter one
+    Trunc {
+        loc: Loc,
+        operand: Box<Operand>,
+        to_ty: LIRType,
+    },
+
+    AllocDynamicBytes {
+        loc: Loc,
+        ty: LIRType,
+        size: Box<Operand>,
+        initializer: Option<Vec<u8>>,
+    },
+
+    /// address-of
+    GetRef {
+        loc: Loc,
+        operand: Box<Operand>,
+    },
+    /// value-of-address
+    Load {
+        loc: Loc,
+        operand: Box<Operand>,
+    },
+    /// Used for accessing struct member
+    StructMember {
+        loc: Loc,
+        operand: Box<Operand>,
+        member: usize,
+    },
+    /// Array subscripting: <array>[<index>]
+    Subscript {
+        loc: Loc,
+        arr: Box<Operand>,
+        index: Box<Operand>,
+    },
+    AdvancePointer {
+        loc: Loc,
+        pointer: Box<Operand>,
+        bytes_offset: Box<Operand>,
+    },
+    // Get the nth param in the current function call stack
+    FunctionArg {
+        loc: Loc,
+        ty: LIRType,
+        arg_no: usize,
+    },
+
+    FormatString {
+        loc: Loc,
+        args: Vec<(FormatArg, Operand)>,
+    },
+    InternalFunctionCfg {
+        loc: Loc,
+        cfg_no: usize,
+    },
+    /// Keccak256 hash
+    Keccak256 {
+        loc: Loc,
+        args: Vec<Operand>,
+    },
+    StringCompare {
+        loc: Loc,
+        left: StringLocation<Operand>,
+        right: StringLocation<Operand>,
+    },
+    StringConcat {
+        loc: Loc,
+        left: StringLocation<Operand>,
+        right: StringLocation<Operand>,
+    },
+    Builtin {
+        loc: Loc,
+        kind: codegen::Builtin,
+        args: Vec<Operand>,
+    },
+
+    StorageArrayLength {
+        loc: Loc,
+        array: Box<Operand>,
+    },
+    // This is designed for external calls: represents a hard coded mem location.
+    ReturnData {
+        loc: Loc,
+    },
+}
+
+impl fmt::Display for BinaryOperator {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            BinaryOperator::Add { overflowing } => {
+                write!(f, "{}", if *overflowing { "(of)+" } else { "+" })
+            }
+            BinaryOperator::Sub { overflowing } => {
+                write!(f, "{}", if *overflowing { "(of)-" } else { "-" })
+            }
+            BinaryOperator::Mul { overflowing } => {
+                write!(f, "{}", if *overflowing { "(of)*" } else { "*" })
+            }
+            BinaryOperator::Pow { overflowing } => {
+                write!(f, "{}", if *overflowing { "(of)**" } else { "**" })
+            }
+            BinaryOperator::Div => write!(f, "/"),
+            // example: uint8 a = b (u)/ c
+            BinaryOperator::UDiv => write!(f, "(u)/"),
+            BinaryOperator::Mod => write!(f, "%"),
+            BinaryOperator::UMod => write!(f, "(u)%"),
+            BinaryOperator::Eq => write!(f, "=="),
+            BinaryOperator::Neq => write!(f, "!="),
+            BinaryOperator::Lt => write!(f, "<"),
+            BinaryOperator::ULt => write!(f, "(u)<"),
+            BinaryOperator::Lte => write!(f, "<="),
+            BinaryOperator::ULte => write!(f, "(u)<="),
+            BinaryOperator::Gt => write!(f, ">"),
+            BinaryOperator::UGt => write!(f, "(u)>"),
+            BinaryOperator::Gte => write!(f, ">="),
+            BinaryOperator::UGte => write!(f, "(u)>="),
+            BinaryOperator::BitAnd => write!(f, "&"),
+            BinaryOperator::BitOr => write!(f, "|"),
+            BinaryOperator::BitXor => write!(f, "^"),
+            BinaryOperator::Shl => write!(f, "<<"),
+            BinaryOperator::Shr => write!(f, ">>"),
+            BinaryOperator::UShr => write!(f, "(u)>>"),
+        }
+    }
+}
+
+impl fmt::Display for UnaryOperator {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            UnaryOperator::Not => write!(f, "!"),
+            UnaryOperator::Neg { overflowing } => {
+                write!(f, "{}", if *overflowing { "(of)-" } else { "-" })
+            }
+            UnaryOperator::BitNot => write!(f, "~"),
+        }
+    }
+}
+
+impl Operand {
+    /// Get id from operand, panic if operand is not an id
+    pub fn get_id_or_error(&self) -> usize {
+        match self {
+            Operand::Id { id, .. } => *id,
+            _ => panic!("Operand is not an id"),
+        }
+    }
+
+    /// Create a new operand from an id
+    pub fn new_id(id: usize, loc: Loc) -> Self {
+        Operand::Id { id, loc }
+    }
+
+    /// Create a new operand from a bool literal
+    pub fn new_bool_literal(value: bool, loc: Loc) -> Self {
+        Operand::BoolLiteral { value, loc }
+    }
+
+    /// Create a new operand from a number literal
+    pub fn new_number_literal(value: &BigInt, ty: LIRType, loc: Loc) -> Self {
+        Operand::NumberLiteral {
+            loc,
+            value: value.clone(),
+            ty,
+        }
+    }
+}

+ 196 - 0
src/lir/instructions.rs

@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::codegen;
+use crate::lir::expressions::{Expression, Operand};
+use crate::lir::lir_type::InternalCallTy;
+use crate::sema::ast::{CallTy, ExternalCallAccounts};
+use solang_parser::pt::Loc;
+
+use super::lir_type::PhiInput;
+
+/// Instructions using three-address code format
+#[derive(Debug)]
+pub enum Instruction {
+    Nop,
+
+    /// Return data to the outside callers
+    ReturnData {
+        loc: Loc,
+        data: Operand,
+        data_len: Operand,
+    },
+    ReturnCode {
+        loc: Loc,
+        code: codegen::cfg::ReturnCode,
+    },
+
+    Set {
+        loc: Loc,
+        res: usize,
+        expr: Expression,
+    },
+    Store {
+        loc: Loc,
+        dest: Operand,
+        data: Operand,
+    },
+    PushMemory {
+        loc: Loc,
+        res: usize,
+        array: usize,
+        value: Operand,
+    },
+    PopMemory {
+        loc: Loc,
+        res: usize,
+        array: usize,
+    },
+    Constructor {
+        loc: Loc,
+        success: Option<usize>,
+        res: usize,
+        contract_no: usize,
+        constructor_no: Option<usize>,
+        encoded_args: Operand,
+        value: Option<Operand>,
+        gas: Operand,
+        salt: Option<Operand>,
+        address: Option<Operand>,
+        seeds: Option<Operand>,
+        accounts: ExternalCallAccounts<Operand>,
+    },
+
+    LoadStorage {
+        loc: Loc,
+        res: usize,
+        storage: Operand,
+    },
+    ClearStorage {
+        loc: Loc,
+        storage: Operand,
+    },
+    SetStorage {
+        loc: Loc,
+        value: Operand,
+        storage: Operand,
+    },
+    SetStorageBytes {
+        loc: Loc,
+        value: Operand,
+        storage: Operand,
+        offset: Operand,
+    },
+    PushStorage {
+        loc: Loc,
+        res: usize,
+        value: Option<Operand>,
+        storage: Operand,
+    },
+    PopStorage {
+        loc: Loc,
+        res: Option<usize>,
+        storage: Operand,
+    },
+
+    Call {
+        loc: Loc,
+        res: Vec<usize>,
+        call: InternalCallTy,
+        args: Vec<Operand>,
+    },
+    /// Print to log message
+    Print {
+        loc: Loc,
+        operand: Operand,
+    },
+    MemCopy {
+        loc: Loc,
+        src: Operand,
+        dest: Operand,
+        bytes: Operand,
+    },
+
+    ExternalCall {
+        loc: Loc,
+        /// Polkadot specific
+        success: Option<usize>,
+        address: Option<Operand>,
+        accounts: ExternalCallAccounts<Operand>,
+        /// Solana specific:
+        /// for deriving and proving the ownership of an account
+        seeds: Option<Operand>,
+        payload: Operand,
+        /// Polkadot specific:
+        /// holding tokens
+        value: Operand,
+        /// Polkadot specific.
+        /// On Solana, charged by transaction
+        gas: Operand,
+        /// CallTy is polkadot specific:
+        /// It involves difference code generation in emit.
+        callty: CallTy,
+        /// only used for analysis passes
+        contract_function_no: Option<(usize, usize)>,
+        /// Polkadot specific
+        flags: Option<Operand>,
+    },
+    /// Value transfer; either address.send() or address.transfer()
+    /// transfer tokens from one addr to another
+    ValueTransfer {
+        loc: Loc,
+        success: Option<usize>,
+        address: Operand,
+        value: Operand,
+    },
+    /// Self destruct
+    /// for destructing the contract from inside.
+    /// Note: only available on Polkadot
+    SelfDestruct {
+        loc: Loc,
+        recipient: Operand,
+    },
+    EmitEvent {
+        loc: Loc,
+        event_no: usize,
+        data: Operand,
+        topics: Vec<Operand>,
+    },
+    WriteBuffer {
+        loc: Loc,
+        buf: Operand,
+        offset: Operand,
+        value: Operand,
+    },
+
+    Branch {
+        loc: Loc,
+        block: usize,
+    },
+    BranchCond {
+        loc: Loc,
+        cond: Operand,
+        true_block: usize,
+        false_block: usize,
+    },
+    Switch {
+        loc: Loc,
+        cond: Operand,
+        cases: Vec<(Operand, usize)>,
+        default: usize,
+    },
+    Return {
+        loc: Loc,
+        value: Vec<Operand>,
+    },
+
+    AssertFailure {
+        loc: Loc,
+        encoded_args: Option<Operand>,
+    },
+
+    Phi {
+        loc: Loc,
+        res: usize,
+        vars: Vec<PhiInput>,
+    },
+}

+ 152 - 0
src/lir/lir_type.rs

@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::fmt;
+
+use crate::lir::expressions::Operand;
+use crate::sema::ast;
+use crate::sema::ast::ArrayLength;
+
+/// A struct type definition that is similar to the one in ast.rs,
+/// extended with a Vector type, as we need a lower level representation of
+/// String and DynamicBytes
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum StructType {
+    UserDefined(usize),
+    SolAccountInfo,
+    SolAccountMeta,
+    SolParameters,
+    ExternalFunction,
+    /// Vector is used here to represent String and DynamicBytes
+    Vector(Box<Type>),
+}
+
+/// A struct that contains the AST type and the LIR type.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct LIRType {
+    pub ast_type: ast::Type,
+    pub lir_type: Type,
+}
+
+/// Types for LIR. Some types present in the AST are not present here, as they
+/// are lowered to other types. See the `lower_ast_type` function in the `lir::converter::Converter`.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Type {
+    Bool,
+    Int(u16),
+    Uint(u16),
+    Bytes(u8),
+    Ptr(Box<Type>),
+    StoragePtr(bool, Box<Type>),
+    Function {
+        params: Vec<Type>,
+        returns: Vec<Type>,
+    },
+    Mapping {
+        key_ty: Box<Type>,
+        value_ty: Box<Type>,
+    },
+    Array(Box<Type>, Vec<ArrayLength>),
+    Struct(StructType),
+    Slice(Box<Type>),
+}
+
+#[derive(Clone, Debug)]
+pub enum InternalCallTy {
+    Static { cfg_no: usize },
+    Dynamic(Operand),
+    Builtin { ast_func_no: usize },
+}
+
+#[derive(Clone, Debug)]
+pub struct PhiInput {
+    pub operand: Operand,
+    pub block_no: usize,
+}
+
+impl From<&ast::StructType> for StructType {
+    fn from(ty: &ast::StructType) -> Self {
+        match ty {
+            ast::StructType::AccountInfo => StructType::SolAccountInfo,
+            ast::StructType::AccountMeta => StructType::SolAccountMeta,
+            ast::StructType::ExternalFunction => StructType::ExternalFunction,
+            ast::StructType::SolParameters => StructType::SolParameters,
+            ast::StructType::UserDefined(i) => StructType::UserDefined(*i),
+        }
+    }
+}
+
+impl fmt::Display for StructType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            StructType::UserDefined(i) => write!(f, "{}", i),
+            StructType::SolAccountInfo => write!(f, "SolAccountInfo"),
+            StructType::SolAccountMeta => write!(f, "SolAccountMeta"),
+            StructType::ExternalFunction => write!(f, "ExternalFunction"),
+            StructType::SolParameters => write!(f, "SolParameters"),
+            StructType::Vector(elem_ty) => write!(f, "vector<{}>", elem_ty),
+        }
+    }
+}
+
+impl fmt::Display for LIRType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.lir_type)
+    }
+}
+
+impl fmt::Display for Type {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Type::Bool => write!(f, "bool"),
+            Type::Int(width) => write!(f, "int{}", width),
+            Type::Uint(width) => write!(f, "uint{}", width),
+            Type::Bytes(width) => write!(f, "bytes{}", width),
+            Type::Ptr(ty) => write!(f, "ptr<{}>", ty),
+            Type::StoragePtr(immutable, ty) => {
+                if *immutable {
+                    write!(f, "const_storage_ptr<{}>", ty)
+                } else {
+                    write!(f, "storage_ptr<{}>", ty)
+                }
+            }
+            Type::Array(ty, len) => {
+                write!(f, "{}", ty)?;
+                len.iter().for_each(|len| match len {
+                    ArrayLength::Fixed(len) => write!(f, "[{}]", len).unwrap(),
+                    ArrayLength::Dynamic => write!(f, "[]").unwrap(),
+                    ArrayLength::AnyFixed => write!(f, "[?]").unwrap(),
+                });
+                Ok(())
+            }
+            Type::Slice(ty) => write!(f, "slice<{}>", ty),
+            Type::Struct(ty) => write!(f, "struct.{}", ty),
+            Type::Function { params, returns } => {
+                write!(f, "function (")?;
+                for (i, param) in params.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ")?;
+                    }
+                    write!(f, "{}", param)?;
+                }
+                write!(f, ") returns (")?;
+                for (i, ret) in returns.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ")?;
+                    }
+                    write!(f, "{}", ret)?;
+                }
+                write!(f, ")")?;
+                Ok(())
+            }
+            Type::Mapping { key_ty, value_ty } => {
+                write!(f, "mapping({} => {})", key_ty, value_ty)
+            }
+        }
+    }
+}
+
+impl PhiInput {
+    pub fn new(operand: Operand, block_no: usize) -> Self {
+        Self { operand, block_no }
+    }
+}

+ 51 - 0
src/lir/mod.rs

@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub mod converter;
+pub mod expressions;
+pub mod instructions;
+pub mod lir_type;
+pub mod printer;
+pub mod vartable;
+
+use crate::codegen::cfg::ASTFunction;
+use crate::lir::instructions::Instruction;
+use crate::lir::vartable::Vartable;
+use crate::pt::FunctionTy;
+use crate::sema::ast::Parameter;
+
+use self::lir_type::LIRType;
+
+/// The `LIR` struct represents the Lower Intermediate Representation of a function,
+/// which uses three-address code for instructions.
+#[derive(Debug)]
+pub struct LIR {
+    /// The name of the function.
+    pub name: String,
+    /// The unique identifier of the function.
+    pub function_no: ASTFunction,
+    /// The parameters of the function, with their types.
+    pub params: Vec<Parameter<LIRType>>,
+    /// The return values of the function, with their types.
+    pub returns: Vec<Parameter<LIRType>>,
+    /// A table of variables used in the function.
+    pub vartable: Vartable,
+    /// The blocks of instructions in the function.
+    pub blocks: Vec<Block>,
+    /// A flag indicating whether the function is non-payable.
+    pub nonpayable: bool,
+    /// A flag indicating whether the function is public.
+    pub public: bool,
+    /// The type of the function (e.g., constructor, fallback, etc.).
+    pub ty: FunctionTy,
+    /// Used to match the function in the contract
+    pub selector: Vec<u8>,
+}
+
+/// A block of instructions in the Lower Intermediate Representation.
+#[derive(Debug)]
+pub struct Block {
+    /// The name of the block.
+    pub name: String,
+    /// The instructions in the block.
+    pub instructions: Vec<Instruction>,
+}

+ 272 - 0
src/lir/printer/expression.rs

@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir::expressions::{Expression, Operand};
+use crate::lir::printer::Printer;
+use crate::sema::ast::StringLocation;
+use std::io::Write;
+
+impl Printer<'_> {
+    /// print left-hand-side operand
+    pub fn print_lhs_operand(&self, f: &mut dyn Write, operand: &Operand) {
+        match operand {
+            Operand::Id { id, .. } => {
+                let ty = self.get_var_type(id);
+                let name = self.get_var_name(id);
+                write!(f, "{} %{}", ty, name).unwrap();
+            }
+            _ => unreachable!("unsupported lhs operand: {:?}", operand),
+        }
+    }
+
+    /// print right-hand-side operand
+    pub fn print_rhs_operand(&self, f: &mut dyn Write, operand: &Operand) {
+        match operand {
+            Operand::Id { id, .. } => {
+                let ty = self.get_var_type(id);
+                let name = self.get_var_name(id);
+                write!(f, "{}(%{})", ty, name).unwrap();
+            }
+            Operand::BoolLiteral { value, .. } => write!(f, "{}", value).unwrap(),
+            Operand::NumberLiteral { value, ty, .. } => write!(f, "{}({})", ty, value).unwrap(),
+        }
+    }
+
+    pub fn print_expr(&self, f: &mut dyn Write, expr: &Expression) {
+        match expr {
+            Expression::BinaryExpr {
+                operator: op,
+                left,
+                right,
+                ..
+            } => {
+                self.print_rhs_operand(f, left);
+                write!(f, " {} ", op).unwrap();
+                self.print_rhs_operand(f, right);
+            }
+            Expression::UnaryExpr {
+                operator: op,
+                right,
+                ..
+            } => {
+                write!(f, "{}", op).unwrap();
+                self.print_rhs_operand(f, right);
+            }
+            Expression::Id { id, .. } => {
+                let ty = self.get_var_type(id);
+                let name = self.get_var_name(id);
+                write!(f, "{}(%{})", ty, name).unwrap()
+            }
+            Expression::ArrayLiteral { ty, values, .. } => {
+                write!(f, "{}", ty).unwrap();
+                write!(f, " [").unwrap();
+                values.iter().enumerate().for_each(|(i, val)| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, val);
+                });
+                write!(f, "]").unwrap()
+            }
+            Expression::ConstArrayLiteral { ty, values, .. } => {
+                write!(f, "const {}", ty).unwrap();
+                write!(f, " [").unwrap();
+                values.iter().enumerate().for_each(|(i, val)| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, val);
+                });
+                write!(f, "]").unwrap();
+            }
+            Expression::BytesLiteral { ty, value, .. } => {
+                write!(f, "{} hex\"", ty).unwrap();
+                value.iter().enumerate().for_each(|(i, byte)| {
+                    if i != 0 {
+                        write!(f, "_").unwrap();
+                    }
+                    write!(f, "{:02x}", byte).unwrap();
+                });
+                write!(f, "\"").unwrap();
+            }
+            Expression::StructLiteral { values, .. } => {
+                write!(f, "struct {{ ").unwrap();
+                values.iter().enumerate().for_each(|(i, val)| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, val);
+                });
+                write!(f, " }}").unwrap();
+            }
+            Expression::Cast {
+                operand: op, to_ty, ..
+            } => {
+                write!(f, "(cast ").unwrap();
+                self.print_rhs_operand(f, op);
+                write!(f, " to {})", to_ty).unwrap();
+            }
+            Expression::BytesCast { operand, to_ty, .. } => {
+                write!(f, "(cast ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, " to {})", to_ty).unwrap();
+            }
+            Expression::SignExt { to_ty, operand, .. } => {
+                write!(f, "(sext ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, " to {})", to_ty).unwrap();
+            }
+            Expression::ZeroExt { to_ty, operand, .. } => {
+                write!(f, "(zext ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, " to {})", to_ty).unwrap();
+            }
+            Expression::Trunc { operand, to_ty, .. } => {
+                write!(f, "(trunc ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, " to {})", to_ty).unwrap();
+            }
+            Expression::AllocDynamicBytes {
+                ty,
+                size,
+                initializer,
+                ..
+            } => {
+                if initializer.is_none() {
+                    write!(f, "alloc {}[", ty).unwrap();
+                    self.print_rhs_operand(f, size);
+                    return write!(f, "]").unwrap();
+                }
+
+                write!(f, "alloc {}[", ty).unwrap();
+                self.print_rhs_operand(f, size);
+                write!(f, "] {{").unwrap();
+                initializer
+                    .as_ref()
+                    .unwrap()
+                    .iter()
+                    .enumerate()
+                    .for_each(|(i, byte)| {
+                        if i != 0 {
+                            write!(f, ", ").unwrap();
+                        }
+                        write!(f, "{:02x}", byte).unwrap();
+                    });
+                write!(f, "}}").unwrap();
+            }
+            Expression::GetRef { operand, .. } => {
+                write!(f, "&").unwrap();
+                self.print_rhs_operand(f, operand)
+            }
+            Expression::Load { operand, .. } => {
+                write!(f, "*").unwrap();
+                self.print_rhs_operand(f, operand)
+            }
+            Expression::StructMember {
+                operand, member, ..
+            } => {
+                write!(f, "access ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, " member {}", member).unwrap();
+            }
+            Expression::Subscript { arr, index, .. } => {
+                self.print_rhs_operand(f, arr);
+                write!(f, "[").unwrap();
+                self.print_rhs_operand(f, index);
+                write!(f, "]").unwrap();
+            }
+            Expression::AdvancePointer {
+                pointer,
+                bytes_offset,
+                ..
+            } => {
+                write!(f, "ptr_add(").unwrap();
+                self.print_rhs_operand(f, pointer);
+                write!(f, ", ").unwrap();
+                self.print_rhs_operand(f, bytes_offset);
+                write!(f, ")").unwrap();
+            }
+            Expression::FunctionArg { arg_no, ty, .. } => {
+                write!(f, "{}(arg#{})", ty, arg_no).unwrap();
+            }
+            Expression::FormatString { args, .. } => {
+                write!(f, "fmt_str(").unwrap();
+                args.iter().enumerate().for_each(|(i, (spec, arg))| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    let spec_str = spec.to_string();
+                    if spec_str.is_empty() {
+                        self.print_rhs_operand(f, arg);
+                    } else {
+                        write!(f, "{} ", spec).unwrap();
+                        self.print_rhs_operand(f, arg);
+                    }
+                });
+                write!(f, ")").unwrap();
+            }
+            Expression::InternalFunctionCfg { cfg_no, .. } => {
+                write!(f, "function#{}", cfg_no).unwrap()
+            }
+            Expression::Keccak256 { args, .. } => {
+                write!(f, "keccak256(").unwrap();
+                args.iter().enumerate().for_each(|(i, arg)| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, arg);
+                });
+                write!(f, ")").unwrap();
+            }
+            Expression::StringCompare { left, right, .. } => {
+                write!(f, "strcmp(").unwrap();
+                match left {
+                    StringLocation::CompileTime(s) => write!(f, "\"{:?}\"", s).unwrap(),
+                    StringLocation::RunTime(op) => self.print_rhs_operand(f, op),
+                };
+
+                write!(f, ", ").unwrap();
+
+                match right {
+                    StringLocation::CompileTime(s) => write!(f, "\"{:?}\"", s).unwrap(),
+                    StringLocation::RunTime(op) => self.print_rhs_operand(f, op),
+                };
+                write!(f, ")").unwrap();
+            }
+            Expression::StringConcat { left, right, .. } => {
+                write!(f, "strcat(").unwrap();
+                match left {
+                    StringLocation::CompileTime(s) => write!(f, "\"{:?}\"", s).unwrap(),
+                    StringLocation::RunTime(op) => self.print_rhs_operand(f, op),
+                };
+
+                write!(f, ", ").unwrap();
+
+                match right {
+                    StringLocation::CompileTime(s) => write!(f, "\"{:?}\"", s).unwrap(),
+                    StringLocation::RunTime(op) => self.print_rhs_operand(f, op),
+                };
+                write!(f, ")").unwrap();
+            }
+            Expression::StorageArrayLength { array, .. } => {
+                write!(f, "storage_arr_len(").unwrap();
+                self.print_rhs_operand(f, array);
+                write!(f, ")").unwrap();
+            }
+            Expression::ReturnData { .. } => write!(f, "(extern_call_ret_data)").unwrap(),
+            Expression::NumberLiteral { value, .. } => {
+                write!(f, "{}", value).unwrap();
+            }
+            Expression::BoolLiteral { value, .. } => write!(f, "{}", value).unwrap(),
+            Expression::Builtin { kind, args, .. } => {
+                write!(f, "builtin: {:?}(", kind).unwrap();
+                args.iter().enumerate().for_each(|(i, arg)| {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, arg);
+                });
+                write!(f, ")").unwrap();
+            }
+        }
+    }
+}

+ 493 - 0
src/lir/printer/instruction.rs

@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir::instructions::Instruction;
+use crate::lir::lir_type::{InternalCallTy, PhiInput};
+use crate::lir::printer::Printer;
+use crate::sema::ast;
+use std::io::Write;
+
+impl Printer<'_> {
+    pub fn print_phi(&self, f: &mut dyn Write, phi: &PhiInput) {
+        write!(f, "[").unwrap();
+        self.print_rhs_operand(f, &phi.operand);
+        write!(f, ", block#{}]", phi.block_no).unwrap();
+    }
+
+    pub fn print_instruction(&self, f: &mut dyn Write, insn: &Instruction) {
+        match insn {
+            Instruction::Nop => write!(f, "nop;").unwrap(),
+            Instruction::ReturnData { data, data_len, .. } => {
+                write!(f, "return_data ").unwrap();
+                self.print_rhs_operand(f, data);
+                write!(f, " of length ",).unwrap();
+                self.print_rhs_operand(f, data_len);
+                write!(f, ";").unwrap();
+            }
+            Instruction::ReturnCode { code, .. } => write!(f, "return_code \"{}\";", code).unwrap(),
+            Instruction::Set { res, expr, .. } => {
+                let res_op = self.get_var_operand(res);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = ").unwrap();
+                self.print_expr(f, expr);
+                write!(f, ";").unwrap();
+            }
+            Instruction::Store { dest, data, .. } => {
+                write!(f, "store ").unwrap();
+                self.print_rhs_operand(f, data);
+                write!(f, " to ").unwrap();
+                self.print_rhs_operand(f, dest);
+                write!(f, ";").unwrap();
+            }
+            Instruction::PushMemory {
+                res, array, value, ..
+            } => {
+                let res_op = self.get_var_operand(res);
+                let array_op = self.get_var_operand(array);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = push_mem ").unwrap();
+                self.print_rhs_operand(f, &array_op);
+                write!(f, " ").unwrap();
+                self.print_rhs_operand(f, value);
+                write!(f, ";").unwrap();
+            }
+            Instruction::PopMemory { res, array, .. } => {
+                let res_op = self.get_var_operand(res);
+                let array_op = self.get_var_operand(array);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = pop_mem ").unwrap();
+                self.print_rhs_operand(f, &array_op);
+                write!(f, ";").unwrap();
+            }
+            Instruction::Constructor {
+                success,
+                res,
+                contract_no,
+                encoded_args,
+                gas,
+                salt,
+                value,
+                address,
+                seeds,
+                accounts,
+                constructor_no,
+                ..
+            } => {
+                // success
+                match success {
+                    Some(success) => {
+                        let res_op = self.get_var_operand(res);
+                        let success_op = self.get_var_operand(success);
+                        self.print_lhs_operand(f, &success_op);
+                        write!(f, ", ").unwrap();
+                        self.print_lhs_operand(f, &res_op);
+                    }
+                    None => write!(f, "{}, _", res).unwrap(),
+                };
+
+                write!(f, " = ").unwrap();
+
+                // constructor
+                match constructor_no {
+                    Some(constructor_no) => write!(
+                        f,
+                        "constructor(no: {}, contract_no:{})",
+                        constructor_no, contract_no
+                    )
+                    .unwrap(),
+                    None => write!(f, "constructor(no: _, contract_no:{})", contract_no).unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                // salt
+                match salt {
+                    Some(salt) => {
+                        write!(f, "salt:").unwrap();
+                        self.print_rhs_operand(f, salt);
+                    }
+                    None => write!(f, "salt:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                // value
+                match value {
+                    Some(value) => {
+                        write!(f, "value:").unwrap();
+                        self.print_rhs_operand(f, value);
+                    }
+                    None => write!(f, "value:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                // gas
+                write!(f, "gas:").unwrap();
+                self.print_rhs_operand(f, gas);
+
+                write!(f, " ").unwrap();
+
+                match address {
+                    Some(address) => {
+                        write!(f, "address:").unwrap();
+                        self.print_rhs_operand(f, address);
+                    }
+                    None => write!(f, "address:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                match seeds {
+                    Some(seeds) => {
+                        write!(f, "seeds:").unwrap();
+                        self.print_rhs_operand(f, seeds);
+                    }
+                    None => write!(f, "seeds:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                write!(f, "encoded-buffer:").unwrap();
+                self.print_rhs_operand(f, encoded_args);
+
+                write!(f, " ").unwrap();
+
+                match accounts {
+                    ast::ExternalCallAccounts::NoAccount => write!(f, "accounts:none").unwrap(),
+                    ast::ExternalCallAccounts::Present(acc) => {
+                        write!(f, "accounts:").unwrap();
+                        self.print_rhs_operand(f, acc);
+                    }
+                    ast::ExternalCallAccounts::AbsentArgument => {
+                        write!(f, "accounts:absent").unwrap()
+                    }
+                }
+            }
+            Instruction::LoadStorage { res, storage, .. } => {
+                let res_op = self.get_var_operand(res);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = load_storage ").unwrap();
+                self.print_rhs_operand(f, storage);
+                write!(f, ";").unwrap();
+            }
+            Instruction::ClearStorage { storage, .. } => {
+                write!(f, "clear_storage ").unwrap();
+                self.print_rhs_operand(f, storage);
+                write!(f, ";").unwrap();
+            }
+            Instruction::SetStorage { value, storage, .. } => {
+                write!(f, "set_storage ").unwrap();
+                self.print_rhs_operand(f, storage);
+                write!(f, " ").unwrap();
+                self.print_rhs_operand(f, value);
+                write!(f, ";").unwrap();
+            }
+            Instruction::SetStorageBytes {
+                value,
+                storage,
+                offset,
+                ..
+            } => {
+                write!(f, "set_storage_bytes ").unwrap();
+                self.print_rhs_operand(f, storage);
+                write!(f, " offset:").unwrap();
+                self.print_rhs_operand(f, offset);
+                write!(f, " value:").unwrap();
+                self.print_rhs_operand(f, value);
+                write!(f, ";").unwrap();
+            }
+            Instruction::PushStorage {
+                res,
+                value,
+                storage,
+                ..
+            } => {
+                let res_op = self.get_var_operand(res);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = push_storage ").unwrap();
+                self.print_rhs_operand(f, storage);
+                write!(f, " ").unwrap();
+                match value {
+                    Some(value) => self.print_rhs_operand(f, value),
+                    None => write!(f, "empty").unwrap(),
+                };
+                write!(f, ";").unwrap();
+            }
+            Instruction::PopStorage { res, storage, .. } => match res {
+                Some(res) => {
+                    let res_op = self.get_var_operand(res);
+                    self.print_lhs_operand(f, &res_op);
+                    write!(f, " = pop_storage ").unwrap();
+                    self.print_rhs_operand(f, storage);
+                    write!(f, ";").unwrap();
+                }
+                None => {
+                    write!(f, "pop_storage ").unwrap();
+                    self.print_rhs_operand(f, storage);
+                    write!(f, ";").unwrap();
+                }
+            },
+            Instruction::Call {
+                res, call, args, ..
+            } => {
+                // lhs: %0, %1, ...
+                for (i, id) in res.iter().enumerate() {
+                    let res_op = self.get_var_operand(id);
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_lhs_operand(f, &res_op);
+                }
+
+                write!(f, " = call ").unwrap();
+
+                match call {
+                    InternalCallTy::Builtin { ast_func_no, .. } => {
+                        write!(f, "builtin#{}", ast_func_no).unwrap();
+                    }
+                    InternalCallTy::Static { cfg_no, .. } => {
+                        write!(f, "function#{}", cfg_no).unwrap()
+                    }
+                    InternalCallTy::Dynamic(op) => self.print_rhs_operand(f, op),
+                };
+
+                write!(f, "(").unwrap();
+
+                for (i, arg) in args.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, arg);
+                }
+
+                write!(f, ");").unwrap();
+            }
+            Instruction::Print { operand, .. } => {
+                write!(f, "print ").unwrap();
+                self.print_rhs_operand(f, operand);
+                write!(f, ";").unwrap();
+            }
+            Instruction::MemCopy {
+                src, dest, bytes, ..
+            } => {
+                write!(f, "memcopy ").unwrap();
+                self.print_rhs_operand(f, src);
+                write!(f, " to ").unwrap();
+                self.print_rhs_operand(f, dest);
+                write!(f, " for ").unwrap();
+                self.print_rhs_operand(f, bytes);
+                write!(f, " bytes;").unwrap();
+            }
+            Instruction::ExternalCall {
+                success,
+                address,
+                payload,
+                value,
+                accounts,
+                seeds,
+                gas,
+                callty,
+                contract_function_no,
+                flags,
+                ..
+            } => {
+                // {} = call_ext ty:{} address:{} payload:{} value:{} gas:{} accounts:{} seeds:{} contract_no:{}, function_no:{} flags:{};
+                match success {
+                    Some(success) => {
+                        let success_op = self.get_var_operand(success);
+                        self.print_lhs_operand(f, &success_op);
+                    }
+                    None => write!(f, "_").unwrap(),
+                };
+
+                write!(f, " = call_ext [{}] ", callty).unwrap();
+
+                match address {
+                    Some(address) => {
+                        write!(f, "address:").unwrap();
+                        self.print_rhs_operand(f, address);
+                    }
+                    None => write!(f, "address:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                write!(f, "payload:").unwrap();
+                self.print_rhs_operand(f, payload);
+
+                write!(f, " ").unwrap();
+
+                write!(f, "value:").unwrap();
+                self.print_rhs_operand(f, value);
+
+                write!(f, " ").unwrap();
+
+                write!(f, "gas:").unwrap();
+                self.print_rhs_operand(f, gas);
+
+                write!(f, " ").unwrap();
+
+                match accounts {
+                    ast::ExternalCallAccounts::NoAccount => write!(f, "accounts:none").unwrap(),
+                    ast::ExternalCallAccounts::Present(acc) => {
+                        write!(f, "accounts:").unwrap();
+                        self.print_rhs_operand(f, acc);
+                    }
+                    ast::ExternalCallAccounts::AbsentArgument => {
+                        write!(f, "accounts:absent").unwrap()
+                    }
+                };
+
+                write!(f, " ").unwrap();
+
+                match seeds {
+                    Some(seeds) => {
+                        write!(f, "seeds:").unwrap();
+                        self.print_rhs_operand(f, seeds);
+                    }
+                    None => write!(f, "seeds:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                match contract_function_no {
+                    Some((contract_no, function_no)) => {
+                        write!(
+                            f,
+                            "contract_no:{}, function_no:{}",
+                            contract_no, function_no
+                        )
+                        .unwrap();
+                    }
+                    None => write!(f, "contract_no:_, function_no:_").unwrap(),
+                };
+
+                write!(f, " ").unwrap();
+
+                match flags {
+                    Some(flags) => {
+                        write!(f, "flags:").unwrap();
+                        self.print_rhs_operand(f, flags);
+                    }
+                    None => write!(f, "flags:_").unwrap(),
+                }
+
+                write!(f, ";").unwrap();
+            }
+            Instruction::ValueTransfer {
+                success,
+                address,
+                value,
+                ..
+            } => {
+                match success {
+                    Some(success) => {
+                        let success_op = self.get_var_operand(success);
+                        self.print_lhs_operand(f, &success_op);
+                    }
+                    None => write!(f, "_").unwrap(),
+                };
+                write!(f, " = value_transfer ").unwrap();
+                self.print_rhs_operand(f, value);
+                write!(f, " to ").unwrap();
+                self.print_rhs_operand(f, address);
+                write!(f, ";").unwrap();
+            }
+            Instruction::SelfDestruct { recipient, .. } => {
+                write!(f, "self_destruct ").unwrap();
+                self.print_rhs_operand(f, recipient);
+                write!(f, ";").unwrap();
+            }
+            Instruction::EmitEvent {
+                data,
+                topics,
+                event_no,
+                ..
+            } => {
+                write!(f, "emit event#{} to topics[", event_no).unwrap();
+                for (i, topic) in topics.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, topic);
+                }
+                write!(f, "], data: ").unwrap();
+                self.print_rhs_operand(f, data);
+                write!(f, ";").unwrap()
+            }
+            Instruction::WriteBuffer {
+                buf, offset, value, ..
+            } => {
+                write!(f, "write_buf ").unwrap();
+                self.print_rhs_operand(f, buf);
+                write!(f, " offset:").unwrap();
+                self.print_rhs_operand(f, offset);
+                write!(f, " value:").unwrap();
+                self.print_rhs_operand(f, value);
+                write!(f, ";").unwrap();
+            }
+            Instruction::Branch { block, .. } => write!(f, "br block#{};", block).unwrap(),
+            Instruction::BranchCond {
+                cond,
+                true_block,
+                false_block,
+                ..
+            } => {
+                write!(f, "cbr ").unwrap();
+                self.print_rhs_operand(f, cond);
+                write!(f, " block#{} else block#{};", true_block, false_block).unwrap();
+            }
+            Instruction::Switch {
+                cond,
+                cases,
+                default,
+                ..
+            } => {
+                write!(f, "switch ").unwrap();
+                self.print_rhs_operand(f, cond);
+                write!(f, ":").unwrap();
+                for (i, (cond, block)) in cases.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    write!(f, "\n    case:    ").unwrap();
+                    self.print_rhs_operand(f, cond);
+                    write!(f, " => block#{}", block).unwrap();
+                }
+                write!(f, "\n    default: block#{};", default).unwrap();
+            }
+            Instruction::Return { value, .. } => {
+                write!(f, "return").unwrap();
+                for (i, value) in value.iter().enumerate() {
+                    if i == 0 {
+                        write!(f, " ").unwrap();
+                    } else {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_rhs_operand(f, value);
+                }
+                write!(f, ";").unwrap();
+            }
+            Instruction::AssertFailure { encoded_args, .. } => match encoded_args {
+                Some(encoded_args) => {
+                    write!(f, "assert_failure ").unwrap();
+                    self.print_rhs_operand(f, encoded_args);
+                    write!(f, ";").unwrap();
+                }
+                None => write!(f, "assert_failure;").unwrap(),
+            },
+            Instruction::Phi { res, vars, .. } => {
+                let res_op = self.get_var_operand(res);
+                self.print_lhs_operand(f, &res_op);
+                write!(f, " = phi ").unwrap();
+                for (i, var) in vars.iter().enumerate() {
+                    if i != 0 {
+                        write!(f, ", ").unwrap();
+                    }
+                    self.print_phi(f, var);
+                }
+                write!(f, ";").unwrap();
+            }
+        }
+    }
+}

+ 88 - 0
src/lir/printer/mod.rs

@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::expressions::Operand;
+use super::lir_type::LIRType;
+use crate::codegen::cfg::ASTFunction;
+use crate::lir::vartable::Vartable;
+use crate::lir::{Block, LIR};
+use std::io::Write;
+
+pub mod expression;
+pub mod instruction;
+
+pub struct Printer<'a> {
+    vartable: &'a Vartable,
+}
+
+impl<'a> Printer<'a> {
+    /// Create a new Printer with a reference to the Vartable.
+    pub fn new(vartable: &'a Vartable) -> Self {
+        Self { vartable }
+    }
+
+    /// get a variable name by its unique identifier.
+    pub(crate) fn get_var_name(&self, id: &usize) -> &str {
+        self.vartable.get_name(id)
+    }
+
+    /// get a variable type by its unique identifier.
+    pub(crate) fn get_var_type(&self, id: &usize) -> &LIRType {
+        self.vartable.get_type(id)
+    }
+
+    /// get a variable operand by its unique identifier.
+    pub(crate) fn get_var_operand(&self, id: &usize) -> Operand {
+        self.vartable.get_operand(
+            id,
+            // the location is not important for printing
+            solang_parser::pt::Loc::Codegen,
+        )
+    }
+
+    pub fn print_lir(&self, f: &mut dyn Write, cfg: &LIR) {
+        let function_no = match cfg.function_no {
+            ASTFunction::SolidityFunction(no) => format!("sol#{}", no),
+            ASTFunction::YulFunction(no) => format!("yul#{}", no),
+            ASTFunction::None => "none".to_string(),
+        };
+
+        let access_ctl = if cfg.public { "public" } else { "private" };
+
+        write!(f, "{} {} {} {} ", access_ctl, cfg.ty, function_no, cfg.name).unwrap();
+
+        write!(f, "(").unwrap();
+        for (i, param) in cfg.params.iter().enumerate() {
+            if i != 0 {
+                write!(f, ", ").unwrap();
+            }
+            write!(f, "{}", param.ty).unwrap();
+        }
+        write!(f, ")").unwrap();
+
+        if !cfg.returns.is_empty() {
+            write!(f, " returns (").unwrap();
+            for (i, ret) in cfg.returns.iter().enumerate() {
+                if i != 0 {
+                    write!(f, ", ").unwrap();
+                }
+                write!(f, "{}", ret.ty).unwrap();
+            }
+            write!(f, ")").unwrap();
+        }
+        writeln!(f, ":").unwrap();
+
+        for (i, block) in cfg.blocks.iter().enumerate() {
+            writeln!(f, "block#{} {}:", i, block.name).unwrap();
+            self.print_block(f, block);
+            writeln!(f).unwrap();
+        }
+    }
+
+    pub fn print_block(&self, f: &mut dyn Write, block: &Block) {
+        for insn in &block.instructions {
+            write!(f, "    ").unwrap();
+            self.print_instruction(f, insn);
+            writeln!(f).unwrap();
+        }
+    }
+}

+ 108 - 0
src/lir/vartable.rs

@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir::expressions::Operand;
+use indexmap::IndexMap;
+use solang_parser::pt::Loc;
+
+use super::lir_type::LIRType;
+
+/// a constant prefix for temporary variables
+pub const TEMP_PREFIX: &str = "temp.ssa_ir.";
+
+/// The `Var` struct represents a variable in the Lower Intermediate Representation.
+/// It contains the variable's unique identifier, its type, and its name.
+#[derive(Debug, Clone)]
+pub struct Var {
+    /// The unique identifier of the variable.
+    pub id: usize,
+    /// The type of the variable.
+    pub ty: LIRType,
+    /// The name of the variable.
+    pub name: String,
+}
+
+/// The `Vartable` struct represents a table of variables in the Lower Intermediate Representation.
+/// It holds a map of variables, a map of function arguments, and the next variable identifier.
+#[derive(Debug, Clone)]
+pub struct Vartable {
+    /// The map of variables
+    /// that contains the variable's unique identifier, its type, and its name.
+    pub vars: IndexMap<usize, Var>,
+    /// The map of function arguments
+    pub args: IndexMap</* arg no */ usize, /* var id */ usize>,
+    /// The next variable identifier.
+    pub next_id: usize,
+}
+
+impl Vartable {
+    /// Get the type of a variable by its unique identifier.
+    pub(crate) fn get_type(&self, id: &usize) -> &LIRType {
+        self.vars
+            .get(id)
+            .map(|var| &var.ty)
+            .ok_or(format!("Variable {} not found.", id))
+            .unwrap()
+    }
+
+    /// Get the name of a variable by its unique identifier.
+    pub(crate) fn get_name(&self, id: &usize) -> &str {
+        self.vars
+            .get(id)
+            .map(|var| var.name.as_str())
+            .ok_or(format!("Variable {} not found.", id))
+            .unwrap()
+    }
+
+    /// Get the operand of a variable by its unique identifier.
+    pub(crate) fn get_operand(&self, id: &usize, loc: Loc) -> Operand {
+        self.vars
+            .get(id)
+            .map(|var| Operand::Id { id: var.id, loc })
+            .ok_or(format!("Variable {} not found.", id))
+            .unwrap()
+    }
+
+    /// Set a temporary variable by its unique identifier.
+    pub fn set_tmp(&mut self, id: usize, ty: LIRType) {
+        let var = Var {
+            id,
+            ty,
+            name: format!("{}{}", TEMP_PREFIX, id),
+        };
+        self.next_id = self.next_id.max(id + 1);
+        self.vars.insert(id, var);
+    }
+
+    /// Create a new temporary variable.
+    pub(crate) fn new_temp(&mut self, ty: LIRType) -> Operand {
+        let name = format!("{}{}", TEMP_PREFIX, self.next_id);
+        let var = Var {
+            id: self.next_id,
+            ty,
+            name: name.clone(),
+        };
+        self.vars.insert(self.next_id, var);
+        let op = Operand::Id {
+            id: self.next_id,
+            loc: Loc::Codegen,
+        };
+        self.next_id += 1;
+        op
+    }
+
+    /// Get the Operand of a function argument by its argument number.
+    pub(crate) fn get_function_arg(&self, arg_no: usize, loc: Loc) -> Option<Operand> {
+        match self.args.get(&arg_no) {
+            Some(id) => {
+                let op = self.get_operand(id, loc);
+                Some(op)
+            }
+            None => None,
+        }
+    }
+
+    /// Add a function argument to the Vartable.
+    pub(crate) fn add_function_arg(&mut self, arg_no: usize, var_id: usize) {
+        self.args.insert(arg_no, var_id);
+    }
+}

+ 17 - 17
src/sema/ast.rs

@@ -160,7 +160,7 @@ pub struct StructDecl {
     pub id: pt::Identifier,
     pub loc: pt::Loc,
     pub contract: Option<String>,
-    pub fields: Vec<Parameter>,
+    pub fields: Vec<Parameter<Type>>,
     // List of offsets of the fields, last entry is the offset for the struct overall size
     pub offsets: Vec<BigInt>,
     // Same, but now in storage
@@ -173,7 +173,7 @@ pub struct EventDecl {
     pub id: pt::Identifier,
     pub loc: pt::Loc,
     pub contract: Option<usize>,
-    pub fields: Vec<Parameter>,
+    pub fields: Vec<Parameter<Type>>,
     pub signature: String,
     pub anonymous: bool,
     pub used: bool,
@@ -194,7 +194,7 @@ pub struct ErrorDecl {
     pub name: String,
     pub loc: pt::Loc,
     pub contract: Option<usize>,
-    pub fields: Vec<Parameter>,
+    pub fields: Vec<Parameter<Type>>,
     pub used: bool,
 }
 
@@ -240,7 +240,7 @@ impl fmt::Display for EnumDecl {
 }
 
 #[derive(PartialEq, Eq, Clone, Debug)]
-pub struct Parameter {
+pub struct Parameter<Type> {
     pub loc: pt::Loc,
     /// The name can empty (e.g. in an event field or unnamed parameter/return)
     pub id: Option<pt::Identifier>,
@@ -266,7 +266,7 @@ pub struct ParameterAnnotation {
     pub id: pt::Identifier,
 }
 
-impl Parameter {
+impl Parameter<Type> {
     /// Create a new instance of the given `Type`, with all other values set to their default.
     pub fn new_default(ty: Type) -> Self {
         Self {
@@ -328,8 +328,8 @@ pub struct Function {
     pub signature: String,
     pub mutability: Mutability,
     pub visibility: pt::Visibility,
-    pub params: Arc<Vec<Parameter>>,
-    pub returns: Arc<Vec<Parameter>>,
+    pub params: Arc<Vec<Parameter<Type>>>,
+    pub returns: Arc<Vec<Parameter<Type>>>,
     /// Constructor arguments for base contracts, only present on constructors
     pub bases: BTreeMap<usize, (pt::Loc, usize, Vec<Expression>)>,
     /// Modifiers for functions
@@ -385,8 +385,8 @@ pub struct ConstructorAnnotations {
 /// for both yul and solidity functions
 pub trait FunctionAttributes {
     fn get_symbol_table(&self) -> &Symtable;
-    fn get_parameters(&self) -> &Vec<Parameter>;
-    fn get_returns(&self) -> &Vec<Parameter>;
+    fn get_parameters(&self) -> &Vec<Parameter<Type>>;
+    fn get_returns(&self) -> &Vec<Parameter<Type>>;
 }
 
 impl FunctionAttributes for Function {
@@ -394,11 +394,11 @@ impl FunctionAttributes for Function {
         &self.symtable
     }
 
-    fn get_parameters(&self) -> &Vec<Parameter> {
+    fn get_parameters(&self) -> &Vec<Parameter<Type>> {
         &self.params
     }
 
-    fn get_returns(&self) -> &Vec<Parameter> {
+    fn get_returns(&self) -> &Vec<Parameter<Type>> {
         &self.returns
     }
 }
@@ -413,8 +413,8 @@ impl Function {
         ty: pt::FunctionTy,
         mutability: Option<pt::Mutability>,
         visibility: pt::Visibility,
-        params: Vec<Parameter>,
-        returns: Vec<Parameter>,
+        params: Vec<Parameter<Type>>,
+        returns: Vec<Parameter<Type>>,
         ns: &Namespace,
     ) -> Self {
         let signature = match ty {
@@ -1805,7 +1805,7 @@ pub enum Statement {
         unchecked: bool,
         statements: Vec<Statement>,
     },
-    VariableDecl(pt::Loc, usize, Parameter, Option<Arc<Expression>>),
+    VariableDecl(pt::Loc, usize, Parameter<Type>, Option<Arc<Expression>>),
     If(pt::Loc, bool, Expression, Vec<Statement>, Vec<Statement>),
     While(pt::Loc, bool, Expression, Vec<Statement>),
     For {
@@ -1842,7 +1842,7 @@ pub enum Statement {
 #[derive(Clone, Debug)]
 pub struct TryCatch {
     pub expr: Expression,
-    pub returns: Vec<(Option<usize>, Parameter)>,
+    pub returns: Vec<(Option<usize>, Parameter<Type>)>,
     pub ok_stmt: Vec<Statement>,
     pub errors: Vec<CatchClause>,
     pub catch_all: Option<CatchClause>,
@@ -1850,7 +1850,7 @@ pub struct TryCatch {
 
 #[derive(Clone, Debug)]
 pub struct CatchClause {
-    pub param: Option<Parameter>,
+    pub param: Option<Parameter<Type>>,
     pub param_pos: Option<usize>,
     pub stmt: Vec<Statement>,
 }
@@ -1860,7 +1860,7 @@ pub struct CatchClause {
 pub enum DestructureField {
     None,
     Expression(Expression),
-    VariableDecl(usize, Parameter),
+    VariableDecl(usize, Parameter<Type>),
 }
 
 impl OptionalCodeLocation for DestructureField {

+ 2 - 2
src/sema/functions.rs

@@ -847,7 +847,7 @@ pub fn resolve_params(
     contract_no: Option<usize>,
     ns: &mut Namespace,
     diagnostics: &mut Diagnostics,
-) -> (Vec<Parameter>, bool) {
+) -> (Vec<Parameter<Type>>, bool) {
     let mut params = Vec::new();
     let mut success = true;
 
@@ -986,7 +986,7 @@ pub fn resolve_returns(
     contract_no: Option<usize>,
     ns: &mut Namespace,
     diagnostics: &mut Diagnostics,
-) -> (Vec<Parameter>, bool) {
+) -> (Vec<Parameter<Type>>, bool) {
     let mut resolved_returns = Vec::new();
     let mut success = true;
 

+ 1 - 1
src/sema/namespace.rs

@@ -1567,7 +1567,7 @@ impl Namespace {
     /// Generate the signature for the given name and parameters; can be used for events and functions.
     ///
     /// Recursive arguments are invalid and default to a signature of `#recursive` to avoid stack overflows.
-    pub fn signature(&self, name: &str, params: &[Parameter]) -> String {
+    pub fn signature(&self, name: &str, params: &[Parameter<Type>]) -> String {
         format!(
             "{}({})",
             name,

+ 3 - 3
src/sema/tags.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use super::ast::{Diagnostic, Namespace, Parameter, Tag};
+use super::ast::{Diagnostic, Namespace, Parameter, Tag, Type};
 use solang_parser::{
     doccomment::{DocComment, DocCommentTag},
     pt,
@@ -12,8 +12,8 @@ pub fn resolve_tags(
     file_no: usize,
     ty: &str,
     tags: &[DocComment],
-    params: Option<&[Parameter]>,
-    returns: Option<&[Parameter]>,
+    params: Option<&[Parameter<Type>]>,
+    returns: Option<&[Parameter<Type>]>,
     bases: Option<Vec<usize>>,
     ns: &mut Namespace,
 ) -> Vec<Tag> {

+ 6 - 6
src/sema/types.rs

@@ -705,8 +705,8 @@ pub fn struct_decl(
     tags: &[DocComment],
     contract_no: Option<usize>,
     ns: &mut Namespace,
-) -> (Vec<Tag>, Vec<Parameter>) {
-    let mut fields: Vec<Parameter> = Vec::new();
+) -> (Vec<Tag>, Vec<Parameter<Type>>) {
+    let mut fields: Vec<Parameter<Type>> = Vec::new();
 
     for field in &def.fields {
         let mut diagnostics = Diagnostics::default();
@@ -805,8 +805,8 @@ fn event_decl(
     tags: &[DocComment],
     contract_no: Option<usize>,
     ns: &mut Namespace,
-) -> (Vec<Tag>, Vec<Parameter>) {
-    let mut fields: Vec<Parameter> = Vec::new();
+) -> (Vec<Tag>, Vec<Parameter<Type>>) {
+    let mut fields: Vec<Parameter<Type>> = Vec::new();
     let mut indexed_fields = 0;
 
     for field in &def.fields {
@@ -929,8 +929,8 @@ fn error_decl(
     tags: &[DocComment],
     contract_no: Option<usize>,
     ns: &mut Namespace,
-) -> (Vec<Tag>, Vec<Parameter>) {
-    let mut fields: Vec<Parameter> = Vec::new();
+) -> (Vec<Tag>, Vec<Parameter<Type>>) {
+    let mut fields: Vec<Parameter<Type>> = Vec::new();
 
     for field in &def.fields {
         let mut diagnostics = Diagnostics::default();

+ 4 - 4
src/sema/variables.rs

@@ -523,10 +523,10 @@ fn collect_parameters(
     name: &Option<pt::Identifier>,
     symtable: &mut Symtable,
     context: &mut ExprContext,
-    params: &mut Vec<Parameter>,
+    params: &mut Vec<Parameter<Type>>,
     expr: &mut Expression,
     ns: &mut Namespace,
-) -> Option<Parameter> {
+) -> Option<Parameter<Type>> {
     match ty {
         Type::Mapping(Mapping {
             key,
@@ -659,12 +659,12 @@ fn collect_parameters(
 /// Build up an ast for the implict accessor function for public state variables.
 fn accessor_body(
     expr: Expression,
-    param: Parameter,
+    param: Parameter<Type>,
     constant: bool,
     symtable: &mut Symtable,
     context: &mut ExprContext,
     ns: &mut Namespace,
-) -> (Vec<Statement>, Vec<Parameter>) {
+) -> (Vec<Statement>, Vec<Parameter<Type>>) {
     // This could be done in codegen rather than sema, however I am not sure how we would implement
     // that. After building up the parameter/returns list for the implicit function, we have done almost
     // all the work already for building the body (see `expr` and `param` above). So, we would need to

+ 10 - 5
src/sema/yul/ast.rs

@@ -46,7 +46,12 @@ pub enum YulExpression {
     ConstantVariable(pt::Loc, Type, Option<usize>, usize),
     StorageVariable(pt::Loc, Type, usize, usize),
     BuiltInCall(pt::Loc, YulBuiltInFunction, Vec<YulExpression>),
-    FunctionCall(pt::Loc, usize, Vec<YulExpression>, Arc<Vec<Parameter>>),
+    FunctionCall(
+        pt::Loc,
+        usize,
+        Vec<YulExpression>,
+        Arc<Vec<Parameter<Type>>>,
+    ),
     SuffixAccess(pt::Loc, Box<YulExpression>, YulSuffix),
 }
 
@@ -127,8 +132,8 @@ impl CodeLocation for YulExpression {
 pub struct YulFunction {
     pub loc: pt::Loc,
     pub name: String,
-    pub params: Arc<Vec<Parameter>>,
-    pub returns: Arc<Vec<Parameter>>,
+    pub params: Arc<Vec<Parameter<Type>>>,
+    pub returns: Arc<Vec<Parameter<Type>>>,
     pub body: YulBlock,
     pub symtable: Symtable,
     pub parent_sol_func: Option<usize>,
@@ -142,11 +147,11 @@ impl FunctionAttributes for YulFunction {
         &self.symtable
     }
 
-    fn get_parameters(&self) -> &Vec<Parameter> {
+    fn get_parameters(&self) -> &Vec<Parameter<Type>> {
         &self.params
     }
 
-    fn get_returns(&self) -> &Vec<Parameter> {
+    fn get_returns(&self) -> &Vec<Parameter<Type>> {
         &self.returns
     }
 }

+ 1 - 1
src/sema/yul/expression.rs

@@ -478,7 +478,7 @@ pub(crate) fn resolve_function_call(
 
 /// Check if the provided argument is compatible with the declared parameters of a function.
 fn check_function_argument(
-    parameter: &Parameter,
+    parameter: &Parameter<Type>,
     argument: &YulExpression,
     function_table: &FunctionsTable,
     ns: &mut Namespace,

+ 13 - 19
src/sema/yul/functions.rs

@@ -18,8 +18,8 @@ use std::sync::Arc;
 /// resolving the function's body
 pub struct FunctionHeader {
     pub id: pt::Identifier,
-    pub params: Arc<Vec<Parameter>>,
-    pub returns: Arc<Vec<Parameter>>,
+    pub params: Arc<Vec<Parameter<Type>>>,
+    pub returns: Arc<Vec<Parameter<Type>>>,
     pub function_no: usize,
     called: bool,
 }
@@ -72,18 +72,6 @@ impl FunctionsTable {
         None
     }
 
-    pub fn get_params_returns_func_no(
-        &self,
-        name: &str,
-    ) -> (Arc<Vec<Parameter>>, Arc<Vec<Parameter>>, usize) {
-        let header = self.find(name).unwrap();
-        (
-            header.params.clone(),
-            header.returns.clone(),
-            header.function_no,
-        )
-    }
-
     pub fn get(&self, index: usize) -> Option<&FunctionHeader> {
         if let Some(func_data) = self.lookup.get_index(index - self.offset) {
             Some(func_data.1)
@@ -95,8 +83,8 @@ impl FunctionsTable {
     pub fn add_function_header(
         &mut self,
         id: &pt::Identifier,
-        params: Vec<Parameter>,
-        returns: Vec<Parameter>,
+        params: Vec<Parameter<Type>>,
+        returns: Vec<Parameter<Type>>,
     ) -> Option<Diagnostic> {
         if let Some(func) = self.find(&id.name) {
             return Some(Diagnostic {
@@ -152,8 +140,11 @@ impl FunctionsTable {
 }
 
 /// Resolve the parameters of a function declaration
-fn process_parameters(parameters: &[pt::YulTypedIdentifier], ns: &mut Namespace) -> Vec<Parameter> {
-    let mut params: Vec<Parameter> = Vec::with_capacity(parameters.len());
+fn process_parameters(
+    parameters: &[pt::YulTypedIdentifier],
+    ns: &mut Namespace,
+) -> Vec<Parameter<Type>> {
+    let mut params: Vec<Parameter<Type>> = Vec::with_capacity(parameters.len());
     for item in parameters {
         let ty = match &item.ty {
             Some(identifier) => {
@@ -249,7 +240,10 @@ pub(crate) fn resolve_function_definition(
         context.yul_function = prev_yul_function;
     });
 
-    let (params, returns, func_no) = functions_table.get_params_returns_func_no(&func_def.id.name);
+    let function_header = functions_table.find(&func_def.id.name).unwrap();
+    let params = function_header.params.clone();
+    let returns = function_header.returns.clone();
+    let func_no = function_header.function_no;
 
     for item in &*params {
         let pos = symtable.exclusive_add(

+ 2 - 0
tests/lir.rs

@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: Apache-2.0
+mod lir_tests;

+ 1549 - 0
tests/lir_tests/convert_lir.rs

@@ -0,0 +1,1549 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::ffi::OsStr;
+
+use solang::{
+    codegen::codegen,
+    file_resolver::FileResolver,
+    lir::{converter::Converter, printer::Printer},
+    parse_and_resolve,
+    sema::ast::Namespace,
+    Target,
+};
+
+use crate::stringfy_lir;
+
+fn new_file_resolver(src: &str) -> FileResolver {
+    let mut cache = FileResolver::default();
+    cache.set_file_contents("test.sol", src.to_string());
+    cache
+}
+
+fn print_lir_str(src: &str, cfg_no: usize, target: Target) {
+    let mut resolver = new_file_resolver(src);
+    let mut ns: Namespace = parse_and_resolve(OsStr::new("test.sol"), &mut resolver, target);
+    // print diagnostics
+    if !ns.diagnostics.is_empty() {
+        ns.print_diagnostics_in_plain(&resolver, false);
+    }
+    codegen(&mut ns, &Default::default());
+    let contract = ns.contracts.get(0).unwrap();
+    let cfg = contract.cfg.get(cfg_no).unwrap();
+
+    let converter = Converter::new(&ns, cfg);
+    let lir = converter.get_lir();
+
+    let printer = Printer::new(&lir.vartable);
+
+    let result = stringfy_lir!(printer, &lir);
+    println!("{}", result);
+}
+
+fn assert_lir_str_eq_by_name(src: &str, cfg_name: &str, expected: &str, target: Target) {
+    let mut resolver = new_file_resolver(src);
+    let mut ns: Namespace = parse_and_resolve(OsStr::new("test.sol"), &mut resolver, target);
+    // print diagnostics
+    if !ns.diagnostics.is_empty() {
+        ns.print_diagnostics_in_plain(&resolver, false);
+    }
+    codegen(&mut ns, &Default::default());
+    let contract = ns.contracts.get(0).unwrap();
+    let cfg = contract
+        .cfg
+        .iter()
+        .filter(|cfg| cfg.name == cfg_name)
+        .last()
+        .unwrap();
+
+    let converter = Converter::new(&ns, cfg);
+    let lir = converter.get_lir();
+
+    let printer = Printer::new(&lir.vartable);
+
+    let result = stringfy_lir!(printer, &lir);
+    assert_eq!(result.trim(), expected);
+
+    let re = regex::Regex::new(r"%temp\.ssa_ir\.\d+ =").unwrap();
+    let mut temp_vars = Vec::new();
+    for cap in re.captures_iter(result.as_str()) {
+        temp_vars.push(cap[0].to_string());
+    }
+    // check if there are duplicated temp variables
+    let mut temp_vars_clone = temp_vars.clone();
+    temp_vars_clone.dedup();
+
+    // assert length equal
+    assert_eq!(temp_vars.len(), temp_vars_clone.len());
+}
+
+fn assert_lir_str_eq(src: &str, cfg_no: usize, expected: &str, target: Target) {
+    let mut resolver = new_file_resolver(src);
+    let mut ns: Namespace = parse_and_resolve(OsStr::new("test.sol"), &mut resolver, target);
+    // print diagnostics
+    if !ns.diagnostics.is_empty() {
+        ns.print_diagnostics_in_plain(&resolver, false);
+    }
+    codegen(&mut ns, &Default::default());
+    let contract = ns.contracts.get(0).unwrap();
+    let cfg = contract.cfg.get(cfg_no).unwrap();
+
+    let converter = Converter::new(&ns, cfg);
+    let lir = converter.get_lir();
+
+    let printer = Printer::new(&lir.vartable);
+
+    let result = stringfy_lir!(printer, &lir);
+    assert_eq!(result.trim(), expected);
+
+    let re = regex::Regex::new(r"%temp\.ssa_ir\.\d+ =").unwrap();
+    let mut temp_vars = Vec::new();
+    for cap in re.captures_iter(result.as_str()) {
+        temp_vars.push(cap[0].to_string());
+    }
+    // check if there are duplicated temp variables
+    let mut temp_vars_clone = temp_vars.clone();
+    temp_vars_clone.dedup();
+
+    // assert length equal
+    assert_eq!(temp_vars.len(), temp_vars_clone.len());
+}
+
+fn assert_solana_lir_str_eq(src: &str, cfg_no: usize, expected: &str) {
+    assert_lir_str_eq(src, cfg_no, expected, Target::Solana);
+}
+
+fn assert_polkadot_lir_str_eq(src: &str, cfg_no: usize, expected: &str) {
+    assert_lir_str_eq(src, cfg_no, expected, Target::default_polkadot());
+}
+
+#[test]
+fn test_convert_lir() {
+    let src = r#"
+contract dynamicarray {
+    function test() public pure {
+        int64[] memory a = new int64[](3);
+        a[0] = 1;
+        a[1] = 2;
+        a[2] = 3;
+        a.push(4);
+
+        assert(a.length == 4);
+    }
+}"#;
+
+    print_lir_str(src, 0, Target::Solana);
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 dynamicarray::dynamicarray::function::test ():
+block#0 entry:
+    uint32 %array_length.temp.1 = 3;
+    ptr<int64[]> %a = alloc ptr<int64[]>[uint32(3)];
+    uint32 %index.temp.3 = 0;
+    bool %temp.ssa_ir.9 = uint32(0) (u)>= uint32(3);
+    cbr bool(%temp.ssa_ir.9) block#1 else block#2;
+
+block#1 out_of_bounds:
+    assert_failure;
+
+block#2 in_bounds:
+    int64 %temp.2 = 1;
+    ptr<int64> %temp.ssa_ir.10 = ptr<int64[]>(%a)[uint32(0)];
+    store int64(1) to ptr<int64>(%temp.ssa_ir.10);
+    uint32 %index.temp.5 = 1;
+    bool %temp.ssa_ir.11 = uint32(1) (u)>= uint32(3);
+    cbr bool(%temp.ssa_ir.11) block#3 else block#4;
+
+block#3 out_of_bounds:
+    assert_failure;
+
+block#4 in_bounds:
+    int64 %temp.4 = 2;
+    ptr<int64> %temp.ssa_ir.12 = ptr<int64[]>(%a)[uint32(1)];
+    store int64(2) to ptr<int64>(%temp.ssa_ir.12);
+    uint32 %index.temp.7 = 2;
+    bool %temp.ssa_ir.13 = uint32(2) (u)>= uint32(3);
+    cbr bool(%temp.ssa_ir.13) block#5 else block#6;
+
+block#5 out_of_bounds:
+    assert_failure;
+
+block#6 in_bounds:
+    int64 %temp.6 = 3;
+    ptr<int64> %temp.ssa_ir.14 = ptr<int64[]>(%a)[uint32(2)];
+    store int64(3) to ptr<int64>(%temp.ssa_ir.14);
+    int64 %temp.8 = push_mem ptr<int64[]>(%a) int64(4);
+    uint32 %array_length.temp.1 = 4;
+    bool %temp.ssa_ir.15 = uint32(4) == uint32(4);
+    cbr bool(%temp.ssa_ir.15) block#7 else block#8;
+
+block#7 noassert:
+    return;
+
+block#8 doassert:
+    assert_failure;"#,
+    );
+}
+
+#[test]
+fn test_bool_exprs() {
+    let cfg_no = 1;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+			enum State {
+				Running,
+				Sleeping,
+				Waiting,
+				Stopped,
+				Zombie,
+				StateCount
+			}
+			State state;
+			int32 pid;
+			int32 constant first_pid = 1;
+			constructor(int32 _pid) {
+				pid = _pid;
+			}
+			function is_zombie_reaper() public view returns (bool) {
+				return (pid == first_pid && state != State.Zombie);
+			}
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#3 test::test::function::is_zombie_reaper () returns (bool):
+block#0 entry:
+    int32 %temp.3 = load_storage uint32(20);
+    bool %and.temp.4 = false;
+    bool %temp.ssa_ir.6 = int32(%temp.3) == int32(1);
+    cbr bool(%temp.ssa_ir.6) block#1 else block#2;
+
+block#1 and_right_side:
+    uint8 %temp.5 = load_storage uint32(16);
+    bool %and.temp.4 = uint8(%temp.5) != uint8(4);
+    br block#2;
+
+block#2 and_end:
+    return bool(%and.temp.4);"#,
+    )
+}
+
+#[test]
+fn test_cast() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+			int32 constant first_pid = 1;
+			function systemd_pid() public pure returns (uint32) {
+				return uint32(first_pid);
+			}
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 test::test::function::systemd_pid () returns (uint32):
+block#0 entry:
+    uint32 %temp.ssa_ir.1 = (cast int32(1) to uint32);
+    return uint32(%temp.ssa_ir.1);"#,
+    )
+}
+
+#[test]
+fn test_arithmetic_exprs() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+			function celcius2fahrenheit(int32 celcius) pure public returns (int32) {
+				int32 fahrenheit = celcius * 9 / 5 + 32;
+				return fahrenheit;
+			}
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 test::test::function::celcius2fahrenheit__int32 (int32) returns (int32):
+block#0 entry:
+    int32 %celcius = int32(arg#0);
+    int32 %temp.ssa_ir.4 = int32(%celcius) * int32(9);
+    int32 %temp.ssa_ir.3 = int32(%temp.ssa_ir.4) / int32(5);
+    int32 %fahrenheit = int32(%temp.ssa_ir.3) + int32(32);
+    return int32(%fahrenheit);"#,
+    )
+}
+
+#[test]
+fn test_arithmetic_exprs_1() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+			function byte8reverse(bytes8 input) public pure returns (bytes8 out) {
+				out = ((input << 56) & hex"ff00_0000_0000_0000") |
+						((input << 40) & hex"00ff_0000_0000_0000") |
+						((input << 24) & hex"0000_ff00_0000_0000") |
+						((input <<  8) & hex"0000_00ff_0000_0000") |
+						((input >>  8) & hex"0000_0000_ff00_0000") |
+						((input >> 24) & hex"0000_0000_00ff_0000") |
+						((input >> 40) & hex"0000_0000_0000_ff00") |
+						((input >> 56) & hex"0000_0000_0000_00ff");
+			}
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 test::test::function::byte8reverse__bytes8 (bytes8) returns (bytes8):
+block#0 entry:
+    bytes8 %input = bytes8(arg#0);
+    bytes8 %out = bytes8 hex"00_00_00_00_00_00_00_00";
+    bytes8 %temp.ssa_ir.9 = bytes8(%input) << bytes8(56);
+    bytes8 %temp.ssa_ir.10 = bytes8 hex"ff_00_00_00_00_00_00_00";
+    bytes8 %temp.ssa_ir.8 = bytes8(%temp.ssa_ir.9) & bytes8(%temp.ssa_ir.10);
+    bytes8 %temp.ssa_ir.12 = bytes8(%input) << bytes8(40);
+    bytes8 %temp.ssa_ir.13 = bytes8 hex"00_ff_00_00_00_00_00_00";
+    bytes8 %temp.ssa_ir.11 = bytes8(%temp.ssa_ir.12) & bytes8(%temp.ssa_ir.13);
+    bytes8 %temp.ssa_ir.7 = bytes8(%temp.ssa_ir.8) | bytes8(%temp.ssa_ir.11);
+    bytes8 %temp.ssa_ir.15 = bytes8(%input) << bytes8(24);
+    bytes8 %temp.ssa_ir.16 = bytes8 hex"00_00_ff_00_00_00_00_00";
+    bytes8 %temp.ssa_ir.14 = bytes8(%temp.ssa_ir.15) & bytes8(%temp.ssa_ir.16);
+    bytes8 %temp.ssa_ir.6 = bytes8(%temp.ssa_ir.7) | bytes8(%temp.ssa_ir.14);
+    bytes8 %temp.ssa_ir.18 = bytes8(%input) << bytes8(8);
+    bytes8 %temp.ssa_ir.19 = bytes8 hex"00_00_00_ff_00_00_00_00";
+    bytes8 %temp.ssa_ir.17 = bytes8(%temp.ssa_ir.18) & bytes8(%temp.ssa_ir.19);
+    bytes8 %temp.ssa_ir.5 = bytes8(%temp.ssa_ir.6) | bytes8(%temp.ssa_ir.17);
+    bytes8 %temp.ssa_ir.21 = bytes8(%input) (u)>> bytes8(8);
+    bytes8 %temp.ssa_ir.22 = bytes8 hex"00_00_00_00_ff_00_00_00";
+    bytes8 %temp.ssa_ir.20 = bytes8(%temp.ssa_ir.21) & bytes8(%temp.ssa_ir.22);
+    bytes8 %temp.ssa_ir.4 = bytes8(%temp.ssa_ir.5) | bytes8(%temp.ssa_ir.20);
+    bytes8 %temp.ssa_ir.24 = bytes8(%input) (u)>> bytes8(24);
+    bytes8 %temp.ssa_ir.25 = bytes8 hex"00_00_00_00_00_ff_00_00";
+    bytes8 %temp.ssa_ir.23 = bytes8(%temp.ssa_ir.24) & bytes8(%temp.ssa_ir.25);
+    bytes8 %temp.ssa_ir.3 = bytes8(%temp.ssa_ir.4) | bytes8(%temp.ssa_ir.23);
+    bytes8 %temp.ssa_ir.27 = bytes8(%input) (u)>> bytes8(40);
+    bytes8 %temp.ssa_ir.28 = bytes8 hex"00_00_00_00_00_00_ff_00";
+    bytes8 %temp.ssa_ir.26 = bytes8(%temp.ssa_ir.27) & bytes8(%temp.ssa_ir.28);
+    bytes8 %temp.ssa_ir.2 = bytes8(%temp.ssa_ir.3) | bytes8(%temp.ssa_ir.26);
+    bytes8 %temp.ssa_ir.30 = bytes8(%input) (u)>> bytes8(56);
+    bytes8 %temp.ssa_ir.31 = bytes8 hex"00_00_00_00_00_00_00_ff";
+    bytes8 %temp.ssa_ir.29 = bytes8(%temp.ssa_ir.30) & bytes8(%temp.ssa_ir.31);
+    bytes8 %out = bytes8(%temp.ssa_ir.2) | bytes8(%temp.ssa_ir.29);
+    return bytes8(%out);"#,
+    )
+}
+
+#[test]
+fn test_for_loop() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+            enum State {
+                Running,
+                Sleeping,
+                Waiting,
+                Stopped,
+                Zombie,
+                StateCount
+            }
+            function get_pid_state(uint64 _pid) pure private returns (State) {
+                uint64 n = 8;
+                for (uint16 i = 1; i < 10; ++i) {
+                    if ((i % 3) == 0) {
+                        n *= _pid / uint64(i);
+                    } else {
+                        n /= 3;
+                    }
+                }
+        
+                return State(n % uint64(State.StateCount));
+            }
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"private function sol#2 test::test::function::get_pid_state__uint64 (uint64) returns (uint8):
+block#0 entry:
+    uint64 %_pid = uint64(arg#0);
+    uint64 %n = 8;
+    uint16 %i = 1;
+    br block#2;
+
+block#1 body:
+    uint16 %temp.ssa_ir.6 = uint16(%i) (u)% uint16(3);
+    bool %temp.ssa_ir.5 = uint16(%temp.ssa_ir.6) == uint16(0);
+    cbr bool(%temp.ssa_ir.5) block#5 else block#6;
+
+block#2 cond:
+    bool %temp.ssa_ir.7 = uint16(%i) (u)< uint16(10);
+    cbr bool(%temp.ssa_ir.7) block#1 else block#4;
+
+block#3 next:
+    uint16 %temp.4 = uint16(%i) + uint16(1);
+    uint16 %i = uint16(%temp.4);
+    br block#2;
+
+block#4 endfor:
+    uint64 %temp.ssa_ir.9 = uint64(%n) (u)% uint64(5);
+    uint8 %temp.ssa_ir.8 = (trunc uint64(%temp.ssa_ir.9) to uint8);
+    return uint8(%temp.ssa_ir.8);
+
+block#5 then:
+    uint64 %temp.ssa_ir.11 = (zext uint16(%i) to uint64);
+    uint64 %temp.ssa_ir.10 = uint64(%_pid) (u)/ uint64(%temp.ssa_ir.11);
+    uint64 %n = uint64(%n) * uint64(%temp.ssa_ir.10);
+    br block#7;
+
+block#6 else:
+    uint64 %n = uint64(%n) (u)/ uint64(3);
+    br block#7;
+
+block#7 endif:
+    br block#3;"#,
+    )
+}
+
+#[test]
+fn test_nested_if_blocks() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+            enum suit { club, diamonds, hearts, spades }
+            enum value { two, three, four, five, six, seven, eight, nine, ten, jack, queen, king, ace }
+            struct card {
+                value v;
+                suit s;
+            }
+			function score_card(card memory c) public pure returns (uint32 score) {
+                if (c.s == suit.hearts) {
+                    if (c.v == value.ace) {
+                        score = 14;
+                    }
+                    if (c.v == value.king) {
+                        score = 13;
+                    }
+                    if (c.v == value.queen) {
+                        score = 12;
+                    }
+                    if (c.v == value.jack) {
+                        score = 11;
+                    }
+                }
+                // all others score 0
+            }
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 test::test::function::score_card__test.card (ptr<struct.0>) returns (uint32):
+block#0 entry:
+    ptr<struct.0> %c = ptr<struct.0>(arg#0);
+    uint32 %score = 0;
+    ptr<uint8> %temp.ssa_ir.4 = access ptr<struct.0>(%c) member 1;
+    uint8 %temp.ssa_ir.3 = *ptr<uint8>(%temp.ssa_ir.4);
+    bool %temp.ssa_ir.2 = uint8(%temp.ssa_ir.3) == uint8(2);
+    cbr bool(%temp.ssa_ir.2) block#1 else block#2;
+
+block#1 then:
+    ptr<uint8> %temp.ssa_ir.7 = access ptr<struct.0>(%c) member 0;
+    uint8 %temp.ssa_ir.6 = *ptr<uint8>(%temp.ssa_ir.7);
+    bool %temp.ssa_ir.5 = uint8(%temp.ssa_ir.6) == uint8(12);
+    cbr bool(%temp.ssa_ir.5) block#3 else block#4;
+
+block#2 endif:
+    return uint32(%score);
+
+block#3 then:
+    uint32 %score = 14;
+    br block#4;
+
+block#4 endif:
+    ptr<uint8> %temp.ssa_ir.10 = access ptr<struct.0>(%c) member 0;
+    uint8 %temp.ssa_ir.9 = *ptr<uint8>(%temp.ssa_ir.10);
+    bool %temp.ssa_ir.8 = uint8(%temp.ssa_ir.9) == uint8(11);
+    cbr bool(%temp.ssa_ir.8) block#5 else block#6;
+
+block#5 then:
+    uint32 %score = 13;
+    br block#6;
+
+block#6 endif:
+    ptr<uint8> %temp.ssa_ir.13 = access ptr<struct.0>(%c) member 0;
+    uint8 %temp.ssa_ir.12 = *ptr<uint8>(%temp.ssa_ir.13);
+    bool %temp.ssa_ir.11 = uint8(%temp.ssa_ir.12) == uint8(10);
+    cbr bool(%temp.ssa_ir.11) block#7 else block#8;
+
+block#7 then:
+    uint32 %score = 12;
+    br block#8;
+
+block#8 endif:
+    ptr<uint8> %temp.ssa_ir.16 = access ptr<struct.0>(%c) member 0;
+    uint8 %temp.ssa_ir.15 = *ptr<uint8>(%temp.ssa_ir.16);
+    bool %temp.ssa_ir.14 = uint8(%temp.ssa_ir.15) == uint8(9);
+    cbr bool(%temp.ssa_ir.14) block#9 else block#10;
+
+block#9 then:
+    uint32 %score = 11;
+    br block#10;
+
+block#10 endif:
+    br block#2;"#,
+    )
+}
+
+#[test]
+fn test_init_struct() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+		contract test {
+            enum suit { club, diamonds, hearts, spades }
+            enum value { two, three, four, five, six, seven, eight, nine, ten, jack, queen, king, ace }
+            struct card {
+                value v;
+                suit s;
+            }
+			function ace_of_spaces() public pure returns (card memory) {
+                return card({s: suit.spades, v: value.ace });
+            }
+		}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 test::test::function::ace_of_spaces () returns (ptr<struct.0>):
+block#0 entry:
+    ptr<struct.0> %temp.ssa_ir.1 = struct { uint8(12), uint8(3) };
+    return ptr<struct.0>(%temp.ssa_ir.1);"#,
+    )
+}
+
+#[test]
+fn test_account_access() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"
+contract Foo {
+    @account(oneAccount)
+    @signer(mySigner)
+    @mutableAccount(otherAccount)
+    @mutableSigner(otherSigner)
+    function bar() external returns (uint64) {
+        assert(tx.accounts.mySigner.is_signer);
+        assert(tx.accounts.otherSigner.is_signer);
+        assert(tx.accounts.otherSigner.is_writable);
+        assert(tx.accounts.otherAccount.is_writable);
+
+        tx.accounts.otherAccount.data[0] = 0xca;
+        tx.accounts.otherSigner.data[1] = 0xfe;
+
+        return tx.accounts.oneAccount.lamports;
+    }
+}"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 Foo::Foo::function::bar () returns (uint64):
+block#0 entry:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.12 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.1 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.12)[uint32(1)];
+    ptr<bool> %temp.ssa_ir.14 = access ptr<struct.SolAccountInfo>(%temp.1) member 5;
+    bool %temp.ssa_ir.13 = *ptr<bool>(%temp.ssa_ir.14);
+    cbr bool(%temp.ssa_ir.13) block#1 else block#2;
+
+block#1 noassert:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.15 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.2 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.15)[uint32(3)];
+    ptr<bool> %temp.ssa_ir.17 = access ptr<struct.SolAccountInfo>(%temp.2) member 5;
+    bool %temp.ssa_ir.16 = *ptr<bool>(%temp.ssa_ir.17);
+    cbr bool(%temp.ssa_ir.16) block#3 else block#4;
+
+block#2 doassert:
+    assert_failure;
+
+block#3 noassert:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.18 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.3 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.18)[uint32(3)];
+    ptr<bool> %temp.ssa_ir.20 = access ptr<struct.SolAccountInfo>(%temp.3) member 6;
+    bool %temp.ssa_ir.19 = *ptr<bool>(%temp.ssa_ir.20);
+    cbr bool(%temp.ssa_ir.19) block#5 else block#6;
+
+block#4 doassert:
+    assert_failure;
+
+block#5 noassert:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.21 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.4 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.21)[uint32(2)];
+    ptr<bool> %temp.ssa_ir.23 = access ptr<struct.SolAccountInfo>(%temp.4) member 6;
+    bool %temp.ssa_ir.22 = *ptr<bool>(%temp.ssa_ir.23);
+    cbr bool(%temp.ssa_ir.22) block#7 else block#8;
+
+block#6 doassert:
+    assert_failure;
+
+block#7 noassert:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.24 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.6 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.24)[uint32(2)];
+    uint32 %index.temp.7 = 0;
+    ptr<slice<bytes1>> %temp.ssa_ir.28 = access ptr<struct.SolAccountInfo>(%temp.6) member 2;
+    ptr<slice<bytes1>> %temp.ssa_ir.27 = *ptr<slice<bytes1>>(%temp.ssa_ir.28);
+    uint32 %temp.ssa_ir.26 = builtin: ArrayLength(ptr<slice<bytes1>>(%temp.ssa_ir.27));
+    bool %temp.ssa_ir.25 = uint32(0) (u)>= uint32(%temp.ssa_ir.26);
+    cbr bool(%temp.ssa_ir.25) block#9 else block#10;
+
+block#8 doassert:
+    assert_failure;
+
+block#9 out_of_bounds:
+    assert_failure;
+
+block#10 in_bounds:
+    bytes1 %temp.5 = 202;
+    ptr<slice<bytes1>> %temp.ssa_ir.31 = access ptr<struct.SolAccountInfo>(%temp.6) member 2;
+    ptr<slice<bytes1>> %temp.ssa_ir.30 = *ptr<slice<bytes1>>(%temp.ssa_ir.31);
+    ptr<bytes1> %temp.ssa_ir.29 = ptr<slice<bytes1>>(%temp.ssa_ir.30)[uint32(%index.temp.7)];
+    store bytes1(202) to ptr<bytes1>(%temp.ssa_ir.29);
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.32 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.9 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.32)[uint32(3)];
+    uint32 %index.temp.10 = 1;
+    ptr<slice<bytes1>> %temp.ssa_ir.36 = access ptr<struct.SolAccountInfo>(%temp.9) member 2;
+    ptr<slice<bytes1>> %temp.ssa_ir.35 = *ptr<slice<bytes1>>(%temp.ssa_ir.36);
+    uint32 %temp.ssa_ir.34 = builtin: ArrayLength(ptr<slice<bytes1>>(%temp.ssa_ir.35));
+    bool %temp.ssa_ir.33 = uint32(1) (u)>= uint32(%temp.ssa_ir.34);
+    cbr bool(%temp.ssa_ir.33) block#11 else block#12;
+
+block#11 out_of_bounds:
+    assert_failure;
+
+block#12 in_bounds:
+    bytes1 %temp.8 = 254;
+    ptr<slice<bytes1>> %temp.ssa_ir.39 = access ptr<struct.SolAccountInfo>(%temp.9) member 2;
+    ptr<slice<bytes1>> %temp.ssa_ir.38 = *ptr<slice<bytes1>>(%temp.ssa_ir.39);
+    ptr<bytes1> %temp.ssa_ir.37 = ptr<slice<bytes1>>(%temp.ssa_ir.38)[uint32(%index.temp.10)];
+    store bytes1(254) to ptr<bytes1>(%temp.ssa_ir.37);
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.40 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.11 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.40)[uint32(0)];
+    ptr<ptr<uint64>> %temp.ssa_ir.43 = access ptr<struct.SolAccountInfo>(%temp.11) member 1;
+    ptr<uint64> %temp.ssa_ir.42 = *ptr<ptr<uint64>>(%temp.ssa_ir.43);
+    uint64 %temp.ssa_ir.41 = *ptr<uint64>(%temp.ssa_ir.42);
+    return uint64(%temp.ssa_ir.41);"#,
+    )
+}
+
+#[test]
+fn test_assertion_using_require() {
+    let src = r#"contract Test {
+        function test(int32 num) public {
+            require(num > 10, "sesa");
+        }
+    }"#;
+
+    assert_polkadot_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#3 Test::Test::function::test__int32 (int32):
+block#0 entry:
+    int32 %num = int32(arg#0);
+    bool %temp.ssa_ir.1 = int32(%num) > int32(10);
+    cbr bool(%temp.ssa_ir.1) block#1 else block#2;
+
+block#1 noassert:
+    return;
+
+block#2 doassert:
+    ptr<slice<bytes1>> %temp.ssa_ir.2 = alloc ptr<slice<bytes1>>[uint32(9)] {08, c3, 79, a0, 10, 73, 65, 73, 61};
+    assert_failure ptr<slice<bytes1>>(%temp.ssa_ir.2);"#,
+    );
+}
+
+#[test]
+fn test_call_1() {
+    let src = r#"contract Test {
+        function test(int32 num) public {
+            check(num);
+        }
+
+        function check(int32 num) pure internal {
+            require(num > 10, "sesa");
+        }
+    }"#;
+
+    assert_polkadot_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#3 Test::Test::function::test__int32 (int32):
+block#0 entry:
+    int32 %num = int32(arg#0);
+     = call function#1(int32(%num));
+    return;"#,
+    )
+}
+
+#[test]
+fn test_return_data_and_return_code() {
+    let src = r#"contract Test {
+        function test() public {
+        }
+    }"#;
+
+    assert_lir_str_eq_by_name(
+        src,
+        "polkadot_call_dispatch",
+        r#"private function none polkadot_call_dispatch (ptr<uint8>, uint32, uint128, ptr<uint32>):
+block#0 entry:
+    uint32 %input_len.temp.4 = uint32(arg#1);
+    uint128 %value.temp.5 = uint128(arg#2);
+    ptr<uint8> %input_ptr.temp.6 = ptr<uint8>(arg#0);
+    bool %temp.ssa_ir.8 = uint32(%input_len.temp.4) (u)< uint32(4);
+    cbr bool(%temp.ssa_ir.8) block#2 else block#1;
+
+block#1 start_dispatch:
+    uint32 %selector.temp.7 = builtin: ReadFromBuffer(ptr<uint8>(%input_ptr.temp.6), uint32(0));
+    uint32 %temp.ssa_ir.9 = uint32(arg#3);
+    store uint32(%selector.temp.7) to uint32(%temp.ssa_ir.9);
+    switch uint32(%selector.temp.7):
+    case:    uint32(1845340408) => block#3
+    default: block#2;
+
+block#2 fb_or_recv:
+    return_code "function selector invalid";
+
+block#3 func_0_dispatch:
+    bool %temp.ssa_ir.10 = uint128(%value.temp.5) (u)> uint128(0);
+    cbr bool(%temp.ssa_ir.10) block#4 else block#5;
+
+block#4 func_0_got_value:
+    assert_failure;
+
+block#5 func_0_no_value:
+     = call function#0();
+    ptr<struct.vector<uint8>> %temp.ssa_ir.11 = alloc ptr<struct.vector<uint8>>[uint32(0)];
+    return_data ptr<struct.vector<uint8>>(%temp.ssa_ir.11) of length uint32(0);"#,
+        Target::default_polkadot(),
+    )
+}
+
+#[test]
+fn test_value_transfer() {
+    let src = r#"contract Test {
+        function transfer(address payable addr, uint128 amount) public {
+            addr.transfer(amount);
+        }
+    }"#;
+
+    // Should be Polkadot
+    assert_polkadot_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#3 Test::Test::function::transfer__address_uint128 (uint8[32], uint128):
+block#0 entry:
+    uint8[32] %addr = uint8[32](arg#0);
+    uint128 %amount = uint128(arg#1);
+    uint128 %temp.ssa_ir.3 = (cast uint128(%amount) to uint128);
+    uint32 %success.temp.2 = value_transfer uint128(%temp.ssa_ir.3) to uint8[32](%addr);
+    bool %temp.ssa_ir.4 = uint32(%success.temp.2) == uint32(0);
+    cbr bool(%temp.ssa_ir.4) block#1 else block#2;
+
+block#1 transfer_success:
+    return;
+
+block#2 transfer_fail:
+    assert_failure;"#,
+    )
+}
+
+#[test]
+fn test_array_type_dynamic_storage() {
+    let cfg_no = 0;
+
+    // read the example.sol file
+    let src = r#"contract s {
+        int64[] a;
+    
+        function test() public {
+            // push takes a single argument with the item to be added
+            a.push(128);
+            // push with no arguments adds 0
+            a.push();
+            // now we have two elements in our array, 128 and 0
+            assert(a.length == 2);
+            a[0] |= 64;
+            // pop removes the last element
+            a.pop();
+            // you can assign the return value of pop
+            int64 v = a.pop();
+            assert(v == 192);
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        cfg_no,
+        r#"public function sol#2 s::s::function::test ():
+block#0 entry:
+    int64 %temp.1 = push_storage uint32(16) int64(128);
+    int64 %temp.2 = push_storage uint32(16) int64(0);
+    uint32 %temp.ssa_ir.9 = storage_arr_len(uint32(16));
+    bool %temp.ssa_ir.8 = uint32(%temp.ssa_ir.9) == uint32(2);
+    cbr bool(%temp.ssa_ir.8) block#1 else block#2;
+
+block#1 noassert:
+    uint256 %index.temp.3 = 0;
+    uint32 %temp.ssa_ir.12 = storage_arr_len(uint32(16));
+    uint256 %temp.ssa_ir.11 = (zext uint32(%temp.ssa_ir.12) to uint256);
+    bool %temp.ssa_ir.10 = uint256(0) (u)>= uint256(%temp.ssa_ir.11);
+    cbr bool(%temp.ssa_ir.10) block#3 else block#4;
+
+block#2 doassert:
+    assert_failure;
+
+block#3 out_of_bounds:
+    assert_failure;
+
+block#4 in_bounds:
+    uint32 %temp.ssa_ir.14 = (trunc uint256(%index.temp.3) to uint32);
+    storage_ptr<int64> %temp.ssa_ir.13 = uint32(16)[uint32(%temp.ssa_ir.14)];
+    int64 %temp.4 = load_storage storage_ptr<int64>(%temp.ssa_ir.13);
+    uint256 %index.temp.6 = 0;
+    uint32 %temp.ssa_ir.17 = storage_arr_len(uint32(16));
+    uint256 %temp.ssa_ir.16 = (zext uint32(%temp.ssa_ir.17) to uint256);
+    bool %temp.ssa_ir.15 = uint256(0) (u)>= uint256(%temp.ssa_ir.16);
+    cbr bool(%temp.ssa_ir.15) block#5 else block#6;
+
+block#5 out_of_bounds:
+    assert_failure;
+
+block#6 in_bounds:
+    int64 %temp.5 = int64(%temp.4) | int64(64);
+    uint32 %temp.ssa_ir.19 = (trunc uint256(%index.temp.6) to uint32);
+    storage_ptr<int64> %temp.ssa_ir.18 = uint32(16)[uint32(%temp.ssa_ir.19)];
+    set_storage storage_ptr<int64>(%temp.ssa_ir.18) int64(%temp.5);
+    pop_storage uint32(16);
+    int64 %temp.7 = pop_storage uint32(16);
+    int64 %v = int64(%temp.7);
+    bool %temp.ssa_ir.20 = int64(%v) == int64(192);
+    cbr bool(%temp.ssa_ir.20) block#7 else block#8;
+
+block#7 noassert:
+    return;
+
+block#8 doassert:
+    assert_failure;"#,
+    )
+}
+
+#[test]
+fn test_switch() {
+    let src = r#"contract foo {
+        function test(uint x) public pure {
+            uint256 yy=0;
+            assembly {
+                let y := 5
+                switch and(x, 3)
+                    case 0 {
+                        y := 5
+                        x := 5
+                    }
+                    case 1 {
+                        y := 7
+                        x := 9
+                    }
+                    case 3 {
+                        y := 10
+                        x := 80
+                    }
+            }
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 foo::foo::function::test__uint256 (uint256):
+block#0 entry:
+    uint256 %x = uint256(arg#0);
+    uint256 %y = 5;
+    uint256 %temp.ssa_ir.3 = uint256(%x) & uint256(3);
+    switch uint256(%temp.ssa_ir.3):
+    case:    uint256(0) => block#2, 
+    case:    uint256(1) => block#3, 
+    case:    uint256(3) => block#4
+    default: block#1;
+
+block#1 end_switch:
+    return;
+
+block#2 case_0:
+    uint256 %y = 5;
+    uint256 %x = 5;
+    br block#1;
+
+block#3 case_1:
+    uint256 %y = 7;
+    uint256 %x = 9;
+    br block#1;
+
+block#4 case_2:
+    uint256 %y = 10;
+    uint256 %x = 80;
+    br block#1;"#,
+    )
+}
+
+#[test]
+fn test_keccak256() {
+    let src = r#"contract b {
+        struct user {
+            bool exists;
+            address addr;
+        }
+        mapping(string => user) users;
+        function add(string name, address addr) public {
+            // This construction is not recommended, because it requires two hash calculations.
+            // See the tip below.
+            users[name].exists = true;
+            users[name].addr = addr;
+        }
+    }"#;
+
+    assert_polkadot_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#3 b::b::function::add__string_address (ptr<struct.vector<uint8>>, uint8[32]):
+block#0 entry:
+    ptr<struct.vector<uint8>> %name = ptr<struct.vector<uint8>>(arg#0);
+    uint8[32] %addr = uint8[32](arg#1);
+    bool %temp.2 = true;
+    storage_ptr<mapping(struct.vector<uint8> => struct.0)> %temp.ssa_ir.5 = keccak256(uint256(0), ptr<struct.vector<uint8>>(%name));
+    uint256 %temp.ssa_ir.4 = storage_ptr<mapping(struct.vector<uint8> => struct.0)>(%temp.ssa_ir.5) (of)+ uint256(0);
+    set_storage uint256(%temp.ssa_ir.4) true;
+    uint8[32] %temp.3 = uint8[32](arg#1);
+    storage_ptr<mapping(struct.vector<uint8> => struct.0)> %temp.ssa_ir.7 = keccak256(uint256(0), ptr<struct.vector<uint8>>(%name));
+    uint256 %temp.ssa_ir.6 = storage_ptr<mapping(struct.vector<uint8> => struct.0)>(%temp.ssa_ir.7) (of)+ uint256(1);
+    set_storage uint256(%temp.ssa_ir.6) uint8[32](%temp.3);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_internal_function_cfg() {
+    let src = r#"contract A {
+      function foo(uint a) internal returns (uint) {
+          return a+2;
+      }
+
+      function bar(uint b) public returns (uint) {
+        function (uint) returns (uint) fPtr = foo;
+        return fPtr(b);
+      }
+    }"#;
+
+    assert_polkadot_lir_str_eq(
+        src,
+        1,
+        r#"public function sol#4 A::A::function::bar__uint256 (uint256) returns (uint256):
+block#0 entry:
+    uint256 %b = uint256(arg#0);
+    ptr<function (uint256) returns (uint256)> %temp.ssa_ir.6 = function#0;
+    ptr<function (uint256) returns (uint256)> %fPtr = (cast ptr<function (uint256) returns (uint256)>(%temp.ssa_ir.6) to ptr<function (uint256) returns (uint256)>);
+    uint256 %.temp.5 = call ptr<function (uint256) returns (uint256)>(%fPtr)(uint256(%b));
+    return uint256(%.temp.5);"#,
+    )
+}
+
+#[test]
+fn test_sign_ext() {
+    let src = r#"contract Test {
+        function test(int32 a) public returns (int128) {
+            return int128(a);
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Test::Test::function::test__int32 (int32) returns (int128):
+block#0 entry:
+    int32 %a = int32(arg#0);
+    int128 %temp.ssa_ir.2 = (sext int32(%a) to int128);
+    return int128(%temp.ssa_ir.2);"#,
+    )
+}
+
+#[test]
+fn test_string_compare() {
+    let src = r#"contract Test {
+        function test(string a, string b) public returns (bool) {
+            return a == b;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Test::Test::function::test__string_string (ptr<struct.vector<uint8>>, ptr<struct.vector<uint8>>) returns (bool):
+block#0 entry:
+    ptr<struct.vector<uint8>> %a = ptr<struct.vector<uint8>>(arg#0);
+    ptr<struct.vector<uint8>> %b = ptr<struct.vector<uint8>>(arg#1);
+    bool %temp.ssa_ir.3 = strcmp(ptr<struct.vector<uint8>>(%a), ptr<struct.vector<uint8>>(%b));
+    return bool(%temp.ssa_ir.3);"#,
+    )
+}
+
+#[test]
+fn test_const_array() {
+    let src = r#"contract Test {
+        uint32[5] constant arr = [1, 2, 3, 4, 5];
+        function test() public returns (uint32) {
+            return arr[0];
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Test::Test::function::test () returns (uint32):
+block#0 entry:
+    uint32 %index.temp.1 = 0;
+    bool %temp.ssa_ir.2 = uint32(0) (u)>= uint32(5);
+    cbr bool(%temp.ssa_ir.2) block#1 else block#2;
+
+block#1 out_of_bounds:
+    assert_failure;
+
+block#2 in_bounds:
+    ptr<uint32[5]> %temp.ssa_ir.5 = const ptr<uint32[5]> [uint32(1), uint32(2), uint32(3), uint32(4), uint32(5)];
+    ptr<uint32> %temp.ssa_ir.4 = ptr<uint32[5]>(%temp.ssa_ir.5)[uint32(0)];
+    uint32 %temp.ssa_ir.3 = *ptr<uint32>(%temp.ssa_ir.4);
+    return uint32(%temp.ssa_ir.3);"#,
+    )
+}
+
+#[test]
+fn test_account_meta() {
+    let src = r#"import 'solana';
+    contract creator {
+        @mutableSigner(data_account_to_initialize)
+        @mutableSigner(payer)
+        function create_with_metas() external {
+            AccountMeta[3] metas = [
+                AccountMeta({
+                    pubkey: tx.accounts.data_account_to_initialize.key,
+                    is_signer: true, 
+                    is_writable: true}),
+                AccountMeta({
+                    pubkey: tx.accounts.payer.key,
+                    is_signer: true,
+                    is_writable: true}),
+                AccountMeta({
+                    pubkey: address"11111111111111111111111111111111",
+                    is_writable: false,
+                    is_signer: false})
+            ];
+            Child.new{accounts: metas}();        
+            Child.use_metas{accounts: []}();
+        }
+    }
+    @program_id("Chi1d5XD6nTAp2EyaNGqMxZzUjh6NvhXRxbGHP3D1RaT")
+    contract Child {
+        @payer(payer)
+        constructor() {
+            print("In child constructor");
+        }
+        function use_metas() pure public {
+            print("I am using metas");
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 creator::creator::function::create_with_metas ():
+block#0 entry:
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.14 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.10 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.14)[uint32(0)];
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.15 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.11 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.15)[uint32(1)];
+    ptr<ptr<uint8[32]>> %temp.ssa_ir.18 = access ptr<struct.SolAccountInfo>(%temp.10) member 0;
+    ptr<uint8[32]> %temp.ssa_ir.17 = *ptr<ptr<uint8[32]>>(%temp.ssa_ir.18);
+    ptr<struct.SolAccountMeta> %temp.ssa_ir.16 = struct { ptr<uint8[32]>(%temp.ssa_ir.17), true, true };
+    ptr<ptr<uint8[32]>> %temp.ssa_ir.21 = access ptr<struct.SolAccountInfo>(%temp.11) member 0;
+    ptr<uint8[32]> %temp.ssa_ir.20 = *ptr<ptr<uint8[32]>>(%temp.ssa_ir.21);
+    ptr<struct.SolAccountMeta> %temp.ssa_ir.19 = struct { ptr<uint8[32]>(%temp.ssa_ir.20), true, true };
+    ptr<uint8[32]> %temp.ssa_ir.23 = &uint8[32](0);
+    ptr<struct.SolAccountMeta> %temp.ssa_ir.22 = struct { ptr<uint8[32]>(%temp.ssa_ir.23), false, false };
+    ptr<struct.SolAccountMeta[3]> %metas = ptr<struct.SolAccountMeta[3]> [ptr<struct.SolAccountMeta>(%temp.ssa_ir.16), ptr<struct.SolAccountMeta>(%temp.ssa_ir.19), ptr<struct.SolAccountMeta>(%temp.ssa_ir.22)];
+    ptr<struct.vector<uint8>> %abi_encoded.temp.12 = alloc ptr<struct.vector<uint8>>[uint32(8)];
+    bytes8 %temp.ssa_ir.24 = bytes8 hex"87_2c_cd_c6_19_01_48_bc";
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.12) offset:uint32(0) value:bytes8(%temp.ssa_ir.24);
+    _ = call_ext [regular] address:uint8[32](78642644713358252795404932596995255556623171005675782810573618728006773308276) payload:ptr<struct.vector<uint8>>(%abi_encoded.temp.12) value:uint64(0) gas:uint64(0) accounts:ptr<struct.SolAccountMeta[3]>(%metas) seeds:_ contract_no:1, function_no:3 flags:_;
+    ptr<struct.vector<uint8>> %abi_encoded.temp.13 = alloc ptr<struct.vector<uint8>>[uint32(8)];
+    bytes8 %temp.ssa_ir.25 = bytes8 hex"97_f8_3c_a2_18_9f_26_9d";
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.13) offset:uint32(0) value:bytes8(%temp.ssa_ir.25);
+    _ = call_ext [regular] address:uint8[32](78642644713358252795404932596995255556623171005675782810573618728006773308276) payload:ptr<struct.vector<uint8>>(%abi_encoded.temp.13) value:uint64(0) gas:uint64(0) accounts:none seeds:_ contract_no:1, function_no:4 flags:_;
+    return;"#,
+    )
+}
+
+#[test]
+fn test_constructor() {
+    let src = r#"
+    contract B {
+      A aa;
+      function test(uint a) public {
+          aa = new A(a);
+      }
+    }
+    contract A {
+      uint public a;
+      constructor(uint b) {
+        a = b;
+      }
+    }
+    "#;
+
+    assert_polkadot_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#3 B::B::function::test__uint256 (uint256):
+block#0 entry:
+    uint256 %a = uint256(arg#0);
+    ptr<struct.vector<uint8>> %abi_encoded.temp.18 = alloc ptr<struct.vector<uint8>>[uint32(36)];
+    uint32 %temp.ssa_ir.20 = uint32 hex"58_16_c4_25";
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.18) offset:uint32(0) value:uint32(%temp.ssa_ir.20);
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.18) offset:uint32(4) value:uint256(%a);
+    uint32 %success.temp.17, uint8[32] %temp.16 = constructor(no: 5, contract_no:1) salt:_ value:_ gas:uint64(0) address:_ seeds:_ encoded-buffer:ptr<struct.vector<uint8>>(%abi_encoded.temp.18) accounts:absent
+    switch uint32(%success.temp.17):
+    case:    uint32(0) => block#1, 
+    case:    uint32(2) => block#2
+    default: block#3;
+
+block#1 ret_success:
+    uint8[32] %temp.19 = uint8[32](%temp.16);
+    set_storage uint256(0) uint8[32](%temp.19);
+    return;
+
+block#2 ret_bubble:
+    ptr<struct.vector<uint8>> %temp.ssa_ir.21 = (extern_call_ret_data);
+    assert_failure ptr<struct.vector<uint8>>(%temp.ssa_ir.21);
+
+block#3 ret_no_data:
+    assert_failure;"#,
+    )
+}
+
+#[test]
+fn test_external_fn() {
+    let src = r#"contract Testing {
+        function testExternalFunction(
+            bytes memory buffer
+        ) public view returns (bytes8, address) {
+            function(uint8) external returns (int8) fPtr = abi.decode(
+                buffer,
+                (function(uint8) external returns (int8))
+            );
+            return (fPtr.selector, fPtr.address);
+        }
+    }
+    "#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Testing::Testing::function::testExternalFunction__bytes (ptr<struct.vector<uint8>>) returns (bytes8, uint8[32]):
+block#0 entry:
+    ptr<struct.vector<uint8>> %buffer = ptr<struct.vector<uint8>>(arg#0);
+    uint32 %temp.4 = builtin: ArrayLength(ptr<struct.vector<uint8>>(%buffer));
+    bool %temp.ssa_ir.5 = uint32(40) (u)< uint32(%temp.4);
+    cbr bool(%temp.ssa_ir.5) block#1 else block#2;
+
+block#1 inbounds:
+    bool %temp.ssa_ir.6 = uint32(40) (u)< uint32(%temp.4);
+    cbr bool(%temp.ssa_ir.6) block#3 else block#4;
+
+block#2 out_of_bounds:
+    assert_failure;
+
+block#3 not_all_bytes_read:
+    assert_failure;
+
+block#4 buffer_read:
+    uint64 %temp.ssa_ir.9 = builtin: ReadFromBuffer(ptr<struct.vector<uint8>>(%buffer), uint32(0));
+    uint8[32] %temp.ssa_ir.10 = builtin: ReadFromBuffer(ptr<struct.vector<uint8>>(%buffer), uint32(8));
+    ptr<struct.ExternalFunction> %temp.ssa_ir.8 = struct { uint64(%temp.ssa_ir.9), uint8[32](%temp.ssa_ir.10) };
+    ptr<struct.ExternalFunction> %temp.ssa_ir.7 = (cast ptr<struct.ExternalFunction>(%temp.ssa_ir.8) to ptr<struct.ExternalFunction>);
+    ptr<struct.ExternalFunction> %fPtr = (cast ptr<struct.ExternalFunction>(%temp.ssa_ir.7) to ptr<struct.ExternalFunction>);
+    ptr<uint64> %temp.ssa_ir.13 = access ptr<struct.ExternalFunction>(%fPtr) member 0;
+    uint64 %temp.ssa_ir.12 = *ptr<uint64>(%temp.ssa_ir.13);
+    bytes8 %temp.ssa_ir.11 = (cast uint64(%temp.ssa_ir.12) to bytes8);
+    ptr<uint8[32]> %temp.ssa_ir.15 = access ptr<struct.ExternalFunction>(%fPtr) member 1;
+    uint8[32] %temp.ssa_ir.14 = *ptr<uint8[32]>(%temp.ssa_ir.15);
+    return bytes8(%temp.ssa_ir.11), uint8[32](%temp.ssa_ir.14);"#,
+    )
+}
+
+#[test]
+fn test_push_pop_mem() {
+    let src = r#"
+    contract foo {
+        struct s {
+            int32 f1;
+            bool f2;
+        }
+        function test() public {
+            s[] bar = new s[](0);
+            s memory n = bar.push();
+            bar.pop();
+        }
+    }
+    "#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 foo::foo::function::test ():
+block#0 entry:
+    uint32 %array_length.temp.2 = 0;
+    ptr<struct.0[]> %bar = alloc ptr<struct.0[]>[uint32(0)];
+    ptr<struct.0> %temp.ssa_ir.5 = struct {  };
+    ptr<struct.0> %temp.3 = push_mem ptr<struct.0[]>(%bar) ptr<struct.0>(%temp.ssa_ir.5);
+    uint32 %array_length.temp.2 = 1;
+    ptr<struct.0> %temp.4 = pop_mem ptr<struct.0[]>(%bar);
+    uint32 %array_length.temp.2 = 0;
+    return;"#,
+    )
+}
+
+#[test]
+fn test_math() {
+    let src = r#"contract store {
+        enum enum_bar { bar1, bar2, bar3, bar4 }
+        uint64 u64;
+        uint32 u32;
+        int16 i16;
+        int256 i256;
+        uint256 u256;
+        string str;
+        bytes bs = hex"b00b1e";
+        bytes4 fixedbytes;
+        enum_bar bar;
+        function do_ops() public {
+            unchecked {
+                // u64 will overflow to 1
+                u64 += 2;
+                u32 &= 0xffff;
+                // another overflow
+                i16 += 1;
+                i256 ^= 1;
+                u256 *= 600;
+                str = "";
+                bs[1] = 0xff;
+                // make upper case
+                fixedbytes |= 0x20202020;
+                bar = enum_bar.bar4;
+            }
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 store::store::function::do_ops ():
+block#0 entry:
+    uint64 %temp.0 = load_storage uint32(16);
+    uint64 %temp.1 = uint64(%temp.0) (of)+ uint64(2);
+    set_storage uint32(16) uint64(%temp.1);
+    uint32 %temp.2 = load_storage uint32(24);
+    uint32 %temp.3 = uint32(%temp.2) & uint32(65535);
+    set_storage uint32(24) uint32(%temp.3);
+    int16 %temp.4 = load_storage uint32(28);
+    int16 %temp.5 = int16(%temp.4) (of)+ int16(1);
+    set_storage uint32(28) int16(%temp.5);
+    int256 %temp.6 = load_storage uint32(32);
+    int256 %temp.7 = int256(%temp.6) ^ int256(1);
+    set_storage uint32(32) int256(%temp.7);
+    uint256 %temp.8 = load_storage uint32(64);
+    uint64 %temp.ssa_ir.16 = (trunc uint256(%temp.8) to uint64);
+    uint64 %temp.ssa_ir.15 = uint64(%temp.ssa_ir.16) (of)* uint64(600);
+    uint256 %temp.9 = (zext uint64(%temp.ssa_ir.15) to uint256);
+    set_storage uint32(64) uint256(%temp.9);
+    ptr<struct.vector<uint8>> %temp.10 = alloc ptr<slice<bytes1>>[uint32(0)] {};
+    set_storage uint32(96) ptr<struct.vector<uint8>>(%temp.10);
+    bytes1 %temp.11 = 255;
+    set_storage_bytes uint32(100) offset:uint32(1) value:bytes1(255);
+    bytes4 %temp.12 = load_storage uint32(104);
+    bytes4 %temp.13 = bytes4(%temp.12) | bytes4(538976288);
+    set_storage uint32(104) bytes4(%temp.13);
+    uint8 %temp.14 = 3;
+    set_storage uint32(108) uint8(3);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_byte_cast() {
+    let src = r#"contract Cast {
+        function test(uint256 num) public returns (bytes) {
+            bytes smol_buf = new bytes(num);
+            bytes32 b32 = bytes32(smol_buf);
+            return b32;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Cast::Cast::function::test__uint256 (uint256) returns (ptr<struct.vector<uint8>>):
+block#0 entry:
+    uint256 %num = uint256(arg#0);
+    uint256 %value.temp.4 = uint256(arg#0);
+    bool %temp.ssa_ir.5 = uint256(%value.temp.4) (u)>= uint256(4294967296);
+    cbr bool(%temp.ssa_ir.5) block#1 else block#2;
+
+block#1 out_of_bounds:
+    assert_failure;
+
+block#2 in_bounds:
+    uint32 %temp.ssa_ir.6 = (trunc uint256(%value.temp.4) to uint32);
+    ptr<struct.vector<uint8>> %smol_buf = alloc ptr<struct.vector<uint8>>[uint32(%temp.ssa_ir.6)];
+    bytes32 %b32 = (cast ptr<struct.vector<uint8>>(%smol_buf) to bytes32);
+    ptr<struct.vector<uint8>> %temp.ssa_ir.7 = (cast bytes32(%b32) to ptr<struct.vector<uint8>>);
+    return ptr<struct.vector<uint8>>(%temp.ssa_ir.7);"#,
+    )
+}
+
+#[test]
+fn test_signed_modulo() {
+    let src = r#"contract SignedModulo {
+        function test(int256 a, int256 b) public returns (int256) {
+            int256 c = a % b;
+            return c;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 SignedModulo::SignedModulo::function::test__int256_int256 (int256, int256) returns (int256):
+block#0 entry:
+    int256 %a = int256(arg#0);
+    int256 %b = int256(arg#1);
+    int256 %c = int256(%a) % int256(%b);
+    return int256(%c);"#,
+    )
+}
+
+#[test]
+fn test_compare() {
+    let src = r#"contract example {
+        int16 stored;
+    
+        function func(int256 x) public {
+            if (x < type(int16).min || x > type(int16).max) {
+                revert("value will not fit");
+            }
+    
+            stored = int16(x);
+        }
+    }
+    "#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 example::example::function::func__int256 (int256):
+block#0 entry:
+    int256 %x = int256(arg#0);
+    bool %or.temp.1 = true;
+    bool %temp.ssa_ir.3 = int256(%x) < int256(-32768);
+    cbr bool(%temp.ssa_ir.3) block#2 else block#1;
+
+block#1 or_right_side:
+    bool %or.temp.1 = int256(%x) > int256(32767);
+    br block#2;
+
+block#2 or_end:
+    cbr bool(%or.temp.1) block#3 else block#4;
+
+block#3 then:
+    assert_failure;
+
+block#4 endif:
+    int16 %temp.2 = (trunc int256(%x) to int16);
+    set_storage uint32(16) int16(%temp.2);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_array() {
+    let src = r#"
+    contract C {
+        function testVec() public pure returns (uint32) {
+            uint32[3] vec = [1, 2, 3];
+            return vec.length;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 C::C::function::testVec () returns (uint32):
+block#0 entry:
+    ptr<uint32[3]> %vec = ptr<uint32[3]> [uint32(1), uint32(2), uint32(3)];
+    return uint32(3);"#,
+    )
+}
+
+#[test]
+fn test_clear_storage() {
+    let src = r#"
+    struct S {
+        function() e;
+    }
+
+    contract C {
+        S s;
+        function test() public {
+            delete s.e;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 C::C::function::test ():
+block#0 entry:
+    clear_storage uint32(64);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_call_ext() {
+    let src = r#"
+    contract adult {
+        function test(address id) external {
+            hatchling.new{program_id: id}("luna");
+        }
+    }
+    contract hatchling {
+        string name;
+        constructor(string id) payable {
+            name = id;
+        }
+    }
+    "#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 adult::adult::function::test__address (uint8[32]):
+block#0 entry:
+    uint8[32] %id = uint8[32](arg#0);
+    ptr<struct.vector<uint8>> %temp.ssa_ir.15 = alloc ptr<struct.vector<uint8>>[uint32(4)] {6c, 75, 6e, 61};
+    uint32 %temp.ssa_ir.14 = builtin: ArrayLength(ptr<struct.vector<uint8>>(%temp.ssa_ir.15));
+    uint32 %temp.ssa_ir.13 = uint32(%temp.ssa_ir.14) + uint32(4);
+    uint32 %temp.ssa_ir.12 = uint32(8) + uint32(%temp.ssa_ir.13);
+    ptr<struct.vector<uint8>> %abi_encoded.temp.10 = alloc ptr<struct.vector<uint8>>[uint32(%temp.ssa_ir.12)];
+    bytes8 %temp.ssa_ir.16 = bytes8 hex"87_2c_cd_c6_19_01_48_bc";
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.10) offset:uint32(0) value:bytes8(%temp.ssa_ir.16);
+    ptr<struct.vector<uint8>> %temp.ssa_ir.17 = alloc ptr<struct.vector<uint8>>[uint32(4)] {6c, 75, 6e, 61};
+    uint32 %temp.11 = builtin: ArrayLength(ptr<struct.vector<uint8>>(%temp.ssa_ir.17));
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.10) offset:uint32(8) value:uint32(%temp.11);
+    ptr<struct.vector<uint8>> %temp.ssa_ir.18 = alloc ptr<struct.vector<uint8>>[uint32(4)] {6c, 75, 6e, 61};
+    ptr<uint8> %temp.ssa_ir.19 = ptr_add(ptr<struct.vector<uint8>>(%abi_encoded.temp.10), uint32(12));
+    memcopy ptr<struct.vector<uint8>>(%temp.ssa_ir.18) to ptr<uint8>(%temp.ssa_ir.19) for uint32(%temp.11) bytes;
+    ptr<struct.SolAccountInfo[]> %temp.ssa_ir.25 = builtin: Accounts();
+    ptr<struct.SolAccountInfo> %temp.ssa_ir.24 = ptr<struct.SolAccountInfo[]>(%temp.ssa_ir.25)[uint32(1)];
+    ptr<ptr<uint8[32]>> %temp.ssa_ir.23 = access ptr<struct.SolAccountInfo>(%temp.ssa_ir.24) member 0;
+    ptr<uint8[32]> %temp.ssa_ir.22 = *ptr<ptr<uint8[32]>>(%temp.ssa_ir.23);
+    ptr<struct.SolAccountMeta> %temp.ssa_ir.21 = struct { ptr<uint8[32]>(%temp.ssa_ir.22), true, false };
+    ptr<struct.SolAccountMeta[1]> %temp.ssa_ir.20 = ptr<struct.SolAccountMeta[1]> [ptr<struct.SolAccountMeta>(%temp.ssa_ir.21)];
+    _ = call_ext [regular] address:uint8[32](%id) payload:ptr<struct.vector<uint8>>(%abi_encoded.temp.10) value:uint64(0) gas:uint64(0) accounts:ptr<struct.SolAccountMeta[1]>(%temp.ssa_ir.20) seeds:_ contract_no:1, function_no:3 flags:_;
+    return;"#,
+    )
+}
+
+#[test]
+fn test_emit_event() {
+    let src = r#"
+    contract mytokenEvent {
+        event Debugging(int b);
+    
+        function test() public {
+            emit Debugging(1);
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 mytokenEvent::mytokenEvent::function::test ():
+block#0 entry:
+    ptr<struct.vector<uint8>> %abi_encoded.temp.0 = alloc ptr<struct.vector<uint8>>[uint32(40)];
+    bytes8 %temp.ssa_ir.1 = bytes8 hex"cc_c9_89_03_bd_da_d5_98";
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.0) offset:uint32(0) value:bytes8(%temp.ssa_ir.1);
+    write_buf ptr<struct.vector<uint8>>(%abi_encoded.temp.0) offset:uint32(8) value:int256(1);
+    emit event#0 to topics[], data: ptr<struct.vector<uint8>>(%abi_encoded.temp.0);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_fmt_string_and_print() {
+    let src = r#"contract Test {
+        function test() public {
+            print("Number: {}".format(123));
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Test::Test::function::test ():
+block#0 entry:
+    ptr<struct.vector<uint8>> %temp.ssa_ir.2 = ptr<struct.vector<uint8>> hex"4e_75_6d_62_65_72_3a_20";
+    ptr<struct.vector<uint8>> %temp.ssa_ir.1 = fmt_str(ptr<struct.vector<uint8>>(%temp.ssa_ir.2), uint8(123));
+    print ptr<struct.vector<uint8>>(%temp.ssa_ir.1);
+    return;"#,
+    )
+}
+
+#[test]
+fn test_bitewise_not() {
+    let src = r#"contract Test {
+        function byte_wise_not(bytes14 a) public pure returns (bytes14) {
+            return ~a;
+        }
+    }"#;
+
+    assert_solana_lir_str_eq(
+        src,
+        0,
+        r#"public function sol#2 Test::Test::function::byte_wise_not__bytes14 (bytes14) returns (bytes14):
+block#0 entry:
+    bytes14 %a = bytes14(arg#0);
+    bytes14 %temp.ssa_ir.2 = ~bytes14(%a);
+    return bytes14(%temp.ssa_ir.2);"#,
+    )
+}

+ 1156 - 0
tests/lir_tests/expr_to_string.rs

@@ -0,0 +1,1156 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir_tests::helpers::{
+    binop_expr, bool_literal, identifier, new_lir_type, new_printer, new_vartable, num_literal,
+    set_tmp, unop_expr,
+};
+use crate::num_literal;
+use crate::stringfy_expr;
+use num_bigint::BigInt;
+use solang::codegen::Builtin;
+use solang::lir::expressions::{BinaryOperator, Expression, UnaryOperator};
+use solang::lir::lir_type::{StructType, Type};
+use solang::sema::ast::{self, ArrayLength, FormatArg, StringLocation};
+use solang_parser::pt::Loc;
+
+#[test]
+fn test_stringfy_binary_expr() {
+    let mut v = new_vartable();
+    for i in 0..100 {
+        set_tmp(&mut v, i, Type::Int(16));
+    }
+    let printer = new_printer(&v);
+
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(1),
+                BinaryOperator::Add { overflowing: false },
+                identifier(2)
+            )
+        ),
+        "int16(%temp.ssa_ir.1) + int16(%temp.ssa_ir.2)"
+    );
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(1),
+                BinaryOperator::Add { overflowing: true },
+                identifier(2)
+            )
+        ),
+        "int16(%temp.ssa_ir.1) (of)+ int16(%temp.ssa_ir.2)"
+    );
+
+    // Sub { overflowing: bool },
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(11),
+                BinaryOperator::Sub { overflowing: false },
+                identifier(12)
+            )
+        ),
+        "int16(%temp.ssa_ir.11) - int16(%temp.ssa_ir.12)"
+    );
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(12),
+                BinaryOperator::Sub { overflowing: true },
+                identifier(13)
+            )
+        ),
+        "int16(%temp.ssa_ir.12) (of)- int16(%temp.ssa_ir.13)"
+    );
+
+    // Mul { overflowing: bool },
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(13),
+                BinaryOperator::Mul { overflowing: false },
+                identifier(14)
+            )
+        ),
+        "int16(%temp.ssa_ir.13) * int16(%temp.ssa_ir.14)"
+    );
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(1),
+                BinaryOperator::Mul { overflowing: true },
+                identifier(9)
+            )
+        ),
+        "int16(%temp.ssa_ir.1) (of)* int16(%temp.ssa_ir.9)"
+    );
+
+    // Pow { overflowing: bool },
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                identifier(10),
+                BinaryOperator::Pow { overflowing: true },
+                identifier(11)
+            )
+        ),
+        "int16(%temp.ssa_ir.10) (of)** int16(%temp.ssa_ir.11)"
+    );
+
+    // Div,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::Div, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) / int16(%temp.ssa_ir.2)"
+    );
+
+    // UDiv,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(3), BinaryOperator::UDiv, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.3) (u)/ int16(%temp.ssa_ir.4)"
+    );
+
+    // Mod,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(4), BinaryOperator::Mod, identifier(5))
+        ),
+        "int16(%temp.ssa_ir.4) % int16(%temp.ssa_ir.5)"
+    );
+
+    // UMod,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(2), BinaryOperator::UMod, identifier(3))
+        ),
+        "int16(%temp.ssa_ir.2) (u)% int16(%temp.ssa_ir.3)"
+    );
+
+    // Eq,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(2), BinaryOperator::Eq, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.2) == int16(%temp.ssa_ir.4)"
+    );
+
+    // Neq,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(2), BinaryOperator::Neq, identifier(3))
+        ),
+        "int16(%temp.ssa_ir.2) != int16(%temp.ssa_ir.3)"
+    );
+
+    // Lt,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::Lt, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) < int16(%temp.ssa_ir.2)"
+    );
+
+    // ULt,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::ULt, identifier(0))
+        ),
+        "int16(%temp.ssa_ir.1) (u)< int16(%temp.ssa_ir.0)"
+    );
+
+    // Lte,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::Lte, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) <= int16(%temp.ssa_ir.2)"
+    );
+
+    // ULte,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::ULte, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) (u)<= int16(%temp.ssa_ir.2)"
+    );
+
+    // Gt,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::Gt, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) > int16(%temp.ssa_ir.2)"
+    );
+
+    // UGt,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::UGt, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) (u)> int16(%temp.ssa_ir.2)"
+    );
+
+    // Gte,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::Gte, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) >= int16(%temp.ssa_ir.2)"
+    );
+
+    // UGte,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::UGte, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) (u)>= int16(%temp.ssa_ir.2)"
+    );
+
+    // BitAnd,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(
+                bool_literal(false),
+                BinaryOperator::BitAnd,
+                bool_literal(true)
+            )
+        ),
+        "false & true"
+    );
+
+    // BitOr,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(3), BinaryOperator::BitOr, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.3) | int16(%temp.ssa_ir.4)"
+    );
+
+    // BitXor,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(1), BinaryOperator::BitXor, identifier(2))
+        ),
+        "int16(%temp.ssa_ir.1) ^ int16(%temp.ssa_ir.2)"
+    );
+
+    // Shl,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(3), BinaryOperator::Shl, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.3) << int16(%temp.ssa_ir.4)"
+    );
+
+    // Shr,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(3), BinaryOperator::Shr, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.3) >> int16(%temp.ssa_ir.4)"
+    );
+
+    // UShr,
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &binop_expr(identifier(3), BinaryOperator::UShr, identifier(4))
+        ),
+        "int16(%temp.ssa_ir.3) (u)>> int16(%temp.ssa_ir.4)"
+    );
+}
+
+#[test]
+fn test_stringfy_unary_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Int(16));
+    set_tmp(&mut v, 2, Type::Int(16));
+    set_tmp(&mut v, 4, Type::Int(16));
+    let printer = new_printer(&v);
+
+    // Not,
+    assert_eq!(
+        stringfy_expr!(
+            new_printer(&new_vartable()),
+            &unop_expr(UnaryOperator::Not, bool_literal(true))
+        ),
+        "!true"
+    );
+
+    // Neg { overflowing: bool },
+
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &unop_expr(UnaryOperator::Neg { overflowing: false }, identifier(1))
+        ),
+        "-int16(%temp.ssa_ir.1)"
+    );
+
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &unop_expr(UnaryOperator::Neg { overflowing: true }, identifier(2))
+        ),
+        "(of)-int16(%temp.ssa_ir.2)"
+    );
+
+    // BitNot,
+    assert_eq!(
+        stringfy_expr!(&printer, &unop_expr(UnaryOperator::BitNot, identifier(4))),
+        "~int16(%temp.ssa_ir.4)"
+    );
+}
+
+#[test]
+fn test_stringfy_id_expr() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Int(16));
+
+    let printer = new_printer(&v);
+
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Id {
+                loc: Loc::Codegen,
+                id: 1,
+            }
+        ),
+        "int16(%temp.ssa_ir.1)"
+    );
+}
+
+#[test]
+fn test_stringfy_array_literal_expr() {
+    // let printer = new_printer();
+    let vartab = &new_vartable();
+    let printer = new_printer(vartab);
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ArrayLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Array(
+                    Box::new(Type::Bool),
+                    vec![ast::ArrayLength::Fixed(BigInt::from(2))]
+                )),
+                dimensions: vec![2],
+                values: vec![bool_literal(true), bool_literal(false)],
+            }
+        ),
+        "bool[2] [true, false]"
+    );
+
+    // int array
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ArrayLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Array(
+                    Box::new(Type::Int(8)),
+                    vec![ast::ArrayLength::Fixed(BigInt::from(2))]
+                )),
+                dimensions: vec![2],
+                values: vec![num_literal(1, true, 8), num_literal(2, true, 8)],
+            }
+        ),
+        "int8[2] [int8(1), int8(2)]"
+    );
+
+    // uint array
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ArrayLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Array(
+                    Box::new(Type::Uint(8)),
+                    vec![ast::ArrayLength::Fixed(BigInt::from(2))]
+                )),
+                dimensions: vec![2],
+                values: vec![num_literal!(1), num_literal!(2)],
+            }
+        ),
+        "uint8[2] [uint8(1), uint8(2)]"
+    );
+
+    // 2d int array
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ArrayLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Array(
+                    Box::new(Type::Int(8)),
+                    vec![
+                        ast::ArrayLength::Fixed(BigInt::from(2)),
+                        ast::ArrayLength::Fixed(BigInt::from(2))
+                    ]
+                )),
+                dimensions: vec![2, 2],
+                values: vec![
+                    num_literal(1, true, 8),
+                    num_literal(2, true, 8),
+                    num_literal(3, true, 8),
+                    num_literal(4, true, 8)
+                ],
+            }
+        ),
+        "int8[2][2] [int8(1), int8(2), int8(3), int8(4)]"
+    );
+
+    // 3d int array
+    // for example: int8[2][2][2] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ArrayLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Array(
+                    Box::new(Type::Int(8)),
+                    vec![
+                        ast::ArrayLength::Fixed(BigInt::from(2)),
+                        ast::ArrayLength::Fixed(BigInt::from(2)),
+                        ast::ArrayLength::Fixed(BigInt::from(2))
+                    ]
+                )),
+                dimensions: vec![2, 2, 2],
+                values: vec![
+                    num_literal(1, true, 8),
+                    num_literal(2, true, 8),
+                    num_literal(3, true, 8),
+                    num_literal(4, true, 8),
+                    num_literal(5, true, 8),
+                    num_literal(6, true, 8),
+                    num_literal(7, true, 8),
+                    num_literal(8, true, 8)
+                ],
+            }
+        ),
+        "int8[2][2][2] [int8(1), int8(2), int8(3), int8(4), int8(5), int8(6), int8(7), int8(8)]"
+    );
+
+    assert_eq!(
+        stringfy_expr!(&printer, &Expression::ConstArrayLiteral {
+            loc: Loc::Codegen,
+            ty: new_lir_type(Type::Array(
+                Box::new(Type::Int(8)),
+                vec![
+                    ast::ArrayLength::Fixed(BigInt::from(2)),
+                    ast::ArrayLength::Fixed(BigInt::from(2)),
+                    ast::ArrayLength::Fixed(BigInt::from(2))
+                ]
+            )),
+            dimensions: vec![2, 2, 2],
+            values: vec![
+                num_literal(1, true, 8),
+                num_literal(2, true, 8),
+                num_literal(3, true, 8),
+                num_literal(4, true, 8),
+                num_literal(5, true, 8),
+                num_literal(6, true, 8),
+                num_literal(7, true, 8),
+                num_literal(8, true, 8)
+            ],
+        }
+        ),
+        "const int8[2][2][2] [int8(1), int8(2), int8(3), int8(4), int8(5), int8(6), int8(7), int8(8)]"
+    );
+}
+
+#[test]
+fn test_stringfy_bytes_literal_expr() {
+    // example: bytes4 hex"41_42_43_44";
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::BytesLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Bytes(4)),
+                value: vec![0x41, 0x42, 0x43, 0x44],
+            }
+        ),
+        "bytes4 hex\"41_42_43_44\""
+    );
+}
+
+#[test]
+fn test_stringfy_struct_literal_expr() {
+    /*
+    example:
+    struct S {
+        uint x;
+        uint y;
+    }
+
+    the literal: S(1, 2)
+
+    print: struct { uint8(1), uint8(2) }
+    */
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::StructLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Struct(StructType::UserDefined(0))),
+                values: vec![num_literal!(1, 8), num_literal!(2, 8)],
+            }
+        ),
+        "struct { uint8(1), uint8(2) }"
+    );
+
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::StructLiteral {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Struct(StructType::UserDefined(0))),
+                values: vec![num_literal!(1, 8), bool_literal(false)],
+            }
+        ),
+        "struct { uint8(1), false }"
+    );
+}
+
+#[test]
+fn test_stringfy_cast_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Uint(8));
+    let printer = new_printer(&v);
+
+    // example: uint8(1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Cast {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+                to_ty: new_lir_type(Type::Uint(16)),
+            }
+        ),
+        "(cast uint8(%temp.ssa_ir.1) to uint16)"
+    );
+}
+
+#[test]
+fn test_stringfy_bytes_cast_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Bytes(2));
+    let printer = new_printer(&v);
+
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::BytesCast {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+                to_ty: new_lir_type(Type::Bytes(4)),
+            }
+        ),
+        "(cast bytes2(%temp.ssa_ir.1) to bytes4)"
+    );
+}
+
+#[test]
+fn test_stringfy_sext_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Int(8));
+    let printer = new_printer(&v);
+
+    // example: sign extending a int8 to int16:
+    //          %1 of int8 to int16
+    //          can be written as: (sext %1 to int16)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::SignExt {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+                to_ty: new_lir_type(Type::Int(16)),
+            }
+        ),
+        "(sext int8(%temp.ssa_ir.1) to int16)"
+    );
+}
+
+#[test]
+fn test_stringfy_zext_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Uint(8));
+    let printer = new_printer(&v);
+
+    // example: zero extending a uint8 to uint16:
+    //          %1 of uint8 to uint16
+    //          can be written as: (zext %1 to int16)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::ZeroExt {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+                to_ty: new_lir_type(Type::Uint(16)),
+            }
+        ),
+        "(zext uint8(%temp.ssa_ir.1) to uint16)"
+    );
+}
+
+#[test]
+fn test_stringfy_trunc_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Uint(16));
+    let printer = new_printer(&v);
+
+    // example: truncating a uint16 to uint8:
+    //          %1 of uint16 to uint8
+    //          can be written as: (trunc uint16 %1 to uint8)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Trunc {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+                to_ty: new_lir_type(Type::Uint(8)),
+            }
+        ),
+        "(trunc uint16(%temp.ssa_ir.1) to uint8)"
+    );
+}
+
+#[test]
+fn test_stringfy_alloc_dyn_bytes() {
+    // case1: allocating a dynamic bytes without initializer:
+    //        Solidity: bytes memory a = new bytes(10);
+    //        rhs print: alloc bytes1[10]
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::AllocDynamicBytes {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Bytes(1)),
+                size: Box::new(num_literal!(10)),
+                initializer: None,
+            }
+        ),
+        "alloc bytes1[uint8(10)]"
+    );
+
+    // case2: allocating a dynamic bytes with initializer:
+    //        Solidity: bytes memory a = new bytes(3) { 0x01, 0x02, 0x03 };
+    //        rhs print: alloc bytes1[] {0x01, 0x02, 0x03}
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::AllocDynamicBytes {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Bytes(1)),
+                size: Box::new(num_literal!(3)),
+                initializer: Some(vec![b'\x01', b'\x02', b'\x03']),
+            }
+        ),
+        "alloc bytes1[uint8(3)] {01, 02, 03}"
+    );
+}
+
+// GetRef
+#[test]
+fn test_stringfy_get_ref_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Uint(8));
+    let printer = new_printer(&v);
+
+    // example: &ptr<uint8>(%temp.ssa_ir.1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::GetRef {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+            }
+        ),
+        "&uint8(%temp.ssa_ir.1)"
+    );
+}
+
+// Load
+#[test]
+fn test_stringfy_load_expr() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 1, Type::Ptr(Box::new(Type::Bytes(1))));
+    let printer = new_printer(&v);
+
+    // example: *%1
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Load {
+                loc: Loc::Codegen,
+                operand: Box::new(identifier(1)),
+            }
+        ),
+        "*ptr<bytes1>(%temp.ssa_ir.1)"
+    );
+}
+
+// StructMember
+#[test]
+fn test_stringfy_struct_member_expr() {
+    let mut v = new_vartable();
+    set_tmp(
+        &mut v,
+        1,
+        Type::Ptr(Box::new(Type::Struct(StructType::UserDefined(0)))),
+    );
+    let printer = new_printer(&v);
+
+    // example: uint8 %1->1
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StructMember {
+                loc: Loc::Codegen,
+                member: 3,
+                operand: Box::new(identifier(1)),
+            }
+        ),
+        "access ptr<struct.0>(%temp.ssa_ir.1) member 3"
+    );
+}
+
+// Subscript
+#[test]
+fn test_stringfy_subscript_expr() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        1,
+        Type::Ptr(Box::new(Type::Array(
+            Box::new(Type::Uint(8)),
+            vec![ArrayLength::Fixed(BigInt::from(2))],
+        ))),
+    );
+    set_tmp(
+        &mut v,
+        2,
+        Type::Ptr(Box::new(Type::Array(
+            Box::new(Type::Uint(8)),
+            vec![ArrayLength::Dynamic],
+        ))),
+    );
+    set_tmp(
+        &mut v,
+        3,
+        Type::Ptr(Box::new(Type::Array(
+            Box::new(Type::Uint(8)),
+            vec![ArrayLength::AnyFixed],
+        ))),
+    );
+    let printer = new_printer(&v);
+
+    // example: ptr<uint8[2]> %1[uint8(0)]
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Subscript {
+                loc: Loc::Codegen,
+                arr: Box::new(identifier(1)),
+                index: Box::new(num_literal!(0)),
+            }
+        ),
+        "ptr<uint8[2]>(%temp.ssa_ir.1)[uint8(0)]"
+    );
+
+    // example: ptr<uint8[]> %1[uint8(0)]
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Subscript {
+                loc: Loc::Codegen,
+                arr: Box::new(identifier(2)),
+                index: Box::new(num_literal!(0)),
+            }
+        ),
+        "ptr<uint8[]>(%temp.ssa_ir.2)[uint8(0)]"
+    );
+
+    // example: ptr<uint8[?]> %1[uint8(0)]
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Subscript {
+                loc: Loc::Codegen,
+                arr: Box::new(identifier(3)),
+                index: Box::new(num_literal!(0)),
+            }
+        ),
+        "ptr<uint8[?]>(%temp.ssa_ir.3)[uint8(0)]"
+    );
+}
+
+// AdvancePointer
+#[test]
+fn test_stringfy_advance_pointer_expr() {
+    let mut v = new_vartable();
+    set_tmp(
+        &mut v,
+        1,
+        Type::Ptr(Box::new(Type::Struct(StructType::UserDefined(0)))),
+    );
+    set_tmp(&mut v, 2, Type::Uint(8));
+    let printer = new_printer(&v);
+
+    // example: ptr_add(%1, %2)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::AdvancePointer {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                pointer: Box::new(identifier(1)),
+                bytes_offset: Box::new(identifier(2)),
+            }
+        ),
+        "ptr_add(ptr<struct.0>(%temp.ssa_ir.1), uint8(%temp.ssa_ir.2))"
+    );
+
+    // example: ptr_add(%1, uint8(1))
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::AdvancePointer {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                pointer: Box::new(identifier(1)),
+                bytes_offset: Box::new(num_literal!(1)),
+            }
+        ),
+        "ptr_add(ptr<struct.0>(%temp.ssa_ir.1), uint8(1))"
+    );
+}
+
+// FunctionArg
+#[test]
+fn test_stringfy_function_arg_expr() {
+    // example: the 2nd arg of type uint8
+    //          (uint8 arg#2)
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::FunctionArg {
+                loc: Loc::Codegen,
+                ty: new_lir_type(Type::Uint(8)),
+                arg_no: 2,
+            }
+        ),
+        "uint8(arg#2)"
+    );
+}
+
+// FormatString
+#[test]
+fn test_stringfy_format_string_expr() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(4));
+    set_tmp(&mut v, 2, Type::Int(16));
+    set_tmp(&mut v, 3, Type::Uint(8));
+    set_tmp(&mut v, 4, Type::Uint(32));
+
+    let printer = new_printer(&v);
+    // case1: spec is empty:
+    //        fmt_str(%1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![(FormatArg::StringLiteral, identifier(1))]
+            }
+        ),
+        "fmt_str(bytes4(%temp.ssa_ir.1))"
+    );
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![(FormatArg::Default, identifier(2))]
+            }
+        ),
+        "fmt_str(int16(%temp.ssa_ir.2))"
+    );
+
+    // case2: spec is binary:
+    //        fmt_str(:b %1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![(FormatArg::Binary, identifier(2))]
+            }
+        ),
+        "fmt_str(:b int16(%temp.ssa_ir.2))"
+    );
+
+    // case3: spec is hex:
+    //        fmt_str(:x int16(%temp.ssa_ir.1))
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![(FormatArg::Hex, identifier(2))]
+            }
+        ),
+        "fmt_str(:x int16(%temp.ssa_ir.2))"
+    );
+
+    // mixed case:
+    // fmt_str(%1, %2, :b %2, :x %3)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::FormatString {
+                loc: Loc::Codegen,
+                args: vec![
+                    (FormatArg::StringLiteral, identifier(1)),
+                    (FormatArg::Default, identifier(2)),
+                    (FormatArg::Binary, identifier(3)),
+                    (FormatArg::Hex, identifier(4))
+                ]
+            }
+        ),
+        "fmt_str(bytes4(%temp.ssa_ir.1), int16(%temp.ssa_ir.2), :b uint8(%temp.ssa_ir.3), :x uint32(%temp.ssa_ir.4))"
+    );
+}
+
+// InternalFunctionCfg
+#[test]
+fn test_stringfy_internal_function_cfg_expr() {
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::InternalFunctionCfg { loc: /*missing from cfg*/ Loc::Codegen, cfg_no: 123 }
+        ),
+        "function#123"
+    );
+}
+
+// Keccak256
+#[test]
+fn test_stringfy_keccak256_expr() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(4));
+    set_tmp(&mut v, 2, Type::Bytes(4));
+
+    let printer = new_printer(&v);
+    // example: keccak256(%1, %2)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Keccak256 {
+                loc: Loc::Codegen,
+                args: vec![identifier(1), identifier(2)],
+            }
+        ),
+        "keccak256(bytes4(%temp.ssa_ir.1), bytes4(%temp.ssa_ir.2))"
+    );
+}
+
+// StringCompare
+#[test]
+fn test_stringfy_string_compare_expr() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(4));
+    set_tmp(&mut v, 2, Type::Bytes(4));
+    set_tmp(&mut v, 3, Type::Bytes(3));
+
+    let printer = new_printer(&v);
+    // case1: strcmp(%1, %2)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringCompare {
+                loc: Loc::Codegen,
+                left: StringLocation::RunTime(Box::new(identifier(1))),
+                right: StringLocation::RunTime(Box::new(identifier(2))),
+            }
+        ),
+        "strcmp(bytes4(%temp.ssa_ir.1), bytes4(%temp.ssa_ir.2))"
+    );
+
+    // case2: strcmp("[97, 98, 99]", %1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringCompare {
+                loc: Loc::Codegen,
+                left: StringLocation::CompileTime(vec![b'a', b'b', b'c']),
+                right: StringLocation::RunTime(Box::new(identifier(3))),
+            }
+        ),
+        "strcmp(\"[97, 98, 99]\", bytes3(%temp.ssa_ir.3))"
+    );
+
+    // case3: strcmp(%1, "[97, 98, 99]")
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringCompare {
+                loc: Loc::Codegen,
+                left: StringLocation::RunTime(Box::new(identifier(3))),
+                right: StringLocation::CompileTime(vec![b'a', b'b', b'c']),
+            }
+        ),
+        "strcmp(bytes3(%temp.ssa_ir.3), \"[97, 98, 99]\")"
+    );
+}
+
+// StringConcat
+#[test]
+fn test_stringfy_string_concat() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(4));
+    set_tmp(&mut v, 2, Type::Bytes(2));
+
+    let printer = new_printer(&v);
+    // case1: strcat(%1, %2)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringConcat {
+                loc: Loc::Codegen,
+                left: StringLocation::RunTime(Box::new(identifier(1))),
+                right: StringLocation::RunTime(Box::new(identifier(2))),
+            }
+        ),
+        "strcat(bytes4(%temp.ssa_ir.1), bytes2(%temp.ssa_ir.2))"
+    );
+    // case2: strcat("[97, 98, 99]", %1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringConcat {
+                loc: Loc::Codegen,
+                left: StringLocation::CompileTime(vec![b'a', b'b', b'c']),
+                right: StringLocation::RunTime(Box::new(identifier(1))),
+            }
+        ),
+        "strcat(\"[97, 98, 99]\", bytes4(%temp.ssa_ir.1))"
+    );
+    // case3: strcat(%1, "[97, 98, 99]")
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StringConcat {
+                loc: Loc::Codegen,
+                left: StringLocation::RunTime(Box::new(identifier(1))),
+                right: StringLocation::CompileTime(vec![b'a', b'b', b'c']),
+            }
+        ),
+        "strcat(bytes4(%temp.ssa_ir.1), \"[97, 98, 99]\")"
+    );
+}
+
+// StorageArrayLength
+#[test]
+fn test_stringfy_storage_array_length() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        1,
+        Type::StoragePtr(
+            false,
+            Box::new(Type::Array(
+                Box::new(Type::Uint(8)),
+                vec![ArrayLength::Dynamic],
+            )),
+        ),
+    );
+
+    let printer = new_printer(&v);
+
+    // example: storage_arr_len(uint8[] %1)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::StorageArrayLength {
+                loc: Loc::Codegen,
+                array: Box::new(identifier(1)),
+            }
+        ),
+        "storage_arr_len(storage_ptr<uint8[]>(%temp.ssa_ir.1))"
+    );
+}
+
+// ReturnData
+#[test]
+fn test_stringfy_return_data() {
+    // example: ret_data
+    assert_eq!(
+        stringfy_expr!(
+            &new_printer(&new_vartable()),
+            &Expression::ReturnData { loc: Loc::Codegen }
+        ),
+        "(extern_call_ret_data)"
+    );
+}
+
+#[test]
+fn test_stringfy_builtin() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Int(16));
+    set_tmp(&mut v, 2, Type::Int(16));
+
+    let printer = new_printer(&v);
+    // example: builtin "addmod"(%1, %2, 0x100)
+    assert_eq!(
+        stringfy_expr!(
+            &printer,
+            &Expression::Builtin {
+                loc: Loc::Codegen,
+                kind: Builtin::AddMod,
+                args: vec![identifier(1), identifier(2), num_literal!(0x100, 16)],
+            }
+        ),
+        "builtin: AddMod(int16(%temp.ssa_ir.1), int16(%temp.ssa_ir.2), uint16(256))"
+    );
+}

+ 134 - 0
tests/lir_tests/helpers.rs

@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use indexmap::IndexMap;
+use num_bigint::BigInt;
+use solang::{
+    lir::{
+        expressions::{BinaryOperator, Expression, Operand, UnaryOperator},
+        lir_type::{LIRType, Type},
+        printer::Printer,
+        vartable::Vartable,
+    },
+    sema::ast,
+};
+use solang_parser::pt::Loc;
+
+pub(crate) fn binop_expr(left: Operand, op: BinaryOperator, right: Operand) -> Expression {
+    Expression::BinaryExpr {
+        loc: Loc::Codegen,
+        operator: op,
+        left: Box::new(left),
+        right: Box::new(right),
+    }
+}
+
+pub(crate) fn unop_expr(op: UnaryOperator, right: Operand) -> Expression {
+    Expression::UnaryExpr {
+        loc: Loc::Codegen,
+        operator: op,
+        right: Box::new(right),
+    }
+}
+
+pub(crate) fn num_literal(value: i32, signed: bool, width: u16) -> Operand {
+    Operand::NumberLiteral {
+        value: BigInt::from(value),
+        ty: if signed {
+            new_lir_type(Type::Int(width))
+        } else {
+            new_lir_type(Type::Uint(width))
+        },
+        loc: Loc::Codegen,
+    }
+}
+
+#[macro_export]
+macro_rules! stringfy_expr {
+    ($printer:expr, $expr:expr) => {{
+        let mut buffer = Vec::new();
+        $printer.print_expr(&mut buffer, $expr);
+        String::from_utf8(buffer).expect("Failed to convert to string")
+    }};
+}
+
+#[macro_export]
+macro_rules! stringfy_insn {
+    ($printer:expr, $insn:expr) => {{
+        let mut buf = Vec::new();
+        $printer.print_instruction(&mut buf, $insn);
+        String::from_utf8(buf).unwrap()
+    }};
+}
+
+#[macro_export]
+macro_rules! stringfy_lir {
+    ($printer:expr, $lir:expr) => {{
+        let mut buf = Vec::new();
+        $printer.print_lir(&mut buf, $lir);
+        String::from_utf8(buf).unwrap()
+    }};
+}
+
+#[macro_export]
+macro_rules! num_literal {
+    ($value: expr, $width: expr) => {
+        num_literal($value, false, $width)
+    };
+    ($value: expr) => {
+        num_literal($value, false, 8)
+    };
+    // error
+    () => {
+        panic!("invalid number literal")
+    };
+}
+
+#[macro_export]
+macro_rules! new_printer {
+    () => {
+        new_printer(&new_vartable())
+    };
+}
+
+pub(crate) fn bool_literal(value: bool) -> Operand {
+    Operand::BoolLiteral {
+        value,
+        loc: Loc::Codegen,
+    }
+}
+
+pub(crate) fn identifier(id: usize) -> Operand {
+    Operand::Id {
+        id,
+        loc: Loc::Codegen,
+    }
+}
+
+pub fn new_printer(v: &Vartable) -> Printer {
+    Printer::new(v)
+}
+
+pub fn new_vartable() -> Vartable {
+    Vartable {
+        vars: IndexMap::new(),
+        args: IndexMap::new(),
+        next_id: 0,
+    }
+}
+
+pub fn set_tmp(v: &mut Vartable, id: usize, ty: Type) {
+    v.set_tmp(
+        id,
+        LIRType {
+        lir_type: ty,
+        ast_type: /*mock value*/ ast::Type::Void,
+    },
+    );
+}
+
+pub fn new_lir_type(ty: Type) -> LIRType {
+    LIRType {
+        lir_type: ty,
+        ast_type: /*mock value*/ ast::Type::Void,
+    }
+}

+ 684 - 0
tests/lir_tests/insn_to_string.rs

@@ -0,0 +1,684 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir_tests::helpers::{identifier, new_printer, new_vartable, num_literal, set_tmp};
+use crate::{num_literal, stringfy_insn};
+use num_bigint::BigInt;
+use solang::codegen::cfg;
+use solang::lir::expressions::{BinaryOperator, Expression};
+use solang::lir::instructions::Instruction;
+use solang::lir::lir_type::{InternalCallTy, PhiInput, StructType, Type};
+use solang::sema::ast::{ArrayLength, CallTy};
+use solang_parser::pt::Loc;
+
+#[test]
+fn test_stringfy_nop_insn() {
+    assert_eq!(
+        stringfy_insn!(&new_printer(&new_vartable()), &Instruction::Nop),
+        "nop;"
+    );
+}
+
+// ReturnData
+#[test]
+fn test_stringfy_returndata_insn() {
+    let mut v = new_vartable();
+    set_tmp(&mut v, 0, Type::Bytes(1));
+    let printer = new_printer(&v);
+
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::ReturnData {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                data: identifier(0),
+                data_len: num_literal!(1),
+            }
+        ),
+        "return_data bytes1(%temp.ssa_ir.0) of length uint8(1);"
+    );
+}
+
+// ReturnCode
+#[test]
+fn test_stringfy_returncode_insn() {
+    assert_eq!(
+        stringfy_insn!(
+            &new_printer(&new_vartable()),
+            &Instruction::ReturnCode {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                code: cfg::ReturnCode::AbiEncodingInvalid,
+            }
+        ),
+        "return_code \"abi encoding invalid\";"
+    );
+
+    assert_eq!(
+        stringfy_insn!(
+            &new_printer(&new_vartable()),
+            &Instruction::ReturnCode {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                code: cfg::ReturnCode::AccountDataTooSmall,
+            }
+        ),
+        "return_code \"account data too small\";"
+    );
+}
+
+// Set
+#[test]
+fn test_stringfy_set_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 121, Type::Uint(8));
+    set_tmp(&mut v, 122, Type::Uint(8));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Set {
+                loc: Loc::Codegen,
+                res: 122,
+                expr: Expression::BinaryExpr {
+                    loc: Loc::Codegen,
+                    operator: BinaryOperator::Mul { overflowing: true },
+                    left: Box::new(num_literal!(1)),
+                    right: Box::new(identifier(121))
+                }
+            }
+        ),
+        "uint8 %temp.ssa_ir.122 = uint8(1) (of)* uint8(%temp.ssa_ir.121);"
+    );
+}
+
+// Store
+#[test]
+fn test_stringfy_store_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 0, Type::Ptr(Box::new(Type::Uint(8))));
+    set_tmp(&mut v, 1, Type::Uint(8));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Store {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                dest: identifier(0),
+                data: identifier(1),
+            }
+        ),
+        "store uint8(%temp.ssa_ir.1) to ptr<uint8>(%temp.ssa_ir.0);"
+    );
+
+    // store a number
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Store {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                dest: identifier(0),
+                data: num_literal!(1),
+            }
+        ),
+        "store uint8(1) to ptr<uint8>(%temp.ssa_ir.0);"
+    );
+}
+
+// PushMemory
+#[test]
+fn test_stringfy_push_memory_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        3,
+        Type::Ptr(Box::new(Type::Array(
+            Box::new(Type::Uint(32)),
+            vec![ArrayLength::Fixed(BigInt::from(3))],
+        ))),
+    );
+    set_tmp(&mut v, 101, Type::Uint(32));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::PushMemory {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: 101,
+                array: 3,
+                value: num_literal!(1, 32),
+            }
+        ),
+        "uint32 %temp.ssa_ir.101 = push_mem ptr<uint32[3]>(%temp.ssa_ir.3) uint32(1);"
+    );
+}
+
+#[test]
+fn test_stringfy_pop_memory_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        3,
+        Type::Ptr(Box::new(Type::Array(
+            Box::new(Type::Uint(32)),
+            vec![ArrayLength::Fixed(BigInt::from(3))],
+        ))),
+    );
+    set_tmp(&mut v, 101, Type::Uint(32));
+    let printer = new_printer(&v);
+
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::PopMemory {
+                res: 101,
+                array: 3,
+                loc: Loc::Codegen,
+            }
+        ),
+        "uint32 %temp.ssa_ir.101 = pop_mem ptr<uint32[3]>(%temp.ssa_ir.3);"
+    );
+}
+
+// LoadStorage
+#[test]
+fn test_stringfy_load_storage_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 101, Type::Uint(32));
+    set_tmp(&mut v, 3, Type::StoragePtr(false, Box::new(Type::Uint(32))));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::LoadStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: 101,
+                storage: identifier(3)
+            }
+        ),
+        "uint32 %temp.ssa_ir.101 = load_storage storage_ptr<uint32>(%temp.ssa_ir.3);"
+    );
+}
+
+#[test]
+fn test_stringfy_clear_storage_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 3, Type::StoragePtr(false, Box::new(Type::Uint(32))));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::ClearStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                storage: identifier(3)
+            }
+        ),
+        "clear_storage storage_ptr<uint32>(%temp.ssa_ir.3);"
+    );
+}
+
+#[test]
+fn test_stringfy_set_storage_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        1,
+        Type::StoragePtr(false, Box::new(Type::Uint(256))),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::SetStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                value: num_literal(13445566, false, 256),
+                storage: identifier(1)
+            }
+        ),
+        "set_storage storage_ptr<uint256>(%temp.ssa_ir.1) uint256(13445566);"
+    );
+}
+
+#[test]
+fn test_stringfy_set_storage_bytes_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(32));
+    set_tmp(
+        &mut v,
+        2,
+        Type::StoragePtr(false, Box::new(Type::Bytes(32))),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::SetStorageBytes {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                value: identifier(1),
+                storage: identifier(2),
+                offset: num_literal!(3)
+            }
+        ),
+        "set_storage_bytes storage_ptr<bytes32>(%temp.ssa_ir.2) offset:uint8(3) value:bytes32(%temp.ssa_ir.1);"
+    );
+}
+
+#[test]
+fn test_stringfy_push_storage_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 101, Type::Uint(32));
+    set_tmp(
+        &mut v,
+        3,
+        Type::StoragePtr(
+            false,
+            Box::new(Type::Array(
+                Box::new(Type::Uint(32)),
+                vec![ArrayLength::Fixed(BigInt::from(3))],
+            )),
+        ),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::PushStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: 101,
+                value: Some(num_literal!(1, 32)),
+                storage: identifier(3)
+            }
+        ),
+        "uint32 %temp.ssa_ir.101 = push_storage storage_ptr<uint32[3]>(%temp.ssa_ir.3) uint32(1);"
+    );
+}
+
+#[test]
+fn test_stringfy_pop_storage_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 123, Type::Uint(32));
+    set_tmp(
+        &mut v,
+        3,
+        Type::StoragePtr(
+            false,
+            Box::new(Type::Array(
+                Box::new(Type::Uint(32)),
+                vec![ArrayLength::Fixed(BigInt::from(3))],
+            )),
+        ),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::PopStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: Some(123),
+                storage: identifier(3)
+            }
+        ),
+        "uint32 %temp.ssa_ir.123 = pop_storage storage_ptr<uint32[3]>(%temp.ssa_ir.3);"
+    );
+
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::PopStorage {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: None,
+                storage: identifier(3)
+            }
+        ),
+        "pop_storage storage_ptr<uint32[3]>(%temp.ssa_ir.3);"
+    )
+}
+
+#[test]
+fn test_stringfy_call_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Uint(8));
+    set_tmp(&mut v, 2, Type::Uint(64));
+    set_tmp(&mut v, 3, Type::Uint(8));
+    set_tmp(&mut v, 133, Type::Uint(64));
+    set_tmp(
+        &mut v,
+        123,
+        Type::Ptr(Box::new(Type::Function {
+            params: vec![Type::Uint(8), Type::Uint(64), Type::Uint(64)],
+            returns: vec![Type::Uint(8), Type::Uint(64), Type::Uint(8)],
+        })),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Call {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: vec![1, 2, 3],
+                call: InternalCallTy::Builtin { ast_func_no: 123 },
+                args: vec![num_literal!(3), identifier(133), num_literal!(6, 64)],
+            }
+        ),
+        "uint8 %temp.ssa_ir.1, uint64 %temp.ssa_ir.2, uint8 %temp.ssa_ir.3 = call builtin#123(uint8(3), uint64(%temp.ssa_ir.133), uint64(6));"
+    );
+
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Call {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: vec![1, 2, 3],
+                call: InternalCallTy::Dynamic(identifier(123)),
+                args: vec![num_literal!(3), identifier(133), num_literal!(6, 64)],
+            }
+        ),
+        "uint8 %temp.ssa_ir.1, uint64 %temp.ssa_ir.2, uint8 %temp.ssa_ir.3 = call ptr<function (uint8, uint64, uint64) returns (uint8, uint64, uint8)>(%temp.ssa_ir.123)(uint8(3), uint64(%temp.ssa_ir.133), uint64(6));"
+    );
+
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Call {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: vec![1, 2, 3],
+                call: InternalCallTy::Static { cfg_no: 123 },
+                args: vec![num_literal!(3), identifier(133), num_literal!(6, 64)],
+            }
+        ),
+        "uint8 %temp.ssa_ir.1, uint64 %temp.ssa_ir.2, uint8 %temp.ssa_ir.3 = call function#123(uint8(3), uint64(%temp.ssa_ir.133), uint64(6));"
+    );
+}
+
+//  ExternalCall
+#[test]
+fn test_stringfy_external_call_insn() {
+    let mut v = new_vartable();
+
+    // success
+    set_tmp(&mut v, 1, Type::Bool);
+    // payload
+    set_tmp(&mut v, 3, Type::Bytes(32));
+    // value
+    set_tmp(&mut v, 4, Type::Uint(64));
+    // gas
+    set_tmp(&mut v, 7, Type::Uint(64));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::ExternalCall {
+                success: Some(1),
+                address: None,
+                payload: identifier(3),
+                value: identifier(4),
+                accounts: solang::sema::ast::ExternalCallAccounts::AbsentArgument,
+                seeds: None,
+                gas: identifier(7),
+                callty: CallTy::Regular,
+                contract_function_no: None,
+                flags: None,
+                loc: Loc::Codegen,
+            }
+        ),
+        "bool %temp.ssa_ir.1 = call_ext [regular] address:_ payload:bytes32(%temp.ssa_ir.3) value:uint64(%temp.ssa_ir.4) gas:uint64(%temp.ssa_ir.7) accounts:absent seeds:_ contract_no:_, function_no:_ flags:_;"
+    );
+}
+
+#[test]
+fn test_stringfy_print_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 3, Type::Uint(8));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Print {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                operand: identifier(3)
+            }
+        ),
+        "print uint8(%temp.ssa_ir.3);"
+    );
+}
+
+#[test]
+fn test_stringfy_memcopy_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 3, Type::Bytes(32));
+    set_tmp(&mut v, 4, Type::Bytes(16));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::MemCopy {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                src: identifier(3),
+                dest: identifier(4),
+                bytes: num_literal!(16)
+            }
+        ),
+        "memcopy bytes32(%temp.ssa_ir.3) to bytes16(%temp.ssa_ir.4) for uint8(16) bytes;"
+    )
+}
+
+#[test]
+fn test_stringfy_value_transfer_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bool);
+    set_tmp(
+        &mut v,
+        2,
+        Type::Array(
+            Box::new(Type::Uint(8)),
+            vec![ArrayLength::Fixed(BigInt::from(32))],
+        ),
+    );
+    set_tmp(&mut v, 3, Type::Uint(8));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::ValueTransfer {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                success: Some(1),
+                address: identifier(2),
+                value: identifier(3),
+            }
+        ),
+        "bool %temp.ssa_ir.1 = value_transfer uint8(%temp.ssa_ir.3) to uint8[32](%temp.ssa_ir.2);"
+    );
+}
+
+#[test]
+fn test_stringfy_selfdestruct_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(
+        &mut v,
+        3,
+        Type::Ptr(Box::new(Type::Struct(StructType::UserDefined(0)))),
+    );
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::SelfDestruct {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                recipient: identifier(3)
+            }
+        ),
+        "self_destruct ptr<struct.0>(%temp.ssa_ir.3);"
+    )
+}
+
+#[test]
+fn test_stringfy_emit_event_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Bytes(32));
+    set_tmp(&mut v, 2, Type::Bytes(32));
+    set_tmp(&mut v, 3, Type::Bytes(32));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::EmitEvent {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                event_no: 13,
+                topics: vec![identifier(1), identifier(2)],
+                data: identifier(3)
+            }
+        ),
+        "emit event#13 to topics[bytes32(%temp.ssa_ir.1), bytes32(%temp.ssa_ir.2)], data: bytes32(%temp.ssa_ir.3);"
+    )
+}
+
+#[test]
+fn test_stringfy_branch_insn() {
+    assert_eq!(
+        stringfy_insn!(
+            &new_printer(&new_vartable()),
+            &Instruction::Branch {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                block: 3
+            }
+        ),
+        "br block#3;"
+    )
+}
+
+#[test]
+fn test_stringfy_branch_cond_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 3, Type::Bool);
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::BranchCond {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                cond: identifier(3),
+                true_block: 5,
+                false_block: 6
+            }
+        ),
+        "cbr bool(%temp.ssa_ir.3) block#5 else block#6;"
+    )
+}
+
+#[test]
+fn test_stringfy_switch_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Uint(8));
+    set_tmp(&mut v, 4, Type::Uint(8));
+    set_tmp(&mut v, 5, Type::Uint(8));
+    set_tmp(&mut v, 6, Type::Uint(8));
+    let printer = new_printer(&v);
+    let s = stringfy_insn!(
+        &printer,
+        &Instruction::Switch {
+            loc: /*missing from cfg*/ Loc::Codegen,
+            cond: identifier(1),
+            cases: vec![
+                (identifier(4), 11),
+                (identifier(5), 12),
+                (identifier(6), 13),
+            ],
+            default: 14,
+        }
+    );
+    assert_eq!(
+        s,
+        r#"switch uint8(%temp.ssa_ir.1):
+    case:    uint8(%temp.ssa_ir.4) => block#11, 
+    case:    uint8(%temp.ssa_ir.5) => block#12, 
+    case:    uint8(%temp.ssa_ir.6) => block#13
+    default: block#14;"#
+    )
+}
+
+#[test]
+fn test_stringfy_return_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Uint(8));
+    set_tmp(&mut v, 2, Type::Bytes(32));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Return {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                value: vec![identifier(1), identifier(2)]
+            }
+        ),
+        "return uint8(%temp.ssa_ir.1), bytes32(%temp.ssa_ir.2);"
+    )
+}
+
+#[test]
+fn test_stringfy_assert_failure_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 3, Type::Bytes(32));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::AssertFailure {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                encoded_args: Some(identifier(3))
+            }
+        ),
+        "assert_failure bytes32(%temp.ssa_ir.3);"
+    );
+
+    assert_eq!(
+        stringfy_insn!(
+            &new_printer(&new_vartable()),
+            &Instruction::AssertFailure {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                encoded_args: None
+            }
+        ),
+        "assert_failure;"
+    )
+}
+
+#[test]
+fn test_stringfy_phi_insn() {
+    let mut v = new_vartable();
+
+    set_tmp(&mut v, 1, Type::Uint(8));
+    set_tmp(&mut v, 2, Type::Uint(8));
+    set_tmp(&mut v, 12, Type::Uint(8));
+    let printer = new_printer(&v);
+    assert_eq!(
+        stringfy_insn!(
+            &printer,
+            &Instruction::Phi {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                res: 12,
+                vars: vec![
+                    PhiInput::new(identifier(1), 13),
+                    PhiInput::new(identifier(2), 14)
+                ],
+            }
+        ),
+        "uint8 %temp.ssa_ir.12 = phi [uint8(%temp.ssa_ir.1), block#13], [uint8(%temp.ssa_ir.2), block#14];"
+    )
+}

+ 176 - 0
tests/lir_tests/lir_to_string.rs

@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::lir_tests::helpers::{identifier, num_literal};
+use crate::{num_literal, stringfy_lir};
+use indexmap::IndexMap;
+use solang::lir::lir_type::{LIRType, Type};
+use solang::lir::printer::Printer;
+use solang::lir::vartable::Var;
+use solang::lir::{instructions::Instruction, vartable::Vartable, Block, LIR};
+use solang::sema::ast::{self, Parameter};
+use solang_parser::pt::{Identifier, Loc};
+
+#[test]
+fn test_stringfy_cfg() {
+    let cfg = new_cfg(vec![
+        new_block(
+            String::from("entry"),
+            vec![
+                Instruction::LoadStorage {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    res: 0,
+                    storage: identifier(3),
+                },
+                Instruction::BranchCond {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    cond: identifier(0),
+                    true_block: 1,
+                    false_block: 2,
+                },
+            ],
+        ),
+        new_block(
+            String::from("blk1"),
+            vec![
+                Instruction::Print {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    operand: num_literal!(1),
+                },
+                Instruction::Branch { loc: /*missing from cfg*/ Loc::Codegen, block: 3 },
+            ],
+        ),
+        new_block(
+            String::from("blk2"),
+            vec![
+                Instruction::Print {
+                    loc: /*missing from cfg*/ Loc::Codegen,
+                    operand: num_literal!(2),
+                },
+                Instruction::Branch { loc: /*missing from cfg*/ Loc::Codegen, block: 3 },
+            ],
+        ),
+        new_block(
+            String::from("exit"),
+            vec![Instruction::ReturnData {
+                loc: /*missing from cfg*/ Loc::Codegen,
+                data: identifier(0),
+                data_len: num_literal!(1),
+            }],
+        ),
+    ]);
+
+    let mut var_table = Vartable {
+        vars: IndexMap::new(),
+        args: IndexMap::new(),
+        next_id: 0,
+    };
+
+    // construct a index map for the vartable
+    var_table.vars.insert(
+        0,
+        Var {
+            id: 0,
+            ty: LIRType {
+                lir_type: Type::Int(32),
+                ast_type: ast::Type::Int(32),
+            },
+            name: String::from("x"),
+        },
+    );
+    var_table.vars.insert(
+        3,
+        Var {
+            id: 1,
+            ty: LIRType {
+                lir_type: Type::StoragePtr(false, Box::new(Type::Int(32))),
+                ast_type: ast::Type::Int(32),
+            },
+            name: String::from("st"),
+        },
+    );
+    let printer = Printer::new(&var_table);
+
+    assert_eq!(
+        format!(
+            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}",
+            "public function sol#0 test_cfg (int32, int32) returns (int32):",
+            "block#0 entry:",
+            "    int32 %x = load_storage storage_ptr<int32>(%st);",
+            "    cbr int32(%x) block#1 else block#2;",
+            "",
+            "block#1 blk1:",
+            "    print uint8(1);",
+            "    br block#3;",
+            "",
+            "block#2 blk2:",
+            "    print uint8(2);",
+            "    br block#3;",
+            "",
+            "block#3 exit:",
+            "    return_data int32(%x) of length uint8(1);"
+        ),
+        stringfy_lir!(&printer, &cfg).trim()
+    )
+}
+
+fn new_block(name: String, instructions: Vec<Instruction>) -> Block {
+    Block { name, instructions }
+}
+
+fn new_cfg(blocks: Vec<Block>) -> LIR {
+    LIR {
+        name: String::from("test_cfg"),
+        function_no: solang::codegen::cfg::ASTFunction::SolidityFunction(0),
+        ty: solang_parser::pt::FunctionTy::Function,
+        public: true,
+        nonpayable: false,
+        vartable: new_vartable(),
+        params: vec![
+            new_parameter(
+                String::from("a"),
+                LIRType {
+                    ast_type: ast::Type::Int(32),
+                    lir_type: Type::Int(32),
+                },
+            ),
+            new_parameter(
+                String::from("b"),
+                LIRType {
+                    ast_type: ast::Type::Int(32),
+                    lir_type: Type::Int(32),
+                },
+            ),
+        ],
+        returns: vec![new_parameter(
+            String::from("c"),
+            LIRType {
+                ast_type: ast::Type::Int(32),
+                lir_type: Type::Int(32),
+            },
+        )],
+        blocks,
+        selector: vec![],
+    }
+}
+
+fn new_parameter(name: String, ty: LIRType) -> Parameter<LIRType> {
+    Parameter {
+        loc: Loc::Codegen,
+        id: Some(Identifier::new(name)),
+        ty,
+        annotation: None,
+        indexed: false,
+        infinite_size: false,
+        readonly: false,
+        recursive: false,
+        ty_loc: None,
+    }
+}
+
+fn new_vartable() -> Vartable {
+    Vartable {
+        vars: IndexMap::new(),
+        args: IndexMap::new(),
+        next_id: 0,
+    }
+}

+ 7 - 0
tests/lir_tests/mod.rs

@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: Apache-2.0
+
+mod convert_lir;
+mod expr_to_string;
+mod helpers;
+mod insn_to_string;
+mod lir_to_string;