gollvm: complex number support

Add support of complex expressions:
- real, imag, complex builtins
- add, sub, mult (division is lowered to runtime calls
  by the front end)
- comparisons (==, !=)
- conversions (complex64 <-> complex128)
- creating complex constants

Change-Id: I0d935f088ccac7af1b7028d4e88116c7b77231ef
Reviewed-on: https://go-review.googlesource.com/45530
Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/llvm-gofrontend/go-llvm-bnode.cpp b/llvm-gofrontend/go-llvm-bnode.cpp
index e950bc8..7636acf 100644
--- a/llvm-gofrontend/go-llvm-bnode.cpp
+++ b/llvm-gofrontend/go-llvm-bnode.cpp
@@ -432,6 +432,7 @@
     inst->setName(name);
   Bvariable *tvar = new Bvariable(varType, loc, name, LocalVar, true, inst);
   tempvars_[inst] = tvar;
+  tvar->markAsTemporary();
   return tvar;
 }
 
diff --git a/llvm-gofrontend/go-llvm-btype.cpp b/llvm-gofrontend/go-llvm-btype.cpp
index 3a6311d..c5f434d 100644
--- a/llvm-gofrontend/go-llvm-btype.cpp
+++ b/llvm-gofrontend/go-llvm-btype.cpp
@@ -68,7 +68,7 @@
   if (isPlaceholder() != other.isPlaceholder())
     return false;
   bool compareLlvmType = true;
-  if ((ctl & IgnoreNames) != 0 && type()->isStructTy())
+  if ((ctl & IgnoreNames) != 0 && type()->isStructTy() && flavor_ != ComplexT)
     compareLlvmType = false;
   if (compareLlvmType && type() != other.type())
     return false;
diff --git a/llvm-gofrontend/go-llvm.cpp b/llvm-gofrontend/go-llvm.cpp
index 2cb38b5..d1c22fa 100644
--- a/llvm-gofrontend/go-llvm.cpp
+++ b/llvm-gofrontend/go-llvm.cpp
@@ -882,7 +882,6 @@
     std::string tname(namegen("tmp"));
     tvar = nbuilder_.mkTempVar(expr->btype(), expr->location(), tname);
     assert(tvar != errorVariable_.get());
-    tvar->markAsTemporary();
     storage = tvar->value();
     setPending = true;
   }
@@ -1030,8 +1029,53 @@
 
 Bexpression *Llvm_backend::complex_constant_expression(Btype *btype,
                                                        mpc_t val) {
-  assert(false && "Llvm_backend::complex_constant_expression not yet impl");
-  return nullptr;
+  if (btype == errorType())
+    return errorExpression();
+
+  BComplexType *bct = btype->castToBComplexType();
+  assert(bct);
+  llvm::Type *llt = btype->type();
+  assert(llt->isStructTy());
+  llvm::StructType *llst = llvm::cast<llvm::StructType>(llt);
+  assert(llst->getNumElements() == 2);
+  llvm::Type *llet = llst->getElementType(0);
+  assert(llet == llst->getElementType(1));
+
+  std::vector<Bexpression *> exps;
+
+  if (llet == llvmFloatType()) {
+    float frval = mpfr_get_flt(mpc_realref(val), GMP_RNDN);
+    float fival = mpfr_get_flt(mpc_imagref(val), GMP_RNDN);
+    llvm::APFloat apr(frval);
+    llvm::APFloat api(fival);
+    llvm::Constant *rcon = llvm::ConstantFP::get(context_, apr);
+    llvm::Constant *icon = llvm::ConstantFP::get(context_, api);
+
+    Btype *bet = floatType(32);
+    Bexpression *brcon = nbuilder_.mkConst(bet, rcon);
+    Bexpression *bicon = nbuilder_.mkConst(bet, icon);
+    exps.push_back(brcon);
+    exps.push_back(bicon);
+  } else if (llet == llvmDoubleType()) {
+    double drval = mpfr_get_d(mpc_realref(val), GMP_RNDN);
+    double dival = mpfr_get_d(mpc_imagref(val), GMP_RNDN);
+    llvm::APFloat apr(drval);
+    llvm::APFloat api(dival);
+    llvm::Constant *rcon = llvm::ConstantFP::get(context_, apr);
+    llvm::Constant *icon = llvm::ConstantFP::get(context_, api);
+
+    Btype *bet = floatType(64);
+    Bexpression *brcon = nbuilder_.mkConst(bet, rcon);
+    Bexpression *bicon = nbuilder_.mkConst(bet, icon);
+    exps.push_back(brcon);
+    exps.push_back(bicon);
+  } else {
+    assert(false && "unknown complex type");
+    return nullptr;
+  }
+
+  std::vector<unsigned long> indexes = {0, 1};
+  return makeConstCompositeExpr(btype, llst, 2, indexes, exps, Location());
 }
 
 // Make a constant string expression.
