///|
trait InsertPoint {
  asInsertPtEnum(Self) -> InsertPointEnum
}

///|
priv enum InsertPointEnum {
  BasicBlock(BasicBlock)
  Instruction(&Instruction)
}

///|
pub impl InsertPoint for BasicBlock with asInsertPtEnum(self) {
  InsertPointEnum::BasicBlock(self)
}

///|
pub impl InsertPoint for &Instruction with asInsertPtEnum(self) {
  InsertPointEnum::Instruction(self)
}

///|
pub struct IRBuilder {
  ctx : Context
  mut bb : BasicBlock?
  mut insertPt : &Instruction?
}

///|
fn IRBuilder::new(ctx : Context) -> IRBuilder {
  IRBuilder::{ ctx, bb: None, insertPt: None }
}

///|
pub fn[T : InsertPoint] IRBuilder::setInsertPoint(
  self : IRBuilder,
  insertPt : T,
) -> Unit {
  match insertPt.asInsertPtEnum() {
    BasicBlock(bb) => {
      self.bb = Some(bb)
      self.insertPt = bb.lastInst()
    }
    Instruction(inst) => {
      self.bb = inst.getBasicBlock()
      self.insertPt = Some(inst)
    }
  }
}

///|
fn IRBuilder::insert(self : IRBuilder, inst : &Instruction) -> Unit raise Error {
  guard self.bb is Some(_) else { raise UnsetInsertPoint }
  match self.insertPt {
    Some(insertPt) => {
      inst.insertAfter(insertPt)
      self.insertPt = Some(inst)
      inst.getInstBase().bb.val = Some(self.bb.unwrap())
    }
    None => {
      self.bb.unwrap().head = Some(inst)
      self.insertPt = Some(inst)
      inst.getInstBase().bb.val = Some(self.bb.unwrap())
    }
  }
}

///|
pub fn IRBuilder::getInsertFunction(self : IRBuilder) -> Function {
  self.bb.unwrap().parent
}

///|
pub fn IRBuilder::getInsertBlock(self : IRBuilder) -> BasicBlock {
  self.bb.unwrap()
}

///|
pub fn IRBuilder::getContext(self : IRBuilder) -> Context {
  self.ctx
}

///|
pub fn IRBuilder::getModule(self : IRBuilder) -> Module {
  self.getInsertFunction().mod
}

///|
/// Create a Return Instruction
///
/// **Note:**
/// 
/// `IRBuilder::createRet` could not return void. use `IRBuilder::createRetVoid` for that purpose.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "direct_ret")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let ret = builder.createRet(arg)
///   inspect(ret, content="  ret i32 %0")
/// }
/// ```
pub fn IRBuilder::createRet(
  self : IRBuilder,
  retVal : &Value,
) -> &Instruction raise Error {
  let vty = retVal.getType()
  let parent = self.getInsertFunction()
  guard vty == parent.getReturnType() else {
    let fname = self.getInsertFunction().getName().unwrap()
    let msg = "Misuse `IRBuilder::createRet`, " +
      "the return value type must match the function's return type, " +
      "function \{fname}'s return type expected `\{parent.getReturnType()}`, but got `\{vty}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let retInst = ReturnInst::new(Some(retVal), parent)
  self.insert(retInst)
  retInst
}

///|
/// Create a Return Instruction with no return value (void)
///
/// **Note:**
///
/// If you want to return a value, use `IRBuilder::createRet` instead.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [])
///   let fval = mod.addFunction(fty, "direct_ret")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let ret = builder.createRetVoid()
///   inspect(ret, content="  ret void")
/// }
/// ```
pub fn IRBuilder::createRetVoid(self : IRBuilder) -> &Instruction raise Error {
  let parent = self.getInsertFunction()
  let void_ty = parent.getContext().getVoidTy()
  guard parent.getReturnType() == void_ty else {
    let msg = "Misuse `IRBuilder::createRetVoid`, " +
      "function return type is \{parent.getReturnType()}"
    raise LLVMValueError(msg)
  }
  let retInst = ReturnInst::new(None, parent)
  self.insert(retInst)
  retInst
}

///|
/// Create an Alloca Instruction.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let void_ty = ctx.getVoidTy()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(void_ty, [])
///   let fval = mod.addFunction(fty, "foo")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let inst = builder.createAlloca(i32_ty, name="var1")
///   inspect(inst, content="  %var1 = alloca i32, align 4")
/// }
/// ```
pub fn IRBuilder::createAlloca(
  self : IRBuilder,
  data_ty : &Type,
  addressSpace? : AddressSpace = AddressSpace::default(),
  name? : String = "",
) -> AllocaInst raise Error {
  let allocaInst = AllocaInst::new(
    data_ty,
    self.getInsertFunction(),
    addressSpace~,
    name=None,
  )
  match name {
    "" => ()
    _ => allocaInst.setName(name)
  }
  self.insert(allocaInst)
  allocaInst
}

///|
/// Create an Add Instruction
///
/// **Note:**
/// 
/// This creates an integer addition instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "add_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let add = builder.createAdd(arg1, arg2, name="sum")
///   inspect(add, content="  %sum = add i32 %0, %1")
///   assert_true(add.asValueEnum() is BinaryInst(_))
///   let one = ctx.getConstInt32(1)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createAdd(one, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createAdd(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  has_nsw? : Bool = false,
  has_nuw? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createAdd`, " +
      "the type of both operands must be the same, " +
      "got `{lhsTy} and {rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createAdd`, " +
      "the type of both operands must be integer types, " +
      "got `{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs)), Some(ConstantInt(rhs))) => return lhs.add(rhs)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if has_nuw {
    flags.add(BinaryOpFlags::NoUnsignedWrap)
  }
  if has_nsw {
    flags.add(BinaryOpFlags::NoSignedWrap)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Add,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an NSW Add Instruction
///
/// **Note:**
/// 
/// This creates an integer addition instruction with No Signed Wrap (NSW) flag. If signed overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nsw_add_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let add = builder.createNSWAdd(arg1, arg2, name="sum")
///   inspect(add, content="  %sum = add nsw i32 %0, %1")
///   assert_true(add.asValueEnum() is BinaryInst(_))
///   let one = ctx.getConstInt32(1)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createNSWAdd(one, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNSWAdd(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createAdd(lhs, rhs, name~, has_nsw=true, has_nuw=false)
}

///|
/// Create an NUW Add Instruction
///
/// **Note:**
/// 
/// This creates an integer addition instruction with No Unsigned Wrap (NUW) flag. If unsigned overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nuw_add_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let add = builder.createNUWAdd(arg1, arg2, name="sum")
///   inspect(add, content="  %sum = add nuw i32 %0, %1")
///   assert_true(add.asValueEnum() is BinaryInst(_))
///   let one = ctx.getConstInt32(1)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createNUWAdd(one, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNUWAdd(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createAdd(lhs, rhs, name~, has_nsw=false, has_nuw=true)
}

///|
/// Create a Sub Instruction
///
/// **Note:**
/// 
/// This creates an integer subtraction instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "sub_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let sub = builder.createSub(arg1, arg2, name="diff")
///   inspect(sub, content="  %diff = sub i32 %0, %1")
///   assert_true(sub.asValueEnum() is BinaryInst(_))
///   let five = ctx.getConstInt32(5)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createSub(five, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createSub(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  has_nsw? : Bool = false,
  has_nuw? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createSub`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createSub`, " +
      "the type of both operands must be integer types, " +
      "got `{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs)), Some(ConstantInt(rhs))) => return lhs.sub(rhs)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if has_nuw {
    flags.add(BinaryOpFlags::NoUnsignedWrap)
  }
  if has_nsw {
    flags.add(BinaryOpFlags::NoSignedWrap)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Sub,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an NSW Sub Instruction
///
/// **Note:**
/// 
/// This creates an integer subtraction instruction with No Signed Wrap (NSW) flag. If signed overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nsw_sub_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let sub = builder.createNSWSub(arg1, arg2, name="diff")
///   inspect(sub, content="  %diff = sub nsw i32 %0, %1")
///   assert_true(sub.asValueEnum() is BinaryInst(_))
///   let five = ctx.getConstInt32(5)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createNSWSub(five, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNSWSub(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createSub(lhs, rhs, name~, has_nsw=true, has_nuw=false)
}

///|
/// Create an NUW Sub Instruction
///
/// **Note:**
/// 
/// This creates an integer subtraction instruction with No Unsigned Wrap (NUW) flag. If unsigned overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nuw_sub_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let sub = builder.createNUWSub(arg1, arg2, name="diff")
///   inspect(sub, content="  %diff = sub nuw i32 %0, %1")
///   assert_true(sub.asValueEnum() is BinaryInst(_))
///   let five = ctx.getConstInt32(5)
///   let two = ctx.getConstInt32(2)
///   let three = builder.createNUWSub(five, two)
///   inspect(three, content="i32 3")
///   assert_true(three.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNUWSub(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createSub(lhs, rhs, name~, has_nsw=false, has_nuw=true)
}

///|
pub fn IRBuilder::createNeg(
  self : IRBuilder,
  val : &Value,
  name? : String = "",
) -> &Value raise Error {
  let ty = val.getType()
  let zero = self.getContext().getConstZero(ty)
  self.createSub(zero, val, name~)
}

