code: def cast(bb, val, input_type, output_type, bitcast=False):
      if input_type == output_type:
          return val
      if bitcast:
          return bb[-1].bitcast(val, dtype_to_llvm_dtype[output_type])
  
      if input_type == dtypes.bfloat16:
          val = bb[-1].bitcast(
              bb[-1].shl(
                  bb[-1].sext(val, ir.IntType(32)),
                  ir.Constant(ir.IntType(32), 16),
              ),
              val,
              ir.FloatType(),
          )
          input_type = dtypes.float32
      if output_type == dtypes.bfloat16:
          val = cast(bb, val, input_type, dtypes.float32)
          return bb[-1].trunc(
              bb[-1].lshr(
                  bb[-1].bitcast(val, ir.IntType(32)),
                  ir.Constant(ir.IntType(32), 16),
              ),
              ir.IntType(16),
          )
  
      if dtypes.is_float(input_type):
          if dtypes.is_float(output_type):
              if output_type.itemsize > input_type.itemsize:
                  return bb[-1].fpext(
                      val, dtype_to_llvm_dtype[output_type]
                  )
              return bb[-1].fptrunc(
                  val, dtype_to_llvm_dtype[output_type]
              )
          if dtypes.is_int(output_type):
              if dtypes.is_unsigned(output_type):
                  return bb[-1].fptoui(
                      val, dtype_to_llvm_dtype[output_type]
                  )
              return bb[-1].fptosi(
                  val, dtype_to_llvm_dtype[output_type]
              )
          if output_type == dtypes.bool:
              return bb[-1].fcmp_unordered(
                  "!=",
                  cast(bb, val, input_type, dtypes.float32),
                  ir.Constant(ir.FloatType(), 0),
              )
  
      if dtypes.is_unsigned(input_type) or input_type == dtypes.bool:
          if output_type == dtypes.float16:
              return bb[-1].fptrunc(
                  bb[-1].uitofp(val, ir.FloatType()), ir.HalfType()
              )
          if dtypes.is_float(output_type):
              return bb[-1].uitofp(
                  val, dtype_to_llvm_dtype[output_type]
              )
          if dtypes.is_int(output_type):
              if input_type.itemsize > output_type.itemsize:
                  return bb[-1].trunc(
                      val, dtype_to_llvm_dtype[output_type]
                  )
              return bb[-1].zext(val, dtype_to_llvm_dtype[output_type])
          if output_type == dtypes.bool:
              return bb[-1].icmp_unsigned(
                  "!=", val, ir.Constant(val.type, 0)
              )
  
      if dtypes.is_int(input_type):
          if output_type == dtypes.float16:
              return bb[-1].fptrunc(
                  bb[-1].sitofp(val, ir.FloatType()), ir.HalfType()
              )
          if dtypes.is_float(output_type):
              return bb[-1].sitofp(
                  val, dtype_to_llvm_dtype[output_type]
              )
          if dtypes.is_int(output_type):
              if input_type.itemsize > output_type.itemsize:
                  return bb[-1].trunc(
                      val, dtype_to_llvm_dtype[output_type]
                  )
              return bb[-1].sext(val, dtype_to_llvm_dtype[output_type])
          if output_type == dtypes.bool:
              return bb[-1].icmp_signed(
                  "!=", val, ir.Constant(val.type, 0)
              )
  
      raise NotImplementedError(
          f"cast from {input_type} -> {output_type} not implemented"
      )
  
file_path: tinygrad/tinygrad/renderer/llvmir.py
imports: from typing import Final, Dict, Callable, Any, List, Optional
  from llvmlite import ir
  from tinygrad.codegen.linearizer import UOps, UOp
  from tinygrad.dtype import DType, PtrDType, dtypes
  from tinygrad.ops import Op, UnaryOps, BinaryOps, TernaryOps
label_desc: Use image node_id 2 for calling a global function with example usage: cast(bb, val, input_type, output_type, bitcast) and returns: val, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb, bb