@@ -1090,16 +1134,72 @@
 
 Bexpression *Llvm_backend::real_part_expression(Bexpression *bcomplex,
                                                 Location location) {
-  assert(false && "Llvm_backend::real_part_expression not yet impl");
-  return nullptr;
+  if (bcomplex == errorExpression())
+    return errorExpression();
+
+  if (bcomplex->compositeInitPending())
+    bcomplex = resolveCompositeInit(bcomplex, nullptr);
+
+  // Construct an appropriate GEP
+  llvm::Type *llt = bcomplex->btype()->type();
+  assert(llt->isStructTy());
+  llvm::StructType *llst = llvm::cast<llvm::StructType>(llt);
+  llvm::Value *cval = bcomplex->value();
+  llvm::Value *fval;
+  if (llvm::isa<llvm::Constant>(cval) && cval->getType()->isStructTy())
+    fval = llvm::cast<llvm::Constant>(cval)->getAggregateElement((unsigned int)0);
+  else
+    fval = makeFieldGEP(llst, 0, cval);
+  BComplexType *bct = bcomplex->btype()->castToBComplexType();
+  assert(bct);
+  Btype *bft = floatType(bct->bits()/2);
+
+  // Wrap result in a Bexpression
+  Bexpression *rval = nbuilder_.mkStructField(bft, fval, bcomplex,
+                                              0, location);
+
+  std::string tag(bcomplex->tag());
+  tag += ".real";
+  rval->setTag(tag);
+
+  // We're done
+  return rval;
 }
 
 // Return the imaginary part of a complex expression.
 
 Bexpression *Llvm_backend::imag_part_expression(Bexpression *bcomplex,
                                                 Location location) {
-  assert(false && "Llvm_backend::imag_part_expression not yet impl");
-  return nullptr;
+  if (bcomplex == errorExpression())
+    return errorExpression();
+
+  if (bcomplex->compositeInitPending())
+    bcomplex = resolveCompositeInit(bcomplex, nullptr);
+
+  // Construct an appropriate GEP
+  llvm::Type *llt = bcomplex->btype()->type();
+  assert(llt->isStructTy());
+  llvm::StructType *llst = llvm::cast<llvm::StructType>(llt);
+  llvm::Value *cval = bcomplex->value();
+  llvm::Value *fval;
+  if (llvm::isa<llvm::Constant>(cval) && cval->getType()->isStructTy())
+    fval = llvm::cast<llvm::Constant>(cval)->getAggregateElement((unsigned int)1);
+  else
+    fval = makeFieldGEP(llst, 1, cval);
+  BComplexType *bct = bcomplex->btype()->castToBComplexType();
+  assert(bct);
+  Btype *bft = floatType(bct->bits()/2);
+
+  // Wrap result in a Bexpression
+  Bexpression *rval = nbuilder_.mkStructField(bft, fval, bcomplex,
+                                              1, location);
+
+  std::string tag(bcomplex->tag());
+  tag += ".imag";
+  rval->setTag(tag);
+
+  // We're done
+  return rval;
 }
 
 // Make a complex expression given its real and imaginary parts.
@@ -1107,8 +1207,26 @@
 Bexpression *Llvm_backend::complex_expression(Bexpression *breal,
                                               Bexpression *bimag,
                                               Location location) {
-  assert(false && "Llvm_backend::complex_expression not yet impl");
-  return nullptr;
+  if (breal == errorExpression() || bimag == errorExpression())
+    return errorExpression();
+
+  assert(breal->btype() == bimag->btype());
+  BFloatType *bet = breal->btype()->castToBFloatType();
+  assert(bet);
+  Btype *bct = complexType(bet->bits()*2);
+  llvm::Type *llt = bct->type();
+  assert(llt->isStructTy());
+  llvm::StructType *llst = llvm::cast<llvm::StructType>(llt);
+
+  std::vector<Bexpression *> vals = {breal, bimag};
+  std::vector<unsigned long> indexes = {0, 1};
+
+  // Constant values?
+  bool isConstant = valuesAreConstant(vals);
+  if (isConstant)
+    return makeConstCompositeExpr(bct, llst, 2, indexes, vals, location);
+  else
+    return makeDelayedCompositeExpr(bct, llst, 2, indexes, vals, location);
 }
 
 // An expression that converts an expression to a different type.
@@ -1128,6 +1246,10 @@
     toType = type->type();
   }
 
+  // Complex -> complex conversion
+  if (type->castToBComplexType())
+    return makeComplexConvertExpr(type, expr, location);
+
   // For composite-init-pending values, materialize a variable now.
   if (expr->compositeInitPending()) {
     assert(!expr->varExprPending());
@@ -1264,6 +1386,48 @@
   return expr;
 }
 