///|
/// Create a Mul Instruction
///
/// **Note:**
/// 
/// This creates an integer multiplication instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "mul_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let mul = builder.createMul(arg1, arg2, name="product")
///   inspect(mul, content="  %product = mul i32 %0, %1")
///   assert_true(mul.asValueEnum() is BinaryInst(_))
///   let three = ctx.getConstInt32(3)
///   let four = ctx.getConstInt32(4)
///   let twelve = builder.createMul(three, four)
///   inspect(twelve, content="i32 12")
///   assert_true(twelve.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createMul(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  has_nsw? : Bool = false,
  has_nuw? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createMul`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createMul`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs)), Some(ConstantInt(rhs))) => return lhs.mul(rhs)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if has_nuw {
    flags.add(BinaryOpFlags::NoUnsignedWrap)
  }
  if has_nsw {
    flags.add(BinaryOpFlags::NoSignedWrap)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Mul,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an NSW Mul Instruction
///
/// **Note:**
/// 
/// This creates an integer multiplication instruction with No Signed Wrap (NSW) flag. If signed overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nsw_mul_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let mul = builder.createNSWMul(arg1, arg2, name="product")
///   inspect(mul, content="  %product = mul nsw i32 %0, %1")
///   assert_true(mul.asValueEnum() is BinaryInst(_))
///   let three = ctx.getConstInt32(3)
///   let four = ctx.getConstInt32(4)
///   let twelve = builder.createNSWMul(three, four)
///   inspect(twelve, content="i32 12")
///   assert_true(twelve.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNSWMul(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createMul(lhs, rhs, name~, has_nsw=true, has_nuw=false)
}

///|
/// Create an NUW Mul Instruction
///
/// **Note:**
/// 
/// This creates an integer multiplication instruction with No Unsigned Wrap (NUW) flag. If unsigned overflow occurs, the result is undefined.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "nuw_mul_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let mul = builder.createNUWMul(arg1, arg2, name="product")
///   inspect(mul, content="  %product = mul nuw i32 %0, %1")
///   assert_true(mul.asValueEnum() is BinaryInst(_))
///   let three = ctx.getConstInt32(3)
///   let four = ctx.getConstInt32(4)
///   let twelve = builder.createNUWMul(three, four)
///   inspect(twelve, content="i32 12")
///   assert_true(twelve.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createNUWMul(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createMul(lhs, rhs, name~, has_nsw=false, has_nuw=true)
}

///|
/// Create an SDiv Instruction
///
/// **Note:**
/// 
/// This creates a signed integer division instruction.
/// Both operands must be integer types with the same bitwidth.
///
/// ```mbt
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "sdiv_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let div = builder.createSDiv(arg1, arg2, name="quotient")
/// inspect(div, content = "  %quotient = sdiv i32 %0, %1")
/// assert_true(div.asValueEnum() is BinaryInst(_))
///
/// let twelve = ctx.getConstInt32(12)
/// let three = ctx.getConstInt32(3)
/// let four = builder.createSDiv(twelve, three)
/// inspect(four, content = "  %2 = sdiv i32 12, 3")
/// assert_true(four.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createSDiv(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  is_exact? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createSDiv`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createSDiv`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }

  // match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
  //   (Some(ConstantInt(c_lhs)), Some(ConstantInt(c_rhs))) => return c_lhs.sdiv(c_rhs) // Assuming ConstantInt has sdiv!
  //   _ => ()
  // }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if is_exact {
    flags.add(BinaryOpFlags::Exact)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::SDiv,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an Exact SDiv Instruction
///
/// **Note:**
/// 
/// This creates an exact signed integer division instruction. Both operands must be integer types with the same bitwidth.
/// The exact flag indicates that the division is expected to be exact (no remainder).
///
/// ```mbt
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "exact_sdiv_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let div = builder.createExactSDiv(arg1, arg2, name="quotient")
/// inspect(div, content = "  %quotient = sdiv exact i32 %0, %1")
/// assert_true(div.asValueEnum() is BinaryInst(_))
///
/// let twelve = ctx.getConstInt32(12)
/// let three = ctx.getConstInt32(3)
/// let four = builder.createExactSDiv(twelve, three)
/// inspect(four, content = "  %2 = sdiv exact i32 12, 3")
/// assert_true(four.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createExactSDiv(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createSDiv(lhs, rhs, name~, is_exact=true)
}

///|
/// Create a UDiv Instruction
///
/// **Note:**
/// 
/// This creates an unsigned integer division instruction. 
/// Both operands must be integer types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "udiv_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let div = builder.createUDiv(arg1, arg2, name="quotient")
/// inspect(div, content = "  %quotient = udiv i32 %0, %1")
/// assert_true(div.asValueEnum() is BinaryInst(_))
///
/// let twelve = ctx.getConstInt32(12)
/// let three = ctx.getConstInt32(3)
/// let four = builder.createUDiv(twelve, three)
/// inspect(four, content = "  %2 = udiv i32 12, 3")
/// assert_true(four.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createUDiv(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  is_exact? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createUDiv`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createUDiv`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  // match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
  //   (Some(ConstantUInt(c_lhs)), Some(ConstantUInt(c_rhs))) => return c_lhs.udiv(c_rhs) // Assumed missing
  //   _ => ()
  // }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if is_exact {
    flags.add(BinaryOpFlags::Exact)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::UDiv,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an Exact UDiv Instruction
///
/// **Note:**
/// 
/// This creates an exact unsigned integer division instruction.
/// Both operands must be integer types with the same bitwidth.
/// The exact flag indicates that the division is expected to be exact (no remainder).
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "exact_udiv_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let div = builder.createExactUDiv(arg1, arg2, name="quotient")
/// inspect(div, content = "  %quotient = udiv exact i32 %0, %1")
/// assert_true(div.asValueEnum() is BinaryInst(_))
///
/// let twelve = ctx.getConstInt32(12)
/// let three = ctx.getConstInt32(3)
/// let four = builder.createExactUDiv(twelve, three)
/// inspect(four, content = "  %2 = udiv exact i32 12, 3")
/// assert_true(four.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createExactUDiv(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createUDiv(lhs, rhs, name~, is_exact=true)
}

