content: OP_COUNT = 0
  
  @llvm
  def inc(a: int) -> int:
      %tmp = add i64 %a, 1
      ret i64 %tmp
  
  class I:
      @llvm
      def __float__(self: int) -> float:
          %tmp = sitofp i64 %self to double
          ret double %tmp
  
      @llvm
      def __bool__(self: int) -> bool:
          %0 = icmp ne i64 %self, 0
          %1 = zext i1 %0 to i8
          ret i8 %1
  
      def __pos__(self: int) -> int:
          return self
  
      def __neg__(self: int) -> int:
          return I.__sub__(0, self)
  
      @llvm
      def __abs__(self: int) -> int:
          %0 = icmp sgt i64 %self, 0
          %1 = sub i64 0, %self
          %2 = select i1 %0, i64 %self, i64 %1
          ret i64 %2
  
      @llvm
      def __lshift__(self: int, other: int) -> int:
          %0 = shl i64 %self, %other
          ret i64 %0
  
      @llvm
      def __rshift__(self: int, other: int) -> int:
          %0 = ashr i64 %self, %other
          ret i64 %0
  
      @llvm
      def __add__(self: int, other: float) -> float:
          %0 = sitofp i64 %self to double
          %1 = fadd double %0, %other
          ret double %1
  
      @llvm
      def __add__(self: int, b: int) -> int:
          %tmp = add i64 %self, %b
          ret i64 %tmp
  
      @llvm
      def __sub__(self: int, other: float) -> float:
          %0 = sitofp i64 %self to double
          %1 = fsub double %0, %other
          ret double %1
  
      @llvm
      def __sub__(self: int, b: int) -> int:
          %tmp = sub i64 %self, %b
          ret i64 %tmp
  
      @llvm
      def __mul__(self: int, other: float) -> float:
          %0 = sitofp i64 %self to double
          %1 = fmul double %0, %other
          ret double %1
  
      @llvm
      def __mul__(self: int, b: int) -> int:
          %tmp = mul i64 %self, %b
          ret i64 %tmp
  
      @llvm
      def __floordiv__(self: int, other: float) -> float:
          declare double @llvm.floor.f64(double)
          %0 = sitofp i64 %self to double
          %1 = fdiv double %0, %other
          %2 = call double @llvm.floor.f64(double %1)
          ret double %2
  
      @llvm
      def __floordiv__(self: int, b: int) -> int:
          %tmp = sdiv i64 %self, %b
          ret i64 %tmp
  
      @llvm
      def __truediv__(self: int, other: float) -> float:
          %0 = sitofp i64 %self to double
          %1 = fdiv double %0, %other
          ret double %1
  
      @llvm
      def __truediv__(self: int, other: int) -> float:
          %0 = sitofp i64 %self to double
          %1 = sitofp i64 %other to double
          %2 = fdiv double %0, %1
          ret double %2
  
      @llvm
      def __mod__(self: int, other: float) -> float:
          %0 = sitofp i64 %self to double
          %1 = frem double %0, %other
          ret double %1
  
      @llvm
      def __mod__(a: int, b: int) -> int:
          %tmp = srem i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __invert__(a: int) -> int:
          %tmp = xor i64 %a, -1
          ret i64 %tmp
  
      @llvm
      def __and__(a: int, b: int) -> int:
          %tmp = and i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __or__(a: int, b: int) -> int:
          %tmp = or i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __xor__(a: int, b: int) -> int:
          %tmp = xor i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __shr__(a: int, b: int) -> int:
          %tmp = ashr i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __shl__(a: int, b: int) -> int:
          %tmp = shl i64 %a, %b
          ret i64 %tmp
  
      @llvm
      def __bitreverse__(a: int) -> int:
          declare i64 @llvm.bitreverse.i64(i64 %a)
          %tmp = call i64 @llvm.bitreverse.i64(i64 %a)
          ret i64 %tmp
  
      @llvm
      def __bswap__(a: int) -> int:
          declare i64 @llvm.bswap.i64(i64 %a)
          %tmp = call i64 @llvm.bswap.i64(i64 %a)
          ret i64 %tmp
  
      @llvm
      def __ctpop__(a: int) -> int:
          declare i64 @llvm.ctpop.i64(i64 %a)
          %tmp = call i64 @llvm.ctpop.i64(i64 %a)
          ret i64 %tmp
  
      @llvm
      def __ctlz__(a: int) -> int:
          declare i64 @llvm.ctlz.i64(i64 %a, i1 %is_zero_undef)
          %tmp = call i64 @llvm.ctlz.i64(i64 %a, i1 false)
          ret i64 %tmp
  
      @llvm
      def __cttz__(a: int) -> int:
          declare i64 @llvm.cttz.i64(i64 %a, i1 %is_zero_undef)
          %tmp = call i64 @llvm.cttz.i64(i64 %a, i1 false)
          ret i64 %tmp
  
      @llvm
      def __eq__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp oeq double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __eq__(a: int, b: int) -> bool:
          %tmp = icmp eq i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      @llvm
      def __ne__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp one double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __ne__(a: int, b: int) -> bool:
          %tmp = icmp ne i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      @llvm
      def __lt__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp olt double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __lt__(a: int, b: int) -> bool:
          %tmp = icmp slt i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      @llvm
      def __gt__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp ogt double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __gt__(a: int, b: int) -> bool:
          %tmp = icmp sgt i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      @llvm
      def __le__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp ole double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __le__(a: int, b: int) -> bool:
          %tmp = icmp sle i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      @llvm
      def __ge__(self: int, b: float) -> bool:
          %0 = sitofp i64 %self to double
          %1 = fcmp oge double %0, %b
          %2 = zext i1 %1 to i8
          ret i8 %2
  
      @llvm
      def __ge__(a: int, b: int) -> bool:
          %tmp = icmp sge i64 %a, %b
          %res = zext i1 %tmp to i8
          ret i8 %res
  
      def __pow__(self: int, exp: float):
          return float(self) ** exp
  
      def __pow__(self: int, exp: int):
          if exp < 0:
              return 0
          result = 1
          while True:
              if exp & 1:
                  result *= self
              exp >>= 1
              if not exp:
                  break
              self *= self
          return result
  
  @extend
  class int:
      def __int__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return self
  
      def __float__(self) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__float__(self)
  
      def __bool__(self) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__bool__(self)
  
      def __pos__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return self
  
      def __neg__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__neg__(self)
  
      def __lshift__(self, other: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__lshift__(self, other)
  
      def __rshift__(self, other: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__rshift__(self, other)
  
      def __add__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__add__(self, other)
  
      def __add__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__add__(self, b)
  
      def __sub__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__sub__(self, other)
  
      def __sub__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__sub__(self, b)
  
      def __mul__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__mul__(self, other)
  
      def __mul__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__mul__(self, b)
  
      def __floordiv__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__floordiv__(self, other)
  
      def __floordiv__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__floordiv__(self, b)
  
      def __truediv__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__truediv__(self, other)
  
      def __truediv__(self, other: int) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__truediv__(self, other)
  
      def __mod__(self, other: float) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__mod__(self, other)
  
      def __mod__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__mod__(self, b)
  
      def __invert__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__invert__(self)
  
      def __and__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__and__(self, b)
  
      def __or__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__or__(self, b)
  
      def __xor__(self, b: int) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__xor__(self, b)
  
      def __eq__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__eq__(self, b)
  
      def __eq__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__eq__(self, b)
  
      def __ne__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__ne__(self, b)
  
      def __ne__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__ne__(self, b)
  
      def __lt__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__lt__(self, b)
  
      def __lt__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__lt__(self, b)
  
      def __gt__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__gt__(self, b)
  
      def __gt__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__gt__(self, b)
  
      def __le__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__le__(self, b)
  
      def __le__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__le__(self, b)
  
      def __ge__(self, b: float) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__ge__(self, b)
  
      def __ge__(self, b: int) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__ge__(self, b)
  
      def __pow__(self, exp: float):
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__pow__(self, exp)
  
      def __pow__(self, exp: int):
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return I.__pow__(self, exp)
  
  
  class F:
      @llvm
      def __int__(self: float) -> int:
          %0 = fptosi double %self to i64
          ret i64 %0
  
      def __float__(self: float):
          return self
  
      @llvm
      def __bool__(self: float) -> bool:
          %0 = fcmp one double %self, 0.000000e+00
          %1 = zext i1 %0 to i8
          ret i8 %1
  
  @extend
  class float:
      def __int__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return F.__int__(self)
  
      def __float__(self) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return self
  
      def __bool__(self) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return F.__bool__(self)
  
  @extend
  class bool:
      def __int__(self) -> int:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return 1 if self else 0
  
      def __float__(self) -> float:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return 1. if self else 0.
  
      def __bool__(self) -> bool:
          global OP_COUNT
          OP_COUNT = inc(OP_COUNT)
          return self
  
  def eq(a: int, b: int) -> bool:
      return I.__eq__(a, b)
  
  @noinline
  def foo(x):
    return x
  
  @test
  def test_int_simple_fold():
      op_count = OP_COUNT
      x = 1
      y = x + 2
      z = x + 3
      assert eq(foo(x + 1), 2)
      assert eq(foo(y * 2), 6)
      assert eq(foo(z // 3), 1)
      assert eq(foo(x >> 2), 0)
      assert eq(foo(x << y), 8)
      assert eq(foo(x | y), 3)
      assert eq(foo(z & z), 4)
      assert not foo(x > y)
      assert foo(y < z)
      assert not foo(x >= z)
      assert foo(x <= 2)
      assert foo(x == 1)
      assert foo(x != 2)
      assert eq(foo(y ** 3), 27)
      assert eq(foo(y ** 0), 1)
      assert eq(foo(y ** -1), 0)
      assert eq(op_count, OP_COUNT)
  test_int_simple_fold()
  
  @test
  def test_ternary_fold():
      op_count = OP_COUNT
      x = 1
      y = x * 2
      assert (x + 1 if x != 0 else -1) > 0
      assert (x + 1 if x == 0 else -1) < 0
      assert eq(op_count, OP_COUNT)
  test_ternary_fold()
  
  @test
  def test_try_catch_fold():
      op_count = OP_COUNT
      x = 0
      y = x + 1
      try:
          x = 1
      finally:
          x = 4
      assert x == 4
      assert eq(op_count, OP_COUNT)
  test_try_catch_fold()
  
  @test
  def test_while_fold():
      op_count = OP_COUNT
      x = 0
      y = 1
      while (x != 0):
          y = 2
      assert y + 1 == 2
      assert eq(op_count, OP_COUNT)
  test_while_fold()
  
  @test
  def test_imperative_for_fold():
      op_count = OP_COUNT
      foo = 2
      y = 1
      for i in range(foo, 2):
          y = foo - 1
      assert y == 1
      assert eq(op_count, OP_COUNT)
  test_imperative_for_fold()
  
  @test
  def test_long_fold():
      op_count = OP_COUNT
      x = 3
      assert eq(foo(x + x + x + x + x + x + x + x + x + x + x + x), 36)
      assert eq(op_count, OP_COUNT)
  test_long_fold()
  
  @test
  def test_conversions():
      op_count = OP_COUNT
      n = 42
      b = True
      x = 3.14
  
      assert eq(foo(n.__int__()), n)
      assert foo(n.__float__()) == 42.0
      assert foo(n.__bool__())
  
      assert eq(foo(b.__int__()), 1)
      assert foo(b.__float__()) == 1.0
      assert foo(b.__bool__())
  
      assert eq(foo(x.__int__()), 3)
      assert foo(x.__float__()) == x
      assert foo(x.__bool__())
      assert eq(op_count, OP_COUNT)
  test_conversions()
  
  @test
  def test_no_ops():
      def v(): return 42
      op_count = OP_COUNT
      n = v()
      assert eq(+n, n)
      assert eq(-(-n), n)
      assert eq(~(~n), n)
      assert eq(op_count, OP_COUNT)
  test_no_ops()
  
  @test
  def test_algebraic_simplification():
      def v(): return 42
      op_count = OP_COUNT
      n = v()
      assert eq(0*n, 0)
      assert eq(n*0, 0)
      assert eq(n*1, n)
      assert eq(1*n, n)
      assert eq(n+0, n)
      assert eq(0+n, n)
      assert eq(n-0, n)
      assert eq(0//n, 0)
      assert eq(op_count, OP_COUNT)
  test_algebraic_simplification()
  
  n = 42
  @test
  def test_global_fold():
      op_count = OP_COUNT
      m = 1
      assert eq(n + m, 43)
      assert eq(op_count, OP_COUNT)
  test_global_fold()
  
  # make sure globals fold properly with demotion
  op_count1 = OP_COUNT
  a = foo(42)
  a = 42
  b = a + 10
  c = b
  op_count2 = OP_COUNT
  
  @test
  def test_global_fold_non_const():
      op_count = OP_COUNT
      assert c == 52
      assert op_count1 == op_count2
  test_global_fold_non_const()
  
  some_global = 42
  @test
  def test_side_effect_analysis():
      @pure
      @test
      def foo():
          assert False
          return some_global
  
      def bar():
          a = 1
          b = 2
          c = 3
          return foo()
  
      def baz():
          global some_global
          some_global += 1
          return some_global
  
      def fab():
          return baz()
  
      foo()
      x = foo()
      bar()
      y = bar()
      assert baz() == 43
      baz()
      assert some_global == 44
      assert fab() == 45
      fab()
      assert some_global == 46
  test_side_effect_analysis()
  