+Bexpression *Llvm_backend::makeComplexConvertExpr(Btype *type,
+                                                  Bexpression *expr,
+                                                  Location location) {
+  if (expr->btype()->type() == type->type())
+    return expr;
+
+  BComplexType *bct = type->castToBComplexType();
+  assert(bct);
+  assert(expr->btype()->castToBComplexType());
+  Btype *bft = floatType(bct->bits()/2);
+
+  // We need to avoid sharing between real part and imag part of the operand.
+  // Create temp variables and assign operand to the temp variable first.
+  // TODO: maybe not make temp var if the operand is already a var or constant?
+  std::string tname1(namegen("tmp"));
+  Bvariable *var = nbuilder_.mkTempVar(expr->btype(), location, tname1);
+  Bfunction *dummyFcn = errorFunction_.get();
+  Bstatement *einit = makeInitStatement(dummyFcn, var, expr);
+
+  Bexpression *vex = nbuilder_.mkVar(var, location);
+  vex->setVarExprPending(false, 0);
+  Bexpression *vexr = real_part_expression(vex, location);
+  Bexpression *vexi = imag_part_expression(vex, location);
+
+  // Make the conversion
+  Bexpression *valr = convert_expression(bft, vexr, location);
+  Bexpression *vali = convert_expression(bft, vexi, location);
+  Bexpression *val = complex_expression(valr, vali, location);
+
+  // Wrap result and the init statements in a compound expression.
+  // Currently we can't resolve composite storage for compound
+  // expression, so we resolve the inner complex expression
+  // here with another temp variable.
+  std::string tname2(namegen("tmp"));
+  Bvariable *rvar = nbuilder_.mkTempVar(val->btype(), location, tname2);
+  Bstatement *rinit = makeInitStatement(dummyFcn, rvar, val);
+  Bexpression *rvex = nbuilder_.mkVar(rvar, location);
+  Bstatement *init = statement_list(std::vector<Bstatement*>{einit, rinit});
+  return compound_expression(init, rvex, location);
+}
+
+
 // Get the address of a function.
 
 Bexpression *Llvm_backend::function_code_expression(Bfunction *bfunc,
@@ -1309,6 +1473,7 @@
                                         unsigned fieldIndex,
                                         llvm::Value *sptr)
 {
+  assert(sptr->getType()->isPointerTy());
   LIRBuilder builder(context_, llvm::ConstantFolder());
   assert(fieldIndex < llst->getNumElements());
   std::string tag(namegen("field"));
@@ -1591,12 +1756,18 @@
   if (left == errorExpression() || right == errorExpression())
     return errorExpression();
 
+  Btype *bltype = left->btype();
+  Btype *brtype = right->btype();
+  BComplexType *blctype = bltype->castToBComplexType();
+  BComplexType *brctype = brtype->castToBComplexType();
+  assert((blctype == nullptr) == (brctype == nullptr));
+  if (blctype)
+    return makeComplexBinaryExpr(op, left, right, location);
+
   left = resolveVarContext(left);
   right = resolveVarContext(right);
   assert(left->value() && right->value());
 
-  Btype *bltype = left->btype();
-  Btype *brtype = right->btype();
   std::pair<llvm::Value *, llvm::Value *> converted =
       convertForBinary(left, right);
   llvm::Value *leftVal = converted.first;
@@ -1733,6 +1904,76 @@
   return nbuilder_.mkBinaryOp(op, bltype, val, left, right, location);
 }
 