///|
/// Create an SRem Instruction
///
/// **Note:**
/// 
/// This creates a signed integer remainder instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "srem_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let rem = builder.createSRem(arg1, arg2, name="remainder")
/// inspect(rem, content = "  %remainder = srem i32 %0, %1")
/// assert_true(rem.asValueEnum() is BinaryInst(_))
///
/// let thirteen = ctx.getConstInt32(13)
/// let five = ctx.getConstInt32(5)
/// let three = builder.createSRem(thirteen, five)
/// inspect(three, content = "  %2 = srem i32 13, 5")
/// assert_true(three.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createSRem(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createSRem`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createSRem`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  // Constant folding for SRem is currently disabled due to assumed missing `ConstantInt::srem!`
  // match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
  //   (Some(ConstantInt(c_lhs)), Some(ConstantInt(c_rhs))) => return c_lhs.srem(c_rhs)
  //   _ => ()
  // }

  let parent = self.getInsertFunction()
  let flags = Set::new() // nsw, nuw, exact are not applicable to SRem
  let inst = BinaryInst::newStandardOp(
    BinaryOps::SRem,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a URem Instruction
///
/// **Note:**
/// 
/// This creates an unsigned integer remainder instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "urem_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let rem = builder.createURem(arg1, arg2, name="remainder")
/// inspect(rem, content = "  %remainder = urem i32 %0, %1")
/// assert_true(rem.asValueEnum() is BinaryInst(_))
///
/// let thirteen = ctx.getConstInt32(13)
/// let five = ctx.getConstInt32(5)
/// let three = builder.createURem(thirteen, five)
/// inspect(three, content = "  %2 = urem i32 13, 5")
/// assert_true(three.asValueEnum() is ConstantInt(_))
/// ```
pub fn IRBuilder::createURem(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createURem`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createURem`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  // Constant folding for URem is currently disabled due to assumed missing `ConstantUInt::urem!`
  // match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
  //   (Some(ConstantUInt(c_lhs)), Some(ConstantUInt(c_rhs))) => return c_lhs.urem(c_rhs)
  //   _ => ()
  // }

  let parent = self.getInsertFunction()
  let flags = Set::new() // nsw, nuw, exact are not applicable to URem
  let inst = BinaryInst::newStandardOp(
    BinaryOps::URem,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an FAdd Instruction
///
/// **Note:**
/// 
/// This creates a floating-point addition instruction.
/// Both operands must be floating-point types with the same bitwidth.
///
/// **Fast Math Flags:**
///
/// Allowed Fast Math Flags:
/// 1. `AllowReassoc`
/// 2. `AllowContract`
/// 3. `NoNaNs`
/// 4. `NoInfs`
/// 5. `NoSignedZeros`
/// 6. `AllowReciprocal`
/// 7. `ApproxFunc`
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let f32_ty = ctx.getFloatTy()
/// let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///
/// let fval = mod.addFunction(fty, "fadd_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let fadd = builder.createFAdd(arg1, arg2, name="sum")
/// inspect(fadd, content = "  %sum = fadd float %0, %1")
/// assert_true(fadd.asValueEnum() is BinaryInst(_))
///
/// let one = ctx.getConstFloat(1.0)
/// let two = ctx.getConstFloat(2.0)
/// let three = builder.createFAdd(one, two)
/// inspect(three, content = "  %2 = fadd float 0x3FF0000000000000, 0x4000000000000000")
/// assert_true(three.asValueEnum() is ConstantFP(_))
///
/// let nnan_fadd = builder.createFAdd(arg1, arg2, name="sum_nnan", fast_math=[NoNaNs])
/// inspect(nnan_fadd, content = "  %sum_nnan = fadd nnan float %0, %1")
///
/// let ninf_fadd = builder.createFAdd(arg1, arg2, name="sum_ninf", fast_math=[NoInfs])
/// inspect(ninf_fadd, content = "  %sum_ninf = fadd ninf float %0, %1")
///
/// let nnan_ninf_fadd = builder.createFAdd(
///   arg1, arg2, name="sum_nnan_ninf", fast_math=[NoNaNs, NoInfs]
/// )
/// inspect(nnan_ninf_fadd, content = "  %sum_nnan_ninf = fadd nnan ninf float %0, %1")
/// ```
pub fn IRBuilder::createFAdd(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  fast_math? : Array[FastMathFlag] = [],
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFAdd`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFAdd`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let fast_math_flags = Set::from_array(fast_math)
  let inst = BinaryInst::newFPMathOp(
    BinaryOps::FAdd,
    lhs,
    rhs,
    parent,
    name=None,
    fast_math_flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an FSub Instruction
///
/// **Note:**
/// 
/// This creates a floating-point subtraction instruction. Both operands must be floating-point types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let f32_ty = ctx.getFloatTy()
/// let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///
/// let fval = mod.addFunction(fty, "fsub_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let fsub = builder.createFSub(arg1, arg2, name="diff")
/// inspect(fsub, content = "  %diff = fsub float %0, %1")
/// assert_true(fsub.asValueEnum() is BinaryInst(_))
///
/// let five = ctx.getConstFloat(5.0)
/// let two = ctx.getConstFloat(2.0)
/// let three = builder.createFSub(five, two)
/// inspect(three, content = "  %2 = fsub float 0x4014000000000000, 0x4000000000000000")
/// assert_true(three.asValueEnum() is ConstantFP(_))
/// ```
pub fn IRBuilder::createFSub(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  fast_math_flags? : Set[FastMathFlag] = Set::new(),
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFSub`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFSub`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newFPMathOp(
    BinaryOps::FSub,
    lhs,
    rhs,
    parent,
    name=None,
    fast_math_flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an FNeg Instruction
///
/// **Note:**
///
/// This creates a floating-point negation instruction. The operand must be a floating-point type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [f32_ty])
///   let fval = mod.addFunction(fty, "fneg_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let fneg = builder.createFNeg(arg, name="neg_value")
///   inspect(fneg, content="  %neg_value = fneg float %0")
///   assert_true(fneg.asValueEnum() is FNegInst(_))
/// }
/// ```
pub fn IRBuilder::createFNeg(
  self : IRBuilder,
  value : &Value,
  name? : String = "",
  fast_math_flags? : Set[FastMathFlag] = Set::new(),
) -> &Value raise Error {
  let ty = value.getType()
  guard ty.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFNeg`, " +
      "the operand must be a floating-point type, " +
      "got `\{ty}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let name = match name {
    "" => None
    _ => Some(name)
  }
  let inst = FNegInst::new(value, parent, name~, fast_math_flags)
  self.insert(inst)
  inst
}

///|
/// Create an FMul Instruction
///
/// **Note:**
/// 
/// This creates a floating-point multiplication instruction. Both operands must be floating-point types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let f32_ty = ctx.getFloatTy()
/// let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///
/// let fval = mod.addFunction(fty, "fmul_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let fmul = builder.createFMul(arg1, arg2, name="product")
/// inspect(fmul, content = "  %product = fmul float %0, %1")
/// assert_true(fmul.asValueEnum() is BinaryInst(_))
///
/// let three = ctx.getConstFloat(3.0)
/// let four = ctx.getConstFloat(4.0)
/// let twelve = builder.createFMul(three, four)
/// inspect(twelve, content = "  %2 = fmul float 0x4008000000000000, 0x4010000000000000")
/// assert_true(twelve.asValueEnum() is ConstantFP(_))
/// ```
pub fn IRBuilder::createFMul(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  fast_math_flags? : Set[FastMathFlag] = Set::new(),
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFMul`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFMul`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newFPMathOp(
    BinaryOps::FMul,
    lhs,
    rhs,
    parent,
    name=None,
    fast_math_flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an FDiv Instruction
///
/// **Note:**
/// 
/// This creates a floating-point division instruction. Both operands must be floating-point types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let f32_ty = ctx.getFloatTy()
/// let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///
/// let fval = mod.addFunction(fty, "fdiv_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let fdiv = builder.createFDiv(arg1, arg2, name="quotient")
/// inspect(fdiv, content = "  %quotient = fdiv float %0, %1")
/// assert_true(fdiv.asValueEnum() is BinaryInst(_))
///
/// let eight = ctx.getConstFloat(8.0)
/// let two = ctx.getConstFloat(2.0)
/// let four = builder.createFDiv(eight, two)
/// inspect(four, content = "  %2 = fdiv float 0x4020000000000000, 0x4000000000000000")
/// assert_true(four.asValueEnum() is ConstantFP(_))
/// ```
pub fn IRBuilder::createFDiv(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  fast_math_flags? : Set[FastMathFlag] = Set::new(),
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFDiv`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFDiv`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newFPMathOp(
    BinaryOps::FDiv,
    lhs,
    rhs,
    parent,
    name=None,
    fast_math_flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an FRem Instruction
///
/// **Note:**
/// 
/// This creates a floating-point remainder instruction. Both operands must be floating-point types with the same bitwidth.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let f32_ty = ctx.getFloatTy()
/// let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///
/// let fval = mod.addFunction(fty, "frem_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let arg1 = fval.getArg(0).unwrap()
/// let arg2 = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let frem = builder.createFRem(arg1, arg2, name="remainder")
/// inspect(frem, content = "  %remainder = frem float %0, %1")
/// assert_true(frem.asValueEnum() is BinaryInst(_))
///
/// let five_and_half = ctx.getConstFloat(5.5)
/// let two = ctx.getConstFloat(2.0)
/// let one_and_half = builder.createFRem(five_and_half, two)
/// inspect(one_and_half, content = "  %2 = frem float 0x4016000000000000, 0x4000000000000000")
/// assert_true(one_and_half.asValueEnum() is ConstantFP(_))
/// ```
pub fn IRBuilder::createFRem(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  fast_math_flags? : Set[FastMathFlag] = Set::new(),
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFRem`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFRem`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newFPMathOp(
    BinaryOps::FRem,
    lhs,
    rhs,
    parent,
    name=None,
    fast_math_flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an And Instruction
///
/// **Note:**
/// 
/// This creates a bitwise AND instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "and_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let and_result = builder.createAnd(arg1, arg2, name="result")
///   inspect(and_result, content="  %result = and i32 %0, %1")
///   assert_true(and_result.asValueEnum() is BinaryInst(_))
///   let val1 = ctx.getConstInt32(12) // 1100 in binary
///   let val2 = ctx.getConstInt32(10) // 1010 in binary
///   let result = builder.createAnd(val1, val2) // 1000 in binary = 8
///   inspect(result, content="i32 8")
///   assert_true(result.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createAnd(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createAnd`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createAnd`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.compute_and(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newStandardOp(
    BinaryOps::And,
    lhs,
    rhs,
    parent,
    name=None,
    Set::new(),
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an Or Instruction
///
/// **Note:**
/// 
/// This creates a bitwise OR instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "or_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let or_result = builder.createOr(arg1, arg2, name="result")
///   inspect(or_result, content="  %result = or i32 %0, %1")
///   assert_true(or_result.asValueEnum() is BinaryInst(_))
///   let val1 = ctx.getConstInt32(12) // 1100 in binary
///   let val2 = ctx.getConstInt32(10) // 1010 in binary
///   let result = builder.createOr(val1, val2) // 1110 in binary = 14
///   inspect(result, content="i32 14")
///   assert_true(result.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createOr(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createOr`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createOr`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.or(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Or,
    lhs,
    rhs,
    parent,
    name=None,
    Set::new(),
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an Xor Instruction
///
/// **Note:**
/// 
/// This creates a bitwise XOR instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "xor_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let xor_result = builder.createXor(arg1, arg2, name="result")
///   inspect(xor_result, content="  %result = xor i32 %0, %1")
///   assert_true(xor_result.asValueEnum() is BinaryInst(_))
///   let val1 = ctx.getConstInt32(12) // 1100 in binary
///   let val2 = ctx.getConstInt32(10) // 1010 in binary
///   let result = builder.createXor(val1, val2) // 0110 in binary = 6
///   inspect(result, content="i32 6")
///   assert_true(result.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createXor(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createXor`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createXor`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.xor(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Xor,
    lhs,
    rhs,
    parent,
    name=None,
    Set::new(),
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
pub fn IRBuilder::createNot(
  self : IRBuilder,
  val : &Value,
  name? : String = "",
) -> &Value raise Error {
  let ctx = self.getContext()
  let allones = match val.getType().asTypeEnum() {
    Int1Type(_) => ctx.getConstTrue()
    Int8Type(_) => ctx.getConstInt8(-1)
    Int16Type(_) => ctx.getConstInt16(-1)
    Int32Type(_) => ctx.getConstInt32(-1)
    Int64Type(_) => ctx.getConstInt64(-1)
    _ =>
      raise LLVMValueError(
        "Misuse `IRBuilder::createNot`, " +
        "the operand must be an integer type, " +
        "got `\{val.getType()}`",
      )
  }
  self.createXor(val, allones, name~)
}

///|
/// Create a Shl Instruction
///
/// **Note:**
/// 
/// This creates a left shift instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "shl_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let shl_result = builder.createShl(arg1, arg2, name="result")
///   inspect(shl_result, content="  %result = shl i32 %0, %1")
///   assert_true(shl_result.asValueEnum() is BinaryInst(_))
///   let val = ctx.getConstInt32(5) // 101 in binary
///   let shift = ctx.getConstInt32(2) // shift left by 2
///   let result = builder.createShl(val, shift) // 10100 in binary = 20
///   inspect(result, content="i32 20")
///   assert_true(result.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createShl(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  has_nsw? : Bool = false,
  has_nuw? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createShl`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createShl`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.compute_shl(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if has_nuw {
    flags.add(BinaryOpFlags::NoUnsignedWrap)
  }
  if has_nsw {
    flags.add(BinaryOpFlags::NoSignedWrap)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::Shl,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an LShr Instruction
///
/// **Note:**
/// 
/// This creates a logical right shift instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "lshr_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let lshr_result = builder.createLShr(arg1, arg2, name="result")
///   inspect(lshr_result, content="  %result = lshr i32 %0, %1")
///   assert_true(lshr_result.asValueEnum() is BinaryInst(_))
///   let twenty = ctx.getConstInt32(20) // 10100 in binary
///   let two = ctx.getConstInt32(2) // shift right by 2
///   let five = builder.createLShr(twenty, two) // 101 in binary = 5
///   inspect(five, content="i32 5")
///   assert_true(five.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createLShr(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  is_exact? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createLShr`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createLShr`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.lshr(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if is_exact {
    flags.add(BinaryOpFlags::Exact)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::LShr,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an AShr Instruction
///
/// **Note:**
/// 
/// This creates an arithmetic right shift instruction. Both operands must be integer types with the same bitwidth.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "ashr_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg1 = fval.getArg(0).unwrap()
///   let arg2 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let ashr_result = builder.createAShr(arg1, arg2, name="result")
///   inspect(ashr_result, content="  %result = ashr i32 %0, %1")
///   assert_true(ashr_result.asValueEnum() is BinaryInst(_))
///   let twenty = ctx.getConstInt32(20) // 10100 in binary
///   let two = ctx.getConstInt32(2) // shift right by 2
///   let five = builder.createAShr(twenty, two) // 101 in binary = 5
///   inspect(five, content="i32 5")
///   assert_true(five.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createAShr(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
  is_exact? : Bool = false,
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createAShr`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createAShr`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs_ci)), Some(ConstantInt(rhs_ci))) =>
      return lhs_ci.ashr(rhs_ci)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let flags = Set::new()
  if is_exact {
    flags.add(BinaryOpFlags::Exact)
  }
  let inst = BinaryInst::newStandardOp(
    BinaryOps::AShr,
    lhs,
    rhs,
    parent,
    name=None,
    flags,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an Integer Compare Instruction.
///
/// **Note:**
///
/// 1. `lhs` and `rhs` must be integer types with the same bitwidth.
/// 2. Allowed predicates: EQ, NE, SGT, SGE, SLT, SLE, UGT, UGE, ULT, ULE.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "icmp_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg0 = fval.getArg(0).unwrap()
///   let arg1 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let eq_cmp = builder.createICmp(IntPredicate::EQ, arg0, arg1, name="eq_cmp")
///   inspect(eq_cmp, content="  %eq_cmp = icmp eq i32 %0, %1")
///   assert_true(eq_cmp.asValueEnum() is ICmpInst(_))
///   let val1 = ctx.getConstInt32(5)
///   let val2 = ctx.getConstInt32(5)
///   let result = builder.createICmpEQ(val1, val2)
///   inspect(result, content="i1 true")
///   assert_true(result.asValueEnum() is ConstantInt(_))
///   let ne_cmp = builder.createICmpNE(arg0, arg1, name="ne_cmp")
///   inspect(ne_cmp, content="  %ne_cmp = icmp ne i32 %0, %1")
///   let sgt_cmp = builder.createICmpSGT(arg0, arg1, name="sgt_cmp")
///   inspect(sgt_cmp, content="  %sgt_cmp = icmp sgt i32 %0, %1")
///   let sge_cmp = builder.createICmpSGE(arg0, arg1, name="sge_cmp")
///   inspect(sge_cmp, content="  %sge_cmp = icmp sge i32 %0, %1")
///   let slt_cmp = builder.createICmpSLT(arg0, arg1, name="slt_cmp")
///   inspect(slt_cmp, content="  %slt_cmp = icmp slt i32 %0, %1")
///   let sle_cmp = builder.createICmpSLE(arg0, arg1, name="sle_cmp")
///   inspect(sle_cmp, content="  %sle_cmp = icmp sle i32 %0, %1")
///   let ugt_cmp = builder.createICmpUGT(arg0, arg1, name="ugt_cmp")
///   inspect(ugt_cmp, content="  %ugt_cmp = icmp ugt i32 %0, %1")
///   let uge_cmp = builder.createICmpUGE(arg0, arg1, name="uge_cmp")
///   inspect(uge_cmp, content="  %uge_cmp = icmp uge i32 %0, %1")
///   let ult_cmp = builder.createICmpULT(arg0, arg1, name="ult_cmp")
///   inspect(ult_cmp, content="  %ult_cmp = icmp ult i32 %0, %1")
///   let ule_cmp = builder.createICmpULE(arg0, arg1, name="ule_cmp")
///   inspect(ule_cmp, content="  %ule_cmp = icmp ule i32 %0, %1")
/// }
/// ```
pub fn IRBuilder::createICmp(
  self : IRBuilder,
  pred : IntPredicate,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createICmp`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createICmp`, " +
      "the type of both operands must be integer types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantInt(lhs)), Some(ConstantInt(rhs))) =>
      return lhs.compare(pred, rhs)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = ICmpInst::new(pred, lhs, rhs, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create Eq (Equality) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(ICMP_EQ, ...)`.
pub fn IRBuilder::createICmpEQ(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(EQ, lhs, rhs, name~)
}

///|
/// Create Ne (Not Equal) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(ICMP_NE, ...)`.
pub fn IRBuilder::createICmpNE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(NE, lhs, rhs, name~)
}

///|
/// Create UGT (Unsigned Greater Than) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(UGT, ...)`.
pub fn IRBuilder::createICmpUGT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(UGT, lhs, rhs, name~)
}

///|
/// Create UGE (Unsigned Greater Than or Equal) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(UGE, ...)`.
pub fn IRBuilder::createICmpUGE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(UGE, lhs, rhs, name~)
}

///|
/// Create ULT (Unsigned Less Than) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(ULT, ...)`.
pub fn IRBuilder::createICmpULT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(ULT, lhs, rhs, name~)
}

///|
/// Create ULE (Unsigned Less Than or Equal) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(ULE, ...)`.
pub fn IRBuilder::createICmpULE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(ULE, lhs, rhs, name~)
}

///|
/// Create SGT (Signed Greater Than) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(SGT, ...)`.
pub fn IRBuilder::createICmpSGT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(SGT, lhs, rhs, name~)
}

///|
/// Create SGE (Signed Greater Than or Equal) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(SGE, ...)`.
pub fn IRBuilder::createICmpSGE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(SGE, lhs, rhs, name~)
}

///|
/// Create SLT (Signed Less Than) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(SLT, ...)`.
pub fn IRBuilder::createICmpSLT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(SLT, lhs, rhs, name~)
}

///|
/// Create SLE (Signed Less Than or Equal) Comparison Instruction.
///
/// It's equivalent to `IRBuilder::createICmp(SLE, ...)`.
pub fn IRBuilder::createICmpSLE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createICmp(SLE, lhs, rhs, name~)
}

///|
/// Create a Float Compare Instruction.
///
/// **Note:**
///
/// 1. `lhs` and `rhs` must be floating-point types with the same bitwidth.
/// 2. Allowed predicates: OEQ, OGT, OGE, OLT, OLE, ONE, ORD, UEQ, UGT, UGE, ULT, ULE, UNE.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [f32_ty, f32_ty])
///   let fval = mod.addFunction(fty, "fcmp_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg0 = fval.getArg(0).unwrap()
///   let arg1 = fval.getArg(1).unwrap()
///   builder.setInsertPoint(bb)
///   let oeq_cmp = builder.createFCmp(
///     FloatPredicate::OEQ,
///     arg0,
///     arg1,
///     name="oeq_cmp",
///   )
///   inspect(oeq_cmp, content="  %oeq_cmp = fcmp oeq float %0, %1")
///   assert_true(oeq_cmp.asValueEnum() is FCmpInst(_))
///   let val1 = ctx.getConstFloat(3.14)
///   let val2 = ctx.getConstFloat(3.14)
///   let result = builder.createFCmpOEQ(val1, val2)
///   inspect(result, content="i1 true")
///   assert_true(result.asValueEnum() is ConstantInt(_))
///   let ogt_cmp = builder.createFCmpOGT(arg0, arg1, name="ogt_cmp")
///   inspect(ogt_cmp, content="  %ogt_cmp = fcmp ogt float %0, %1")
///   let oge_cmp = builder.createFCmpOGE(arg0, arg1, name="oge_cmp")
///   inspect(oge_cmp, content="  %oge_cmp = fcmp oge float %0, %1")
///   let olt_cmp = builder.createFCmpOLT(arg0, arg1, name="olt_cmp")
///   inspect(olt_cmp, content="  %olt_cmp = fcmp olt float %0, %1")
///   let ole_cmp = builder.createFCmpOLE(arg0, arg1, name="ole_cmp")
///   inspect(ole_cmp, content="  %ole_cmp = fcmp ole float %0, %1")
///   let one_cmp = builder.createFCmpONE(arg0, arg1, name="one_cmp")
///   inspect(one_cmp, content="  %one_cmp = fcmp one float %0, %1")
///   let ord_cmp = builder.createFCmpORD(arg0, arg1, name="ord_cmp")
///   inspect(ord_cmp, content="  %ord_cmp = fcmp ord float %0, %1")
///   let ueq_cmp = builder.createFCmpUEQ(arg0, arg1, name="ueq_cmp")
///   inspect(ueq_cmp, content="  %ueq_cmp = fcmp ueq float %0, %1")
///   let ugt_cmp = builder.createFCmpUGT(arg0, arg1, name="ugt_cmp")
///   inspect(ugt_cmp, content="  %ugt_cmp = fcmp ugt float %0, %1")
///   let uge_cmp = builder.createFCmpUGE(arg0, arg1, name="uge_cmp")
///   inspect(uge_cmp, content="  %uge_cmp = fcmp uge float %0, %1")
///   let ult_cmp = builder.createFCmpULT(arg0, arg1, name="ult_cmp")
///   inspect(ult_cmp, content="  %ult_cmp = fcmp ult float %0, %1")
///   let ule_cmp = builder.createFCmpULE(arg0, arg1, name="ule_cmp")
///   inspect(ule_cmp, content="  %ule_cmp = fcmp ule float %0, %1")
///   let une_cmp = builder.createFCmpUNE(arg0, arg1, name="une_cmp")
///   inspect(une_cmp, content="  %une_cmp = fcmp une float %0, %1")
/// }
/// ```
pub fn IRBuilder::createFCmp(
  self : IRBuilder,
  pred : FloatPredicate,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  let (lhsTy, rhsTy) = (lhs.getType(), rhs.getType())
  guard lhsTy == rhsTy else {
    let msg = "Misuse `IRBuilder::createFCmp`, " +
      "the type of both operands must be the same, " +
      "got `\{lhsTy} and \{rhsTy}`"
    raise LLVMValueError(msg)
  }
  guard lhsTy.tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFCmp`, " +
      "the type of both operands must be floating-point types, " +
      "got `\{lhsTy}`"
    raise LLVMValueError(msg)
  }
  match (lhs.tryAsConstantEnum(), rhs.tryAsConstantEnum()) {
    (Some(ConstantFP(lhs)), Some(ConstantFP(rhs))) =>
      return lhs.compare(pred, rhs)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = FCmpInst::new(pred, lhs, rhs, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create OEQ (Ordered Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(OEQ, ...)`.
pub fn IRBuilder::createFCmpOEQ(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(OEQ, lhs, rhs, name~)
}

///|
/// Create OGT (Ordered Greater Than) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(OGT, ...)`.
pub fn IRBuilder::createFCmpOGT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(OGT, lhs, rhs, name~)
}

///|
/// Create OGE (Ordered Greater Than or Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(OGE, ...)`.
pub fn IRBuilder::createFCmpOGE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(OGE, lhs, rhs, name~)
}

///|
/// Create OLT (Ordered Less Than) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(OLT, ...)`.
pub fn IRBuilder::createFCmpOLT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(OLT, lhs, rhs, name~)
}

///|
/// Create OLE (Ordered Less Than or Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(OLE, ...)`.
pub fn IRBuilder::createFCmpOLE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(OLE, lhs, rhs, name~)
}

///|
/// Create ONE (Ordered Not Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(ONE, ...)`.
pub fn IRBuilder::createFCmpONE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(ONE, lhs, rhs, name~)
}

///|
/// Create ORD (Ordered) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(ORD, ...)`.
pub fn IRBuilder::createFCmpORD(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(ORD, lhs, rhs, name~)
}

///|
/// Create UNO (Unordered) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(UNO, ...)`.
pub fn IRBuilder::createFCmpUNO(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(UNO, lhs, rhs, name~)
}

///|
/// Create UEQ (Unordered Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(UEQ, ...)`.
pub fn IRBuilder::createFCmpUEQ(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(UEQ, lhs, rhs, name~)
}

///|
/// Create UGT (Unordered Greater Than) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(UGT, ...)`.
pub fn IRBuilder::createFCmpUGT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(UGT, lhs, rhs, name~)
}

///|
/// Create UGE (Unordered Greater Than or Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(UGE, ...)`.
pub fn IRBuilder::createFCmpUGE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(UGE, lhs, rhs, name~)
}

///|
/// Create ULT (Unordered Less Than) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(ULT, ...)`.
pub fn IRBuilder::createFCmpULT(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(ULT, lhs, rhs, name~)
}

///|
/// Create ULE (Unordered Less Than or Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(ULE, ...)`.
pub fn IRBuilder::createFCmpULE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(ULE, lhs, rhs, name~)
}

///|
/// Create UNE (Unordered Not Equal) Comparison Instruction for floating-point values.
///
/// It's equivalent to `IRBuilder::createFCmp(UNE, ...)`.
pub fn IRBuilder::createFCmpUNE(
  self : IRBuilder,
  lhs : &Value,
  rhs : &Value,
  name? : String = "",
) -> &Value raise Error {
  self.createFCmp(UNE, lhs, rhs, name~)
}

///|
/// Create a Trunc Instruction
///
/// **Note:**
/// 
/// This creates a truncation instruction that truncates an integer value to a smaller integer type.
/// The input value must be an integer type, and the target type must be a smaller integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i64_ty = ctx.getInt64Ty()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i64_ty])
///   let fval = mod.addFunction(fty, "trunc_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let trunc = builder.createTrunc(arg, i32_ty, name="truncated")
///   inspect(trunc, content="  %truncated = trunc i64 %0 to i32")
///   assert_true(trunc.asValueEnum() is CastInst(_))
///   let big_val = ctx.getConstInt64(0x123456789L)
///   let small_val = builder.createTrunc(big_val, i32_ty)
///   inspect(small_val, content="i32 591751049")
///   assert_true(small_val.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createTrunc(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(src_ty) else {
    let msg = "Misuse `IRBuilder::createTrunc`, " +
      "the source value must be an integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  guard src_ty.getBitWidth() > dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createTrunc`, " +
      "the destination type must be smaller than the source type, " +
      "got `\{src_ty.getBitWidth()} bits` and `\{dst_ty.getBitWidth()} bits`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.trunc(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newTrunc(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a ZExt Instruction