+Bexpression *Llvm_backend::makeComplexBinaryExpr(Operator op, Bexpression *left,
+                                                 Bexpression *right,
+                                                 Location location) {
+  // We need to avoid sharing between real part and imag part of the operand.
+  // Create temp variables and assign operands to the temp vars first.
+  // TODO: maybe not make temp var if the operand is already a var or constant?
+  std::string tname1(namegen("tmp")), tname2(namegen("tmp"));
+  Bvariable *lvar = nbuilder_.mkTempVar(left->btype(), location, tname1);
+  Bvariable *rvar = nbuilder_.mkTempVar(right->btype(), location, tname2);
+  Bfunction *dummyFcn = errorFunction_.get();
+  Bstatement *linit = makeInitStatement(dummyFcn, lvar, left);
+  Bstatement *rinit = makeInitStatement(dummyFcn, rvar, right);
+
+  Bexpression *lvex = nbuilder_.mkVar(lvar, location);
+  lvex->setVarExprPending(false, 0);
+  Bexpression *rvex = nbuilder_.mkVar(rvar, location);
+  rvex->setVarExprPending(false, 0);
+  Bexpression *lr = real_part_expression(lvex, location);
+  Bexpression *li = imag_part_expression(lvex, location);
+  Bexpression *rr = real_part_expression(rvex, location);
+  Bexpression *ri = imag_part_expression(rvex, location);
+  Bexpression *val;
+
+  switch (op) {
+  case OPERATOR_PLUS:
+  case OPERATOR_MINUS: {
+    Bexpression *valr = binary_expression(op, lr, rr, location);
+    Bexpression *vali = binary_expression(op, li, ri, location);
+    val = complex_expression(valr, vali, location);
+    break;
+  }
+  case OPERATOR_MULT: {
+    // (a+bi)*(c+di) = (ac-bd) + (ad+bc)i
+    Bexpression *ac = binary_expression(OPERATOR_MULT, lr, rr, location);
+    Bexpression *bd = binary_expression(OPERATOR_MULT, li, ri, location);
+    Bexpression *ad = binary_expression(OPERATOR_MULT, lr, ri, location);
+    Bexpression *bc = binary_expression(OPERATOR_MULT, li, rr, location);
+    Bexpression *valr = binary_expression(OPERATOR_MINUS, ac, bd, location);
+    Bexpression *vali = binary_expression(OPERATOR_PLUS, ad, bc, location);
+    val = complex_expression(valr, vali, location);
+    break;
+  }
+  case OPERATOR_EQEQ:
+  case OPERATOR_NOTEQ: {
+    Bexpression *cmpr = binary_expression(op, lr, rr, location);
+    Bexpression *cmpi = binary_expression(op, li, ri, location);
+    if (op == OPERATOR_EQEQ)
+      val = binary_expression(OPERATOR_ANDAND, cmpr, cmpi, location);
+    else
+      val = binary_expression(OPERATOR_OROR, cmpr, cmpi, location);
+    Bstatement *init = statement_list(std::vector<Bstatement*>{linit, rinit});
+    return compound_expression(init, val, location);
+  }
+  default:
+    std::cerr << "Op " << op << " unhandled\n";
+    assert(false);
+  }
+
+  // Wrap result and the init statements in a compound expression.
+  // Currently we can't resolve composite storage for compound
+  // expression, so we resolve the inner complex expression
+  // here with another temp variable.
+  std::string tname3(namegen("tmp"));
+  Bvariable *vvar = nbuilder_.mkTempVar(val->btype(), location, tname3);
+  Bstatement *vinit = makeInitStatement(dummyFcn, vvar, val);
+  Bexpression *vvex = nbuilder_.mkVar(vvar, location);
+  Bstatement *init = statement_list(std::vector<Bstatement*>{linit, rinit, vinit});
+  return compound_expression(init, vvex, location);
+}
+
 bool
 Llvm_backend::valuesAreConstant(const std::vector<Bexpression *> &vals)
 {
@@ -2291,6 +2532,14 @@
   if (var == errorVariable_.get() || init == errorExpression() ||
       bfunction == errorFunction_.get())
     return errorStatement();
+
+  return makeInitStatement(bfunction, var, init);
+}
+
+Bstatement *Llvm_backend::makeInitStatement(Bfunction *bfunction,
+                                          Bvariable *var,
+                                          Bexpression *init)
+{
   if (init) {
     if (init->compositeInitPending()) {
       init = resolveCompositeInit(init, var->value());
diff --git a/llvm-gofrontend/go-llvm.h b/llvm-gofrontend/go-llvm.h
index ceacc92..59747da 100644
--- a/llvm-gofrontend/go-llvm.h
+++ b/llvm-gofrontend/go-llvm.h
@@ -448,6 +448,16 @@
                                         const std::vector<Bexpression *> &vals,
                                         Location location);
 
+  // Helper for creating a complex binary expression.
+  Bexpression *makeComplexBinaryExpr(Operator op,
+                                     Bexpression *left,
+                                     Bexpression *right,
+                                     Location location);
+
+  // Helper for creating a complex conversion expression.
+  Bexpression *makeComplexConvertExpr(Btype *type, Bexpression *expr,
+                                      Location location);
+
   // Field GEP helper
   llvm::Value *makeFieldGEP(llvm::StructType *llst,
                             unsigned fieldIndex,
@@ -467,6 +477,10 @@
   Bstatement *makeAssignment(Bfunction *function, llvm::Value *lvalue,
                              Bexpression *lhs, Bexpression *rhs, Location);
 
+  // Helper to make init statement
+  Bstatement *makeInitStatement(Bfunction *bfunction, Bvariable *var,
+                              Bexpression *init);
+
   // Helper to set up entry block for function
   llvm::BasicBlock *genEntryBlock(Bfunction *bfunction);
 
diff --git a/unittests/BackendCore/BackendExprTests.cpp b/unittests/BackendCore/BackendExprTests.cpp
index b75f8ee..0b045d9 100644
--- a/unittests/BackendCore/BackendExprTests.cpp
+++ b/unittests/BackendCore/BackendExprTests.cpp
@@ -119,6 +119,44 @@
   }
 }
 
+TEST(BackendExprTests, MakeComplexConstExpr) {
+  llvm::LLVMContext C;
+
+  std::unique_ptr<Backend> be(go_get_backend(C));
+
+  // Complex constants
+  Btype *bc64t = be->complex_type(64);
+  Btype *bc128t = be->complex_type(128);
+  Btype *bf32t = be->float_type(32);
+  Btype *bf64t = be->float_type(64);
+  static const double f64vals[] = {1.7976931348623158e+308, 0.0, 1.1,
+                                   2.2250738585072014e-308};
+  for (auto valr : f64vals)
+    for (auto vali : f64vals) {
+      mpc_t mpc_val;
+      mpc_init2(mpc_val, 256);
+      mpc_set_d_d(mpc_val, valr, vali, GMP_RNDN);
+
+      Bexpression *bc64val = be->complex_constant_expression(bc64t, mpc_val);
+      ASSERT_TRUE(bc64val != nullptr);
+      llvm::StructType *llc64st = llvm::cast<llvm::StructType>(bc64t->type());
+      llvm::SmallVector<llvm::Constant *, 2> llf32vals(2);
+      llf32vals[0] = llvm::ConstantFP::get(bf32t->type(), valr);
+      llf32vals[1] = llvm::ConstantFP::get(bf32t->type(), vali);
+      EXPECT_EQ(bc64val->value(), llvm::ConstantStruct::get(llc64st, llf32vals));
+
+      Bexpression *bc128val = be->complex_constant_expression(bc128t, mpc_val);
+      ASSERT_TRUE(bc128val != nullptr);
+      llvm::StructType *llc128st = llvm::cast<llvm::StructType>(bc128t->type());
+      llvm::SmallVector<llvm::Constant *, 2> llf64vals(2);
+      llf64vals[0] = llvm::ConstantFP::get(bf64t->type(), valr);
+      llf64vals[1] = llvm::ConstantFP::get(bf64t->type(), vali);
+      EXPECT_EQ(bc128val->value(), llvm::ConstantStruct::get(llc128st, llf64vals));
+
+      mpc_clear(mpc_val);
+    }
+}
+
 TEST(BackendExprTests, MakeZeroValueExpr) {
   llvm::LLVMContext C;
 
@@ -139,6 +177,10 @@
   Btype *s2t = mkBackendStruct(be, pbt, "f1", bi32t, "f2", nullptr);
   Bexpression *bszero = be->zero_expression(s2t);
   ASSERT_TRUE(bszero != nullptr);
+  Btype *bct = be->complex_type(128);
+  Bexpression *bczero = be->zero_expression(bct);
+  ASSERT_TRUE(bczero != nullptr);
+  EXPECT_EQ(repr(bczero->value()), "{ double, double } zeroinitializer");
 
   // Error handling
   EXPECT_EQ(be->zero_expression(be->error_type()), be->error_expression());
@@ -360,6 +402,120 @@
   EXPECT_FALSE(broken && "Module failed to verify.");
 }
 
+TEST(BackendExprTests, TestComplexConversionExpression) {
+  FcnTestHarness h("foo");
+  Llvm_backend *be = h.be();
+  BFunctionType *befty = mkFuncTyp(be, L_END);
+  Bfunction *func = h.mkFunction("foo", befty);
+  Location loc;
+
+  Btype *bc64t = be->complex_type(64);
+  Btype *bc128t = be->complex_type(128);
+
+  // var a, b complex64
+  // var x, y complex128
+  Bvariable *a = h.mkLocal("a", bc64t);
+  Bvariable *b = h.mkLocal("b", bc64t);
+  Bvariable *x = h.mkLocal("x", bc128t);
+  Bvariable *y = h.mkLocal("y", bc128t);
+
+  // a = complex64(x)
+  Bexpression *avex1 = be->var_expression(a, VE_lvalue, loc);
+  Bexpression *xvex1 = be->var_expression(x, VE_rvalue, loc);
+  Bexpression *convex1 = be->convert_expression(bc64t, xvex1, loc);
+  h.mkAssign(avex1, convex1);
+
+  // y = complex128(b)
+  Bexpression *yvex2 = be->var_expression(y, VE_lvalue, loc);
+  Bexpression *bvex2 = be->var_expression(b, VE_rvalue, loc);
+  Bexpression *convex2 = be->convert_expression(bc128t, bvex2, loc);
+  h.mkAssign(yvex2, convex2);
+
+  // No-op conversions
+  // a = complex64(b)
+  Bexpression *avex3 = be->var_expression(a, VE_lvalue, loc);
+  Bexpression *bvex3 = be->var_expression(b, VE_rvalue, loc);
+  Bexpression *convex3 = be->convert_expression(bc64t, bvex3, loc);
+  h.mkAssign(avex3, convex3);
+
+  // x = complex128(y)
+  Bexpression *xvex4 = be->var_expression(x, VE_lvalue, loc);
+  Bexpression *yvex4 = be->var_expression(y, VE_rvalue, loc);
+  Bexpression *convex4 = be->convert_expression(bc128t, yvex4, loc);
+  h.mkAssign(xvex4, convex4);
+
+  const char *exp = R"RAW_RESULT(
+  define void @foo.1(i8* nest %nest.1) #0 {
+  entry:
+    %tmp.3 = alloca { double, double }
+    %tmp.2 = alloca { float, float }
+    %tmp.1 = alloca { float, float }
+    %tmp.0 = alloca { double, double }
+    %a = alloca { float, float }
+    %b = alloca { float, float }
+    %x = alloca { double, double }
+    %y = alloca { double, double }
+    %cast.0 = bitcast { float, float }* %a to i8*
+    %cast.1 = bitcast { float, float }* @const.0 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.0, i8* %cast.1, i64 8, i32 8, i1 false)
+    %cast.2 = bitcast { float, float }* %b to i8*
+    %cast.3 = bitcast { float, float }* @const.1 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.2, i8* %cast.3, i64 8, i32 8, i1 false)
+    %cast.4 = bitcast { double, double }* %x to i8*
+    %cast.5 = bitcast { double, double }* @const.2 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.4, i8* %cast.5, i64 16, i32 8, i1 false)
+    %cast.6 = bitcast { double, double }* %y to i8*
+    %cast.7 = bitcast { double, double }* @const.3 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.6, i8* %cast.7, i64 16, i32 8, i1 false)
+    %cast.8 = bitcast { double, double }* %tmp.0 to i8*
+    %cast.9 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.8, i8* %cast.9, i64 16, i32 8, i1 false)
+    %field.0 = getelementptr inbounds { double, double }, { double, double }* %tmp.0, i32 0, i32 0
+    %.real.ld.0 = load double, double* %field.0
+    %fptrunc.0 = fptrunc double %.real.ld.0 to float
+    %field.1 = getelementptr inbounds { double, double }, { double, double }* %tmp.0, i32 0, i32 1
+    %.imag.ld.0 = load double, double* %field.1
+    %fptrunc.1 = fptrunc double %.imag.ld.0 to float
+    %field.2 = getelementptr inbounds { float, float }, { float, float }* %tmp.1, i32 0, i32 0
+    store float %fptrunc.0, float* %field.2
+    %field.3 = getelementptr inbounds { float, float }, { float, float }* %tmp.1, i32 0, i32 1
+    store float %fptrunc.1, float* %field.3
+    %cast.10 = bitcast { float, float }* %a to i8*
+    %cast.11 = bitcast { float, float }* %tmp.1 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.10, i8* %cast.11, i64 8, i32 8, i1 false)
+    %cast.12 = bitcast { float, float }* %tmp.2 to i8*
+    %cast.13 = bitcast { float, float }* %b to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.12, i8* %cast.13, i64 8, i32 8, i1 false)
+    %field.4 = getelementptr inbounds { float, float }, { float, float }* %tmp.2, i32 0, i32 0
+    %.real.ld.1 = load float, float* %field.4
+    %fpext.0 = fpext float %.real.ld.1 to double
+    %field.5 = getelementptr inbounds { float, float }, { float, float }* %tmp.2, i32 0, i32 1
+    %.imag.ld.1 = load float, float* %field.5
+    %fpext.1 = fpext float %.imag.ld.1 to double
+    %field.6 = getelementptr inbounds { double, double }, { double, double }* %tmp.3, i32 0, i32 0
+    store double %fpext.0, double* %field.6
+    %field.7 = getelementptr inbounds { double, double }, { double, double }* %tmp.3, i32 0, i32 1
+    store double %fpext.1, double* %field.7
+    %cast.14 = bitcast { double, double }* %y to i8*
+    %cast.15 = bitcast { double, double }* %tmp.3 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.14, i8* %cast.15, i64 16, i32 8, i1 false)
+    %cast.16 = bitcast { float, float }* %a to i8*
+    %cast.17 = bitcast { float, float }* %b to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.16, i8* %cast.17, i64 8, i32 8, i1 false)
+    %cast.18 = bitcast { double, double }* %x to i8*
+    %cast.19 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.18, i8* %cast.19, i64 16, i32 8, i1 false)
+    ret void
+  }
+  )RAW_RESULT";
+
+  bool broken = h.finish(StripDebugInfo);
+  EXPECT_FALSE(broken && "Module failed to verify.");
+
+  bool isOK = h.expectValue(func->function(), exp);
+  EXPECT_TRUE(isOK && "Function does not have expected contents");
+}
+
 TEST(BackendExprTests, MakeVarExpressions) {
   FcnTestHarness h("foo");
   Llvm_backend *be = h.be();
@@ -805,6 +961,255 @@
   EXPECT_FALSE(broken && "Module failed to verify.");
 }
 
+TEST(BackendExprTests, TestComplexOps) {
+  FcnTestHarness h;
+  Llvm_backend *be = h.be();
+  BFunctionType *befty = mkFuncTyp(be, L_END);
+  Bfunction *func = h.mkFunction("foo", befty);
+  Location loc;
+
+  Operator optotest[] = {OPERATOR_PLUS, OPERATOR_MINUS, OPERATOR_MULT,
+                         OPERATOR_EQEQ, OPERATOR_NOTEQ};
+
+  // var x, y, z complex128
+  // var b bool
+  Btype *bc128t = be->complex_type(128);
+  Btype *bt = be->bool_type();
+  Bvariable *x = h.mkLocal("x", bc128t);
+  Bvariable *y = h.mkLocal("y", bc128t);
+  Bvariable *z = h.mkLocal("z", bc128t);
+  Bvariable *b = h.mkLocal("b", bt);
+
+  for (auto op : optotest) {
+    Bexpression *bleft = be->var_expression(x, VE_rvalue, loc);
+    Bexpression *bright = be->var_expression(y, VE_rvalue, loc);
+    Bexpression *bop = be->binary_expression(op, bleft, bright, Location());
+    Bexpression *bvex = be->var_expression(op == OPERATOR_EQEQ || op == OPERATOR_NOTEQ ? b : z,
+                                           VE_lvalue, loc);
+    h.mkAssign(bvex, bop);
+  }
+
+  const char *exp = R"RAW_RESULT(
+  define void @foo(i8* nest %nest.0) #0 {
+  entry:
+    %tmp.12 = alloca { double, double }
+    %tmp.11 = alloca { double, double }
+    %tmp.10 = alloca { double, double }
+    %tmp.9 = alloca { double, double }
+    %tmp.8 = alloca { double, double }
+    %tmp.7 = alloca { double, double }
+    %tmp.6 = alloca { double, double }
+    %tmp.4 = alloca { double, double }
+    %tmp.3 = alloca { double, double }
+    %tmp.2 = alloca { double, double }
+    %tmp.5 = alloca { double, double }
+    %tmp.1 = alloca { double, double }
+    %tmp.0 = alloca { double, double }
+    %x = alloca { double, double }
+    %y = alloca { double, double }
+    %z = alloca { double, double }
+    %b = alloca i8
+    %cast.0 = bitcast { double, double }* %x to i8*
+    %cast.1 = bitcast { double, double }* @const.0 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.0, i8* %cast.1, i64 16, i32 8, i1 false)
+    %cast.2 = bitcast { double, double }* %y to i8*
+    %cast.3 = bitcast { double, double }* @const.1 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.2, i8* %cast.3, i64 16, i32 8, i1 false)
+    %cast.4 = bitcast { double, double }* %z to i8*
+    %cast.5 = bitcast { double, double }* @const.2 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.4, i8* %cast.5, i64 16, i32 8, i1 false)
+    store i8 0, i8* %b
+    %cast.6 = bitcast { double, double }* %tmp.0 to i8*
+    %cast.7 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.6, i8* %cast.7, i64 16, i32 8, i1 false)
+    %cast.8 = bitcast { double, double }* %tmp.1 to i8*
+    %cast.9 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.8, i8* %cast.9, i64 16, i32 8, i1 false)
+    %field.0 = getelementptr inbounds { double, double }, { double, double }* %tmp.0, i32 0, i32 0
+    %.real.ld.0 = load double, double* %field.0
+    %field.2 = getelementptr inbounds { double, double }, { double, double }* %tmp.1, i32 0, i32 0
+    %.real.ld.1 = load double, double* %field.2
+    %fadd.0 = fadd double %.real.ld.0, %.real.ld.1
+    %field.1 = getelementptr inbounds { double, double }, { double, double }* %tmp.0, i32 0, i32 1
+    %.imag.ld.0 = load double, double* %field.1
+    %field.3 = getelementptr inbounds { double, double }, { double, double }* %tmp.1, i32 0, i32 1
+    %.imag.ld.1 = load double, double* %field.3
+    %fadd.1 = fadd double %.imag.ld.0, %.imag.ld.1
+    %field.4 = getelementptr inbounds { double, double }, { double, double }* %tmp.2, i32 0, i32 0
+    store double %fadd.0, double* %field.4
+    %field.5 = getelementptr inbounds { double, double }, { double, double }* %tmp.2, i32 0, i32 1
+    store double %fadd.1, double* %field.5
+    %cast.10 = bitcast { double, double }* %z to i8*
+    %cast.11 = bitcast { double, double }* %tmp.2 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.10, i8* %cast.11, i64 16, i32 8, i1 false)
+    %cast.12 = bitcast { double, double }* %tmp.3 to i8*
+    %cast.13 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.12, i8* %cast.13, i64 16, i32 8, i1 false)
+    %cast.14 = bitcast { double, double }* %tmp.4 to i8*
+    %cast.15 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.14, i8* %cast.15, i64 16, i32 8, i1 false)
+    %field.6 = getelementptr inbounds { double, double }, { double, double }* %tmp.3, i32 0, i32 0
+    %.real.ld.2 = load double, double* %field.6
+    %field.8 = getelementptr inbounds { double, double }, { double, double }* %tmp.4, i32 0, i32 0
+    %.real.ld.3 = load double, double* %field.8
+    %fsub.0 = fsub double %.real.ld.2, %.real.ld.3
+    %field.7 = getelementptr inbounds { double, double }, { double, double }* %tmp.3, i32 0, i32 1
+    %.imag.ld.2 = load double, double* %field.7
+    %field.9 = getelementptr inbounds { double, double }, { double, double }* %tmp.4, i32 0, i32 1
+    %.imag.ld.3 = load double, double* %field.9
+    %fsub.1 = fsub double %.imag.ld.2, %.imag.ld.3
+    %field.10 = getelementptr inbounds { double, double }, { double, double }* %tmp.5, i32 0, i32 0
+    store double %fsub.0, double* %field.10
+    %field.11 = getelementptr inbounds { double, double }, { double, double }* %tmp.5, i32 0, i32 1
+    store double %fsub.1, double* %field.11
+    %cast.16 = bitcast { double, double }* %z to i8*
+    %cast.17 = bitcast { double, double }* %tmp.5 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.16, i8* %cast.17, i64 16, i32 8, i1 false)
+    %cast.18 = bitcast { double, double }* %tmp.6 to i8*
+    %cast.19 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.18, i8* %cast.19, i64 16, i32 8, i1 false)
+    %cast.20 = bitcast { double, double }* %tmp.7 to i8*
+    %cast.21 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.20, i8* %cast.21, i64 16, i32 8, i1 false)
+    %field.12 = getelementptr inbounds { double, double }, { double, double }* %tmp.6, i32 0, i32 0
+    %.real.ld.4 = load double, double* %field.12
+    %field.14 = getelementptr inbounds { double, double }, { double, double }* %tmp.7, i32 0, i32 0
+    %.real.ld.5 = load double, double* %field.14
+    %fmul.0 = fmul double %.real.ld.4, %.real.ld.5
+    %field.13 = getelementptr inbounds { double, double }, { double, double }* %tmp.6, i32 0, i32 1
+    %.imag.ld.4 = load double, double* %field.13
+    %field.15 = getelementptr inbounds { double, double }, { double, double }* %tmp.7, i32 0, i32 1
+    %.imag.ld.5 = load double, double* %field.15
+    %fmul.1 = fmul double %.imag.ld.4, %.imag.ld.5
+    %fsub.2 = fsub double %fmul.0, %fmul.1
+    %field.121 = getelementptr inbounds { double, double }, { double, double }* %tmp.6, i32 0, i32 0
+    %.real.ld.6 = load double, double* %field.12
+    %field.152 = getelementptr inbounds { double, double }, { double, double }* %tmp.7, i32 0, i32 1
+    %.imag.ld.6 = load double, double* %field.15
+    %fmul.2 = fmul double %.real.ld.6, %.imag.ld.6
+    %field.133 = getelementptr inbounds { double, double }, { double, double }* %tmp.6, i32 0, i32 1
+    %.imag.ld.7 = load double, double* %field.13
+    %field.144 = getelementptr inbounds { double, double }, { double, double }* %tmp.7, i32 0, i32 0
+    %.real.ld.7 = load double, double* %field.14
+    %fmul.3 = fmul double %.imag.ld.7, %.real.ld.7
+    %fadd.2 = fadd double %fmul.2, %fmul.3
+    %field.16 = getelementptr inbounds { double, double }, { double, double }* %tmp.8, i32 0, i32 0
+    store double %fsub.2, double* %field.16
+    %field.17 = getelementptr inbounds { double, double }, { double, double }* %tmp.8, i32 0, i32 1
+    store double %fadd.2, double* %field.17
+    %cast.22 = bitcast { double, double }* %z to i8*
+    %cast.23 = bitcast { double, double }* %tmp.8 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.22, i8* %cast.23, i64 16, i32 8, i1 false)
+    %cast.24 = bitcast { double, double }* %tmp.9 to i8*
+    %cast.25 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.24, i8* %cast.25, i64 16, i32 8, i1 false)
+    %cast.26 = bitcast { double, double }* %tmp.10 to i8*
+    %cast.27 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.26, i8* %cast.27, i64 16, i32 8, i1 false)
+    %field.18 = getelementptr inbounds { double, double }, { double, double }* %tmp.9, i32 0, i32 0
+    %.real.ld.8 = load double, double* %field.18
+    %field.20 = getelementptr inbounds { double, double }, { double, double }* %tmp.10, i32 0, i32 0
+    %.real.ld.9 = load double, double* %field.20
+    %fcmp.0 = fcmp oeq double %.real.ld.8, %.real.ld.9
+    %zext.0 = zext i1 %fcmp.0 to i8
+    %field.19 = getelementptr inbounds { double, double }, { double, double }* %tmp.9, i32 0, i32 1
+    %.imag.ld.8 = load double, double* %field.19
+    %field.21 = getelementptr inbounds { double, double }, { double, double }* %tmp.10, i32 0, i32 1
+    %.imag.ld.9 = load double, double* %field.21
+    %fcmp.1 = fcmp oeq double %.imag.ld.8, %.imag.ld.9
+    %zext.1 = zext i1 %fcmp.1 to i8
+    %iand.0 = and i8 %zext.0, %zext.1
+    store i8 %iand.0, i8* %b
+    %cast.28 = bitcast { double, double }* %tmp.11 to i8*
+    %cast.29 = bitcast { double, double }* %x to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.28, i8* %cast.29, i64 16, i32 8, i1 false)
+    %cast.30 = bitcast { double, double }* %tmp.12 to i8*
+    %cast.31 = bitcast { double, double }* %y to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.30, i8* %cast.31, i64 16, i32 8, i1 false)
+    %field.22 = getelementptr inbounds { double, double }, { double, double }* %tmp.11, i32 0, i32 0
+    %.real.ld.10 = load double, double* %field.22
+    %field.24 = getelementptr inbounds { double, double }, { double, double }* %tmp.12, i32 0, i32 0
+    %.real.ld.11 = load double, double* %field.24
+    %fcmp.2 = fcmp one double %.real.ld.10, %.real.ld.11
+    %zext.2 = zext i1 %fcmp.2 to i8
+    %field.23 = getelementptr inbounds { double, double }, { double, double }* %tmp.11, i32 0, i32 1
+    %.imag.ld.10 = load double, double* %field.23
+    %field.25 = getelementptr inbounds { double, double }, { double, double }* %tmp.12, i32 0, i32 1
+    %.imag.ld.11 = load double, double* %field.25
+    %fcmp.3 = fcmp one double %.imag.ld.10, %.imag.ld.11
+    %zext.3 = zext i1 %fcmp.3 to i8
+    %ior.0 = or i8 %zext.2, %zext.3
+    store i8 %ior.0, i8* %b
+    ret void
+  }
+  )RAW_RESULT";
+
+  bool broken = h.finish(StripDebugInfo);
+  EXPECT_FALSE(broken && "Module failed to verify.");
+
+  bool isOK = h.expectValue(func->function(), exp);
+  EXPECT_TRUE(isOK && "Block does not have expected contents");
+}
+
+TEST(BackendExprTests, TestComplexExpressions) {
+  FcnTestHarness h("foo");
+  Llvm_backend *be = h.be();
+  Location loc;
+
+  // var a, b float64
+  // var x complex128
+  Btype *bf64t = be->float_type(64);
+  Btype *bc128t = be->complex_type(128);
+  Bvariable *a = h.mkLocal("a", bf64t);
+  Bvariable *b = h.mkLocal("b", bf64t);
+  Bvariable *x = h.mkLocal("x", bc128t);
+
+  // a = real(x)
+  Bexpression *avex1 = be->var_expression(a, VE_lvalue, loc);
+  Bexpression *xvex1 = be->var_expression(x, VE_rvalue, loc);
+  Bexpression *realex = be->real_part_expression(xvex1, loc);
+  h.mkAssign(avex1, realex);
+
+  // b = imag(x)
+  Bexpression *bvex2 = be->var_expression(b, VE_lvalue, loc);
+  Bexpression *xvex2 = be->var_expression(x, VE_rvalue, loc);
+  Bexpression *imagex = be->imag_part_expression(xvex2, loc);
+  h.mkAssign(bvex2, imagex);
+
+  // x = complex(b, a)
+  Bexpression *xvex3 = be->var_expression(x, VE_lvalue, loc);
+  Bexpression *bvex3 = be->var_expression(b, VE_rvalue, loc);
+  Bexpression *avex3 = be->var_expression(a, VE_rvalue, loc);
+  Bexpression *compex = be->complex_expression(bvex3, avex3, loc);
+  h.mkAssign(xvex3, compex);
+
+  const char *exp = R"RAW_RESULT(
+    store double 0.000000e+00, double* %a
+    store double 0.000000e+00, double* %b
+    %cast.0 = bitcast { double, double }* %x to i8*
+    %cast.1 = bitcast { double, double }* @const.0 to i8*
+    call void @llvm.memcpy.p0i8.p0i8.i64(i8* %cast.0, i8* %cast.1, i64 16, i32 8, i1 false)
+    %field.0 = getelementptr inbounds { double, double }, { double, double }* %x, i32 0, i32 0
+    %x.real.ld.0 = load double, double* %field.0
+    store double %x.real.ld.0, double* %a
+    %field.1 = getelementptr inbounds { double, double }, { double, double }* %x, i32 0, i32 1
+    %x.imag.ld.0 = load double, double* %field.1
+    store double %x.imag.ld.0, double* %b
+    %b.ld.0 = load double, double* %b
+    %a.ld.0 = load double, double* %a
+    %field.2 = getelementptr inbounds { double, double }, { double, double }* %x, i32 0, i32 0
+    store double %b.ld.0, double* %field.2
+    %field.3 = getelementptr inbounds { double, double }, { double, double }* %x, i32 0, i32 1
+    store double %a.ld.0, double* %field.3
+  )RAW_RESULT";
+
+  bool isOK = h.expectBlock(exp);
+  EXPECT_TRUE(isOK && "Block does not have expected contents");
+
+  bool broken = h.finish(StripDebugInfo);
+  EXPECT_FALSE(broken && "Module failed to verify.");
+}
+
 TEST(BackendExprTests, CreateStringConstantExpressions) {
 
   FcnTestHarness h("foo");