///
/// **Note:**
/// 
/// This creates a zero extension instruction that extends an integer value to a larger integer type by padding with zeros.
/// The input value must be an integer type, and the target type must be a larger integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let i64_ty = ctx.getInt64Ty()
///   let fty = ctx.getFunctionType(i64_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "zext_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let zext = builder.createZExt(arg, i64_ty, name="extended")
///   inspect(zext, content="  %extended = zext i32 %0 to i64")
///   assert_true(zext.asValueEnum() is CastInst(_))
///   let small_val = ctx.getConstInt32(42)
///   let big_val = builder.createZExt(small_val, i64_ty)
///   inspect(big_val, content="i64 42")
///   assert_true(big_val.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createZExt(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(src_ty) else {
    let msg = "Misuse `IRBuilder::createZExt`, " +
      "the source value must be an integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createZExt`, " +
      "the destination type must be larger than the source type, " +
      "got `\{src_ty.getBitWidth()} bits` and `\{dst_ty.getBitWidth()} bits`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.zext(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newZExt(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a SExt Instruction
///
/// **Note:**
/// 
/// This creates a sign extension instruction that extends an integer value to a larger integer type by replicating the sign bit.
/// The input value must be an integer type, and the target type must be a larger integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let i64_ty = ctx.getInt64Ty()
///   let fty = ctx.getFunctionType(i64_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "sext_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let sext = builder.createSExt(arg, i64_ty, name="extended")
///   inspect(sext, content="  %extended = sext i32 %0 to i64")
///   assert_true(sext.asValueEnum() is CastInst(_))
///   let small_val = ctx.getConstInt32(-42)
///   let big_val = builder.createSExt(small_val, i64_ty)
///   inspect(big_val, content="i64 -42")
///   assert_true(big_val.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createSExt(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(src_ty) else {
    let msg = "Misuse `IRBuilder::createSExt`, " +
      "the source value must be an integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createSExt`, " +
      "the destination type must be larger than the source type, " +
      "got `\{src_ty.getBitWidth()} bits` and `\{dst_ty.getBitWidth()} bits`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.sext(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newSExt(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a FPTrunc Instruction
///
/// **Note:**
/// 
/// This creates a floating-point truncation instruction that truncates a floating-point value to a smaller floating-point type.
/// The input value must be a floating-point type, and the target type must be a smaller floating-point type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f64_ty = ctx.getDoubleTy()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [f64_ty])
///   let fval = mod.addFunction(fty, "fptrunc_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let fptrunc = builder.createFPTrunc(arg, f32_ty, name="truncated")
///   inspect(fptrunc, content="  %truncated = fptrunc double %0 to float")
///   assert_true(fptrunc.asValueEnum() is CastInst(_))
///   let big_val = ctx.getConstDouble(3.14159)
///   let small_val = builder.createFPTrunc(big_val, f32_ty)
///   assert_true(small_val.asValueEnum() is ConstantFP(_))
/// }
/// ```
pub fn IRBuilder::createFPTrunc(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &FPType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsFPTypeEnum() is Some(src_ty) else {
    let msg = "Misuse `IRBuilder::createFPTrunc`, " +
      "the source value must be a floating-point type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  guard src_ty.getBitWidth() > dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createFPTrunc`, " +
      "the destination type must be smaller than the source type, " +
      "got `\{src_ty.getBitWidth()} bits` and `\{dst_ty.getBitWidth()} bits`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantFP(cf)) => return cf.fptrunc(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newFPTrunc(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a FPExt Instruction
///
/// **Note:**
/// 
/// This creates a floating-point extension instruction that extends a floating-point value to a larger floating-point type.
/// The input value must be a floating-point type, and the target type must be a larger floating-point type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f32_ty = ctx.getFloatTy()
///   let f64_ty = ctx.getDoubleTy()
///   let fty = ctx.getFunctionType(f64_ty, [f32_ty])
///   let fval = mod.addFunction(fty, "fpext_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let fpext = builder.createFPExt(arg, f64_ty, name="extended")
///   inspect(fpext, content="  %extended = fpext float %0 to double")
/// }
/// ```
pub fn IRBuilder::createFPExt(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &FPType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsFPTypeEnum() is Some(src_ty) else {
    let msg = "Misuse `IRBuilder::createFPExt`, " +
      "the source value must be a floating-point type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  guard src_ty.getBitWidth() < dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createFPExt`, " +
      "the destination type must be larger than the source type, " +
      "got `\{src_ty.getBitWidth()} bits` and `\{dst_ty.getBitWidth()} bits`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantFP(cf)) => return cf.fpext(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newFPExt(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a UIToFP Instruction
///
/// **Note:**
/// 
/// This creates an unsigned integer to floating-point conversion instruction that converts an unsigned integer value to a floating-point value.
/// The input value must be an integer type, and the target type must be a floating-point type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "uitofp_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let uitofp = builder.createUIToFP(arg, f32_ty, name="converted")
///   inspect(uitofp, content="  %converted = uitofp i32 %0 to float")
///   assert_true(uitofp.asValueEnum() is CastInst(_))
///   let int_val = ctx.getConstInt32(42)
///   let float_val = builder.createUIToFP(int_val, f32_ty)
///   assert_true(float_val.asValueEnum() is ConstantFP(_))
/// }
/// ```
pub fn IRBuilder::createUIToFP(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &FPType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createUIToFP`, " +
      "the source value must be an unsigned integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.uitofp(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newUIToFP(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a FPToUI Instruction
///
/// **Note:**
/// 
/// This creates a floating-point to unsigned integer conversion instruction that converts a floating-point value to an unsigned integer.
/// The input value must be a floating-point type, and the target type must be an integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f32_ty = ctx.getFloatTy()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [f32_ty])
///   let fval = mod.addFunction(fty, "fptoui_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let fptoui = builder.createFPToUI(arg, i32_ty, name="converted")
///   inspect(fptoui, content="  %converted = fptoui float %0 to i32")
///   assert_true(fptoui.asValueEnum() is CastInst(_))
///   let float_val = ctx.getConstFloat(3.14)
///   let int_val = builder.createFPToUI(float_val, i32_ty)
///   inspect(int_val, content="i32 3")
///   assert_true(int_val.asValueEnum() is ConstantInt(_))
/// }
/// ```
pub fn IRBuilder::createFPToUI(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFPToUI`, " +
      "the source value must be a floating-point type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantFP(cf)) => return cf.fptoui(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newFPToUI(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a SIToFP Instruction
///
/// **Note:**
/// 
/// This creates a signed integer to floating-point conversion instruction that converts a signed integer value to a floating-point value.
/// The input value must be an integer type, and the target type must be a floating-point type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "sitofp_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let sitofp = builder.createSIToFP(arg, f32_ty, name="converted")
///   inspect(sitofp, content="  %converted = sitofp i32 %0 to float")
/// }
/// ```
pub fn IRBuilder::createSIToFP(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &FPType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createSIToFP`, " +
      "the source value must be a signed integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.sitofp(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newSIToFP(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a FPToSI Instruction
///
/// **Note:**
/// 
/// This creates a floating-point to signed integer conversion instruction that converts a floating-point value to a signed integer.
/// The input value must be a floating-point type, and the target type must be an integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let f32_ty = ctx.getFloatTy()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [f32_ty])
///   let fval = mod.addFunction(fty, "fptosi_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let fptosi = builder.createFPToSI(arg, i32_ty, name="converted")
///   inspect(fptosi, content="  %converted = fptosi float %0 to i32")
/// }
/// ```
pub fn IRBuilder::createFPToSI(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsFPTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createFPToSI`, " +
      "the source value must be a floating-point type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantFP(cf)) => return cf.fptosi(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newFPToSI(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an IntToPtr Instruction
///
/// **Note:**
/// 
/// This creates an integer to pointer conversion instruction that converts an integer value to a pointer.
/// The input value must be an integer type, and the target type must be a pointer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i64_ty = ctx.getInt64Ty()
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ptr_ty, [i64_ty])
///   let fval = mod.addFunction(fty, "inttoptr_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let inttoptr = builder.createIntToPtr(arg, name="ptr")
///   inspect(inttoptr, content="  %ptr = inttoptr i64 %0 to ptr")
/// }
/// ```
pub fn IRBuilder::createIntToPtr(
  self : IRBuilder,
  src_val : &Value,
  name? : String = "",
) -> &Value raise Error {
  guard src_val.getType().tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createIntToPtr`, " +
      "the source value must be an integer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.inttoptr()
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newIntToPtr(src_val, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a PtrToInt Instruction
///
/// **Note:**
/// 
/// This creates a pointer to integer conversion instruction that converts a pointer value to an integer.
/// The input value must be a pointer type, and the target type must be an integer type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i64_ty = ctx.getInt64Ty()
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(i64_ty, [ptr_ty])
///   let fval = mod.addFunction(fty, "ptrtoint_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let ptrtoint = builder.createPtrToInt(arg, i64_ty, name="int_val")
///   inspect(ptrtoint, content="  %int_val = ptrtoint ptr %0 to i64")
/// }
/// ```
pub fn IRBuilder::createPtrToInt(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &IntegerType,
  name? : String = "",
) -> &Value raise Error {
  // No constant folding for PtrToInt as pointer constants are complex
  guard src_val.getType().asTypeEnum() is PointerType(_) else {
    let msg = "Misuse `IRBuilder::createPtrToInt`, " +
      "the source value must be a pointer type, " +
      "got `\{src_val.getType()}`"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newPtrToInt(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a BitCast Instruction
///
/// **Note:**
/// 
/// This creates a bitcast instruction that converts a value from one type to another without changing the bit representation.
/// The source and destination types must have the same bit width.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let f32_ty = ctx.getFloatTy()
///   let fty = ctx.getFunctionType(f32_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "bitcast_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   builder.setInsertPoint(bb)
///   let bitcast = builder.createBitCast(arg, f32_ty, name="bits")
///   inspect(bitcast, content="  %bits = bitcast i32 %0 to float")
/// }
/// ```
pub fn IRBuilder::createBitCast(
  self : IRBuilder,
  src_val : &Value,
  dst_ty : &PrimitiveType,
  name? : String = "",
) -> &Value raise Error {
  let src_ty = src_val.getType()
  guard src_ty != dst_ty else {
    let msg = "Misuse `IRBuilder::createBitCast`, " +
      "the source and destination types must be different, " +
      "got \{src_ty} and \{dst_ty}"
    raise LLVMValueError(msg)
  }
  guard src_ty.tryAsPrimitiveTypeEnum() is Some(src_ty_prim) else {
    let msg = "Misuse `IRBuilder::createBitCast`, " +
      "the source value must be a primitive type, " +
      "got \{src_ty}"
    raise LLVMValueError(msg)
  }
  guard src_ty_prim.getBitWidth() == dst_ty.getBitWidth() else {
    let msg = "Misuse `IRBuilder::createBitCast`, " +
      "the source and destination types must have the same bit width, " +
      "got \{src_ty_prim.getBitWidth()} bits and \{dst_ty.getBitWidth()} bits"
    raise LLVMValueError(msg)
  }
  match src_val.tryAsConstantEnum() {
    Some(ConstantInt(ci)) => return ci.bitcast(dst_ty)
    Some(ConstantFP(cf)) => return cf.bitcast(dst_ty)
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = CastInst::newBitCast(src_val, dst_ty, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a GetElementPtr Instruction
///
/// **Note:**
/// 
/// This creates a getelementptr instruction that calculates the address of a sub-element of an aggregate object.
/// The pointer must be a pointer type, and all indices must be integer values.
/// When inbounds is true, the result is undefined if the computed address is outside the bounds of the allocated object.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let array_ty = ctx.getArrayType(i32_ty, 10)
///   let ptr_ty = ctx.getPtrTy()
///   let fty = ctx.getFunctionType(ctx.getPtrTy(), [ptr_ty])
///   let fval = mod.addFunction(fty, "gep_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let arg = fval.getArg(0).unwrap()
///   let zero = ctx.getConstInt32(0)
///   let two = ctx.getConstInt32(2)
///   builder.setInsertPoint(bb)
///   let gep = builder.createGEP(
///     arg,
///     array_ty,
///     [zero, two],
///     name="elem_ptr",
///     inbounds=true,
///   )
///   inspect(
///     gep,
///     content="  %elem_ptr = getelementptr inbounds [10 x i32], ptr %0, i32 0, i32 2",
///   )
/// }
/// ```
pub fn IRBuilder::createGEP(
  self : IRBuilder,
  ptr : &Value,
  pointeeType : &Type,
  indices : Array[&Value],
  name? : String = "",
  inbounds? : Bool = false,
) -> &Value raise Error {
  guard ptr.getType().asTypeEnum() is PointerType(_) else {
    let msg = "Misuse `IRBuilder::createGEP`: " +
      "getelementptr instruction must operate on a pointer, " +
      "got \{ptr.getType()}"
    raise LLVMValueError(msg)
  }
  guard pointeeType.isValidGEPType() else {
    let msg = "Misuse `IRBuilder::createGEP`: " +
      "getelementptr instruction must operate on a valid pointee type, " +
      "got \{pointeeType}"
    raise LLVMValueError(msg)
  }
  for idx in indices {
    guard idx.getType().tryAsIntTypeEnum() is Some(_) else {
      let msg = "Misuse `IRBuilder::createGEP`: " +
        "getelementptr instruction indices must be integer types, " +
        "got \{idx.getType()}"
      raise LLVMValueError(msg)
    }
  }
  let parent = self.getInsertFunction()
  let inst = GetElementPtrInst::new(
    ptr,
    pointeeType,
    indices,
    inbounds,
    parent,
    name=None,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a Load Instruction
///
/// **Note:**
/// 
/// This loads a value from memory at the specified pointer.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(ctx.getVoidTy(), [])
///   let fval = mod.addFunction(fty, "load_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let alloca = builder.createAlloca(i32_ty, name="temp")
///   let load = builder.createLoad(i32_ty, alloca, name="val")
///   inspect(load, content="  %val = load i32, ptr %temp, align 4")
/// }
/// ```
pub fn IRBuilder::createLoad(
  self : IRBuilder,
  load_ty : &Type,
  ptr : &Value,
  isVolatile? : Bool = false,
  atomicOrdering? : AtomicOrdering = NotAtomic,
  name? : String = "",
) -> &Value raise Error {
  guard ptr.getType().asTypeEnum() is PointerType(_) else {
    let msg = "Misuse `IRBuilder::createLoad`: " +
      "load instruction must load from a pointer, " +
      "got \{ptr.getType()}"
    raise LLVMValueError(msg)
  }
  guard load_ty.tryAsAbstractTypeEnum() is None else {
    let msg = "Misuse `IRBuilder::createLoad`: " +
      "load instruction must load to a concrete type, " +
      "got \{load_ty}"
    raise LLVMValueError(msg)
  }
  guard not(isVolatile && not(atomicOrdering is NotAtomic)) else {
    let msg = "Misuse `IRBuilder::createLoad`: " +
      "load instruction cannot be both volatile and atomic"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = LoadInst::new(
    load_ty,
    ptr,
    isVolatile,
    atomicOrdering,
    parent,
    name=None,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a Store Instruction
///
/// **Note:**
/// 
/// This stores a value into memory at the specified pointer.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(ctx.getVoidTy(), [])
///   let fval = mod.addFunction(fty, "store_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   builder.setInsertPoint(bb)
///   let alloca = builder.createAlloca(i32_ty, name="temp")
///   let const_val = ctx.getConstInt32(42)
///   let store = builder.createStore(const_val, alloca)
///   inspect(store, content="  store i32 42, ptr %temp, align 4")
/// }
/// ```
pub fn IRBuilder::createStore(
  self : IRBuilder,
  value : &Value,
  ptr : &Value,
  isVolatile? : Bool = false,
  atomicOrdering? : AtomicOrdering = NotAtomic,
) -> StoreInst raise Error {
  guard ptr.getType().asTypeEnum() is PointerType(_) else {
    let msg = "Misuse `IRBuilder::createStore`: " +
      "store instruction must store to a pointer, " +
      "got \{ptr.getType()}"
    raise LLVMValueError(msg)
  }
  guard not(isVolatile && not(atomicOrdering is NotAtomic)) else {
    let msg = "Misuse `IRBuilder::createStore`: " +
      "store instruction cannot be both volatile and atomic"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = StoreInst::new(value, ptr, isVolatile, atomicOrdering, parent)
  self.insert(inst)
  inst
}

///|
/// Create an Unconditional Branch Instruction
///
/// **Note:**
/// 
/// This creates an unconditional branch instruction that transfers control to the specified basic block.
/// The destination must be a valid basic block.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [])
///   let fval = mod.addFunction(fty, "br_demo")
///   let entry_bb = fval.addBasicBlock(name="entry")
///   let target_bb = fval.addBasicBlock(name="target")
///   builder.setInsertPoint(entry_bb)
///   let br = builder.createBr(target_bb)
///   inspect(br, content="  br label %target")
/// }
/// ```
pub fn IRBuilder::createBr(
  self : Self,
  dst : BasicBlock,
) -> &Instruction raise Error {
  let parent = self.getInsertFunction()
  let currBlock = self.getInsertBlock()
  let inst = BranchInst::newUnconditional(dst, parent)
  self.insert(inst)
  dst.preds.push(currBlock)
  inst
}

///|
/// Create a Conditional Branch Instruction
///
/// **Note:**
/// 
/// This creates a conditional branch instruction that transfers control to one of two basic blocks based on a boolean condition.
/// The condition must be an i1 (boolean) type, and both destinations must be valid basic blocks.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i1_ty = ctx.getInt1Ty()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [i1_ty])
///   let fval = mod.addFunction(fty, "cond_br_demo")
///   let entry_bb = fval.addBasicBlock(name="entry")
///   let true_bb = fval.addBasicBlock(name="true_branch")
///   let false_bb = fval.addBasicBlock(name="false_branch")
///   let cond = fval.getArg(0).unwrap()
///   builder.setInsertPoint(entry_bb)
///   let cond_br = builder.createCondBr(cond, true_bb, false_bb)
///   inspect(
///     cond_br,
///     content="  br i1 %0, label %true_branch, label %false_branch",
///   )
/// }
/// ```
pub fn IRBuilder::createCondBr(
  self : Self,
  cond : &Value,
  true_dst : BasicBlock,
  false_dst : BasicBlock,
) -> &Instruction raise Error {
  guard cond.getType().tryAsIntTypeEnum() is Some(Int1Type(_)) else {
    let msg = "Misuse `IRBuilder::createCondBr`, " +
      "the condition must be an i1 (boolean) type, " +
      "got \{cond.getType()}"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = BranchInst::newConditional(cond, true_dst, false_dst, parent)
  self.insert(inst)
  let currBlock = self.getInsertBlock()
  true_dst.preds.push(currBlock)
  false_dst.preds.push(currBlock)
  inst
}

///|
/// Create a Select Instruction
///
/// **Note:**
/// 
/// This creates a select instruction that chooses between two values based on a boolean condition.
/// The condition must be an i1 (boolean) type, and both values must have the same type.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i1_ty = ctx.getInt1Ty()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [i1_ty, i32_ty, i32_ty])
///   let fval = mod.addFunction(fty, "select_demo")
///   let bb = fval.addBasicBlock(name="entry")
///   let cond = fval.getArg(0).unwrap()
///   let true_val = fval.getArg(1).unwrap()
///   let false_val = fval.getArg(2).unwrap()
///   builder.setInsertPoint(bb)
///   let select = builder.createSelect(cond, true_val, false_val, name="result")
///   inspect(select, content="  %result = select i1 %0, i32 %1, i32 %2")
/// }
/// ```
pub fn IRBuilder::createSelect(
  self : IRBuilder,
  condition : &Value,
  trueValue : &Value,
  falseValue : &Value,
  name? : String = "",
) -> &Value raise Error {
  guard condition.getType().tryAsIntTypeEnum() is Some(Int1Type(_)) else {
    let msg = "Misuse `IRBuilder::createSelect`, " +
      "the condition must be an i1 (boolean) type, " +
      "got \{condition.getType()}"
    raise LLVMValueError(msg)
  }
  let vty = trueValue.getType()
  guard vty == falseValue.getType() else {
    let msg = "Misuse `IRBuilder::createSelect`, " +
      "the true and false values must have the same type, " +
      "got \{vty} and \{falseValue.getType()}"
    raise LLVMValueError(msg)
  }
  match condition.tryAsConstantEnum() {
    Some(ConstantInt(ci)) =>
      if ci.getValue() != 0L {
        return trueValue
      } else {
        return falseValue
      }
    _ => ()
  }
  let parent = self.getInsertFunction()
  let inst = SelectInst::new(
    condition,
    trueValue,
    falseValue,
    parent,
    name=None,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create a Switch Instruction
///
/// **Note:**
/// 
/// This creates a switch instruction that transfers control to one of many basic blocks based on an integer value.
/// The value must be an integer type, and numCases specifies the expected number of cases.
/// Use `SwitchInst::addCase` to add individual cases after creation.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let void_ty = ctx.getVoidTy()
///   let fty = ctx.getFunctionType(void_ty, [i32_ty])
///   let fval = mod.addFunction(fty, "switch_demo")
///   let entry_bb = fval.addBasicBlock(name="entry")
///   let case1_bb = fval.addBasicBlock(name="case1")
///   let default_bb = fval.addBasicBlock(name="default")
///   let value = fval.getArg(0).unwrap()
///   builder.setInsertPoint(entry_bb)
///   let switch = builder.createSwitch(value, default_bb)
///   let case_val = ctx.getConstInt32(42)
///   switch.addCase(case_val, case1_bb)
///   let expect =
///     #|  switch i32 %0, label %default [
///     #|    i32 42, label %case1
///     #|  ]
///   inspect(switch, content=expect)
/// }
/// ```
pub fn IRBuilder::createSwitch(
  self : IRBuilder,
  cond : &Value,
  defaultDest : BasicBlock,
) -> SwitchInst raise Error {
  guard cond.getType().tryAsIntTypeEnum() is Some(_) else {
    let msg = "Misuse `IRBuilder::createSwitch`, " +
      "the condition must be an integer type, " +
      "got \{cond.getType()}"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = SwitchInst::new(cond, defaultDest, parent)
  self.insert(inst)
  let currBlock = self.getInsertBlock()
  defaultDest.preds.push(currBlock)
  inst
}

///|
/// Create a PHI Instruction
///
/// **Note:**
/// 
/// This creates a PHI node instruction that selects a value based on the predecessor basic block.
/// The type specifies the type of the PHI node's result value.
/// Use `PHINode::addIncoming` to add incoming values and their corresponding basic blocks.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let fty = ctx.getFunctionType(i32_ty, [])
///   let fval = mod.addFunction(fty, "phi_demo")
///   let entry_bb = fval.addBasicBlock(name="entry")
///   let block_bb = fval.addBasicBlock(name="block")
///   let merge_bb = fval.addBasicBlock(name="merge")
///   let val1 = ctx.getConstInt32(10)
///   let val2 = ctx.getConstInt32(20)
///   builder.setInsertPoint(merge_bb)
///   let phi = builder.createPHI(i32_ty, name="result")
///   phi.addIncoming(val1, entry_bb)
///   phi.addIncoming(val2, block_bb)
///   inspect(phi, content="  %result = phi i32 [ 10, %entry ], [ 20, %block ]")
/// }
/// ```
pub fn IRBuilder::createPHI(
  self : IRBuilder,
  ty : &Type,
  name? : String = "",
) -> PHINode raise Error {
  let parent = self.getInsertFunction()
  let phi = PHINode::new(ty, parent, name=None)
  match name {
    "" => ()
    _ => phi.setName(name)
  }
  self.insert(phi)
  phi
}

///|
/// Create a Call Instruction
///
/// **Note:**
/// 
/// This creates a call instruction that invokes a function with the specified arguments.
/// The function must be a valid Function object, and the arguments must match the function's parameter types.
///
/// ```mbt check
/// test {
///   let ctx = Context::new()
///   let mod = ctx.addModule("demo")
///   let builder = ctx.createBuilder()
///   let i32_ty = ctx.getInt32Ty()
///   let add_fty = ctx.getFunctionType(i32_ty, [i32_ty, i32_ty])
///   let main_fty = ctx.getFunctionType(i32_ty, [])
///   let add_func = mod.addFunction(add_fty, "add")
///   let main_func = mod.addFunction(main_fty, "main")
///   let bb = main_func.addBasicBlock(name="entry")
///   let arg1 = ctx.getConstInt32(10)
///   let arg2 = ctx.getConstInt32(20)
///   builder.setInsertPoint(bb)
///   let call = builder.createCall(add_func, [arg1, arg2], name="sum")
///   inspect(call, content="  %sum = call i32 @add(i32 10, i32 20)")
/// }
/// ```
pub fn IRBuilder::createCall(
  self : IRBuilder,
  callee : Function,
  args : Array[&Value],
  name? : String = "",
) -> CallInst raise Error {
  let fty = callee.getFunctionType()
  let expected_params = fty.getNumParams()
  let num_args = args.length()
  if fty.isVarArg {
    guard num_args >= expected_params else {
      let msg = "Misuse `IRBuilder::createCall`, " +
        "expected at least \{expected_params} arguments for variadic function, " +
        "got \{args.length()} arguments"
      raise LLVMValueError(msg)
    }
  } else {
    guard num_args == expected_params else {
      let msg = "Misuse `IRBuilder::createCall`, " +
        "expected \{expected_params} arguments, " +
        "got \{args.length()} arguments"
      raise LLVMValueError(msg)
    }
  }
  fn min(a, b) {
    if a < b {
      a
    } else {
      b
    }
  }

  let min_param_cnt = min(num_args, expected_params)
  for i in 0.. true
      _ => false
    }
    guard param_ty == arg_ty || is_function_to_ptr else {
      let msg = "Misuse `IRBuilder::createCall`, " +
        "argument type does not match function parameter type at index \{i}, " +
        "got \{arg_ty} but expected \{param_ty}"
      raise LLVMValueError(msg)
    }
  }
  let parent = self.getInsertFunction()
  let inst = CallInst::new(callee, args, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
pub fn IRBuilder::createCallPtr(
  self : IRBuilder,
  callee : &Value,
  functionType : FunctionType,
  args : Array[&Value],
  name? : String = "",
) -> CallInst raise Error {
  let expected_params = functionType.getNumParams()
  let num_args = args.length()
  if functionType.isVarArg {
    guard num_args >= expected_params else {
      let msg = "Misuse `IRBuilder::createCallPtr`, " +
        "expected at least \{expected_params} arguments for variadic function, " +
        "got \{args.length()} arguments"
      raise LLVMValueError(msg)
    }
  } else {
    guard num_args == expected_params else {
      let msg = "Misuse `IRBuilder::createCallPtr`, " +
        "expected \{expected_params} arguments, " +
        "got \{args.length()} arguments"
      raise LLVMValueError(msg)
    }
  }
  fn min(a, b) {
    if a < b {
      a
    } else {
      b
    }
  }

  let min_param_cnt = min(num_args, expected_params)
  for i in 0.. true
      _ => false
    }
    guard param_ty == arg_ty || is_function_to_ptr else {
      let msg = "Misuse `IRBuilder::createCallPtr`, " +
        "argument type does not match function parameter type at index \{i}, " +
        "got \{arg_ty} but expected \{param_ty}"
      raise LLVMValueError(msg)
    }
  }
  let parent = self.getInsertFunction()
  let inst = CallInst::new_indirect(
    callee,
    functionType,
    args,
    parent,
    name=None,
  )
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an ExtractValue Instruction
///
/// **Note:**
/// 
/// This creates an extractvalue instruction that extracts a value from an aggregate (struct or array) at the specified index.
/// The aggregate must be an aggregate type, and the index must be valid for the aggregate type.
///
/// ```mbt
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let struct_ty = ctx.getStructType([i32_ty, i32_ty])
/// let fty = ctx.getFunctionType(i32_ty, [struct_ty])
///
/// let fval = mod.addFunction(fty, "extractvalue_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let aggregate = fval.getArg(0).unwrap()
///
/// builder.setInsertPoint(bb)
/// let extract = builder.createExtractValue(aggregate, 0, name="field")
///
/// inspect(extract, content = "  %field = extractvalue { i32, i32 } %0, 0")
/// ```
pub fn IRBuilder::createExtractValue(
  self : IRBuilder,
  aggregate : &Value,
  indices : Array[Int],
  name? : String = "",
) -> &Value raise Error {
  guard not(indices.is_empty()) else {
    let msg = "Misuse `IRBuilder::createExtractValue`, " +
      "indices must not be empty"
    raise LLVMValueError(msg)
  }
  let agg_ty = aggregate.getType()
  guard agg_ty.tryAsAggregateTypeEnum() is Some(agg_ty) else {
    let msg = "Misuse `IRBuilder::createExtractValue`, " +
      "the aggregate must be an aggregate type, " +
      "got \{agg_ty}"
    raise LLVMValueError(msg)
  }
  guard agg_ty.getIndexedType(indices) is Some(_) else {
    let msg = "Misuse `IRBuilder::createExtractValue`, " +
      "invalid index for aggregate type \{agg_ty}, " +
      "indices: \{indices}"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = ExtractValueInst::new(aggregate, indices, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
/// Create an InsertValue Instruction
///
/// **Note:**
/// 
/// This creates an insertvalue instruction that inserts a value into an aggregate (struct or array) at the specified index.
/// The aggregate must be an aggregate type, and the value type must match the element type at the given index.
///
/// ```mbt
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let struct_ty = ctx.getStructType([i32_ty, i32_ty])
/// let fty = ctx.getFunctionType(struct_ty, [struct_ty, i32_ty])
///
/// let fval = mod.addFunction(fty, "insertvalue_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let aggregate = fval.getArg(0).unwrap()
/// let new_value = fval.getArg(1).unwrap()
///
/// builder.setInsertPoint(bb)
/// let insert = builder.createInsertValue(aggregate, new_value, 1, name="updated")
///
/// inspect(insert, content = "  %updated = insertvalue { i32, i32 } %0, i32 %1, 1")
/// ```
pub fn IRBuilder::createInsertValue(
  self : IRBuilder,
  aggregate : &Value,
  value : &Value,
  indices : Array[Int],
  name? : String = "",
) -> &Value raise Error {
  guard not(indices.is_empty()) else {
    let msg = "Misuse `IRBuilder::createInsertValue`, " +
      "indices must not be empty"
    raise LLVMValueError(msg)
  }
  let agg_ty = aggregate.getType()
  guard agg_ty.tryAsAggregateTypeEnum() is Some(agg_ty) else {
    let msg = "Misuse `IRBuilder::createInsertValue`, " +
      "the aggregate must be an aggregate type, " +
      "got \{agg_ty}"
    raise LLVMValueError(msg)
  }
  guard agg_ty.getIndexedType(indices) is Some(_) else {
    let msg = "Misuse `IRBuilder::createInsertValue`, " +
      "invalid index for aggregate type \{agg_ty}, " +
      "indices: \{indices}"
    raise LLVMValueError(msg)
  }
  let parent = self.getInsertFunction()
  let inst = InsertValueInst::new(aggregate, value, indices, parent, name=None)
  match name {
    "" => ()
    _ => inst.setName(name)
  }
  self.insert(inst)
  inst
}

///|
pub fn IRBuilder::createGlobalString(
  self : Self,
  string : String,
  name? : String = "",
) -> GlobalConstant raise {
  let mod = self.getModule()
  mod.addGlobalString(string, name~)
}

///| Create a Malloc Instruction
///
/// **Note:**
/// 
/// This creates a malloc instruction that allocates memory for a single instance of the specified type.
/// The type must not be an abstract type. The result is a pointer to the allocated memory.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let i32_ty = ctx.getInt32Ty()
/// let ptr_ty = ctx.getPtrTy()
/// let fty = ctx.getFunctionType(ptr_ty, [])
///
/// let fval = mod.addFunction(fty, "malloc_demo")
/// let bb = fval.addBasicBlock(name="entry")
///
/// builder.setInsertPoint(bb)
/// let malloc = builder.createMalloc(i32_ty, name="ptr")
///
/// inspect(malloc, content = "  %ptr = tail call ptr @malloc(i32 ptrtoint (ptr getelementptr (i32, ptr null, i32 1) to i32))")
/// ```
//pub fn IRBuilder::createMalloc(
//  self : Self,
//  ty : &Type,
//  name~ : String = "",
//  loc~ : SourceLoc = _
//) -> CallInst raise {

///| Create a Free Instruction
///
/// **Note:**
/// 
/// This creates a free instruction that deallocates memory previously allocated by malloc.
/// The pointer must be a pointer type and should point to memory allocated by malloc.
///
/// ```mbt skip
/// let ctx = Context::new()
/// let mod = ctx.addModule("demo")
/// let builder = ctx.createBuilder()
///
/// let void_ty = ctx.getVoidTy()
/// let ptr_ty = ctx.getPtrTy()
/// let fty = ctx.getFunctionType(void_ty, [ptr_ty])
///
/// let fval = mod.addFunction(fty, "free_demo")
/// let bb = fval.addBasicBlock(name="entry")
/// let ptr_arg = fval.getArg(0).unwrap()
///
/// builder.setInsertPoint(bb)
/// let free = builder.createFree(ptr_arg)
///
/// inspect(free, content = "  tail call void @free(ptr %0)")
/// ```
//pub fn IRBuilder::createFree(
//  self : Self,
//  ptr : &Value,
//  loc~ : SourceLoc = _
//) -> CallInst raise {