gollvm: synthesize LHS/RHS context for var expressions

Up until now the bridge code would rely on the frontend to pass in a
tag on each var expression to indicate whether the variable appeared
in a left-hand-side or right-hand-side context.  This change adds
machinery to synthesize the correct tags from within the bridge,
without any input from the front end.

Also as part of this change:

- move the expansion of complex operations (compare, arithmetic, etc)
  a bit earlier in the compiler flow, so as to avoid a lot ofu
  materialize() / resolve calls along the way

- revamp handling of compound expressions (N_Compound) to be more
  consistent with other expressions with respect to llvm::Value:
  initial Bexpression will have a NULL llvm::Value, then a value is
  assigned during materialization.

Change-Id: Ic70e62d20122118bb123d7babb7ae64ef45a75e9
Reviewed-on: https://go-review.googlesource.com/47930
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/llvm-gofrontend/go-llvm-bexpression.h b/llvm-gofrontend/go-llvm-bexpression.h
index 37e0fea..c61b535 100644
--- a/llvm-gofrontend/go-llvm-bexpression.h
+++ b/llvm-gofrontend/go-llvm-bexpression.h
@@ -86,8 +86,7 @@
 
   bool pending() const { return pending_; }
   unsigned addrLevel() const { return addrLevel_; }
-  unsigned lvalue() const { return lvalue_; }
-  void incrementAddrLevel() { addrLevel_ += 1; }
+  bool lvalue() const { return lvalue_; }
   void setPending(bool lvalue, unsigned addrLevel) {
     assert(!pending_);
     pending_ = true;
@@ -95,6 +94,11 @@
     addrLevel_ = addrLevel;
   }
   void reset() { assert(pending_); pending_ = false; }
+  bool equal(const VarContext &other) const {
+    return (pending_ == other.pending_ &&
+            addrLevel_ == other.addrLevel_ &&
+            lvalue_ == other.lvalue_);
+  }
 
  private:
   unsigned addrLevel_;
diff --git a/llvm-gofrontend/go-llvm-bnode.cpp b/llvm-gofrontend/go-llvm-bnode.cpp
index c49bc16..71a3a0e 100644
--- a/llvm-gofrontend/go-llvm-bnode.cpp
+++ b/llvm-gofrontend/go-llvm-bnode.cpp
@@ -544,8 +544,6 @@
       new Bexpression(N_UnaryOp, kids, val, typ, loc);
   rval->u.op = op;
   appendInstIfNeeded(rval, val);
-  if (src->varExprPending())
-    rval->setVarExprPending(src->varContext());
   return archive(rval);
 }
 
@@ -556,8 +554,6 @@
   Bexpression *rval =
       new Bexpression(N_Conversion, kids, val, typ, loc);
   appendInstIfNeeded(rval, val);
-  if (src->varExprPending())
-    rval->setVarExprPending(src->varContext());
   return archive(rval);
 }
 
@@ -646,8 +642,6 @@
       new Bexpression(N_StructField, kids, val, typ, loc);
   appendInstIfNeeded(rval, val);
   rval->u.fieldIndex = fieldIndex;
-  if (structval->varExprPending())
-    rval->setVarExprPending(structval->varContext());
   return archive(rval);
 }
 
@@ -661,8 +655,6 @@
   Bexpression *rval =
       new Bexpression(N_ArrayIndex, kids, val, typ, loc);
   appendInstIfNeeded(rval, val);
-  if (arval->varExprPending())
-    rval->setVarExprPending(arval->varContext());
   return archive(rval);
 }
 
@@ -681,14 +673,13 @@
 
 Bexpression *BnodeBuilder::mkCompound(Bstatement *st,
                                       Bexpression *expr,
+                                      llvm::Value *val,
                                       Location loc)
 {
   std::vector<Bnode *> kids = { st, expr };
   Bexpression *rval =
-      new Bexpression(N_Compound, kids, expr->value(), expr->btype(), loc);
+      new Bexpression(N_Compound, kids, val, expr->btype(), loc);
   rval->setTag(expr->tag());
-  if (expr->varExprPending())
-    rval->setVarExprPending(expr->varContext());
   return archive(rval);
 }
 
diff --git a/llvm-gofrontend/go-llvm-bnode.h b/llvm-gofrontend/go-llvm-bnode.h
index 9c8ce2e..16d5ec5 100644
--- a/llvm-gofrontend/go-llvm-bnode.h
+++ b/llvm-gofrontend/go-llvm-bnode.h
@@ -253,7 +253,7 @@
   Bexpression *mkBinaryOp(Operator op, Btype *typ, llvm::Value *val,
                           Bexpression *left, Bexpression *right,
                           Binstructions &instructions, Location loc);
-  Bexpression *mkCompound(Bstatement *st, Bexpression *expr,
+  Bexpression *mkCompound(Bstatement *st, Bexpression *expr, llvm::Value *val,
                           Location loc);
   Bexpression *mkStructField(Btype *type, llvm::Value *value,
                              Bexpression *structval, unsigned fieldIndex,
diff --git a/llvm-gofrontend/go-llvm-materialize.cpp b/llvm-gofrontend/go-llvm-materialize.cpp
index f8b6649..a45ff13 100644
--- a/llvm-gofrontend/go-llvm-materialize.cpp
+++ b/llvm-gofrontend/go-llvm-materialize.cpp
@@ -121,10 +121,6 @@
   assert(iexprs.size() == 1);
   Bexpression *expr = iexprs[0];
 
-  // 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());
@@ -256,46 +252,6 @@
   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?
-  auto p = makeTempVar(expr, location);
-  Bvariable *var = p.first;
-  Bstatement *einit = p.second;
-
-  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.
-  auto p2 = makeTempVar(val, location);
-  Bvariable *rvar = p2.first;
-  Bstatement *rinit = p2.second;
-  Bexpression *rvex = nbuilder_.mkVar(rvar, location);
-  Bstatement *init = statement_list(std::vector<Bstatement*>{einit, rinit});
-  return nbuilder_.mkCompound(init, rvex, location);
-}
-
 llvm::Value *Llvm_backend::makePointerOffsetGEP(llvm::PointerType *llpt,
                                                 llvm::Value *idxval,
                                                 llvm::Value *sptr)
@@ -386,7 +342,10 @@
 
   // Compound expressions can be used to produce lvalues, so we don't
   // want to call resolve() on bexpr here.
-  Bexpression *rval = nbuilder_.mkCompound(bstat, bexpr, location);
+  Bexpression *rval = nbuilder_.mkCompound(bstat, bexpr, bexpr->value(),
+                                           location);
+  if (bexpr->varExprPending())
+    rval->setVarExprPending(bexpr->varContext());
   return rval;
 }
 
@@ -455,7 +414,8 @@
   Bexpression *rval = (tempv ?
                        var_expression(tempv, VE_rvalue, location) :
                        nbuilder_.mkVoidValue(void_type()));
-  Bexpression *result = compound_expression(ifStmt, rval, location);
+  Bexpression *result =
+      materialize(compound_expression(ifStmt, rval, location));
   return result;
 }
 
@@ -628,11 +588,6 @@
 
   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);
@@ -774,73 +729,6 @@
   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?
-  auto p = makeTempVar(left, location), p2 = makeTempVar(right, location);
-  Bvariable *lvar = p.first, *rvar = p2.first;
-  Bstatement *linit = p.second, *rinit = p2.second;
-
-  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 materialize(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.
-  auto p3 = makeTempVar(val, location);
-  Bvariable *vvar = p3.first;
-  Bstatement *vinit = p3.second;
-  Bexpression *vvex = nbuilder_.mkVar(vvar, location);
-  Bstatement *init = statement_list(std::vector<Bstatement*>{linit, rinit, vinit});
-  return compound_expression(init, vvex, location);
-}
-
 Bexpression *Llvm_backend::materializeComposite(Bexpression *comExpr)
 {
   Location location = comExpr->location();
@@ -1522,6 +1410,185 @@
   return srcVal;
 }
 
+// Walk the specified expression and invoke setVarExprPending on
+// each var expression, with correct lvalue/rvalue tag depending on
+// context.
+
+class VarContextVisitor {
+ public:
+  VarContextVisitor(Bexpression *top,
+                    Varexpr_context lvalueContext,
+                    bool dumpDiffs)
+      : dumpDiffs_(dumpDiffs)
+  {
+    if (lvalueContext == VE_lvalue && isMem(top))
+      setLvalue(top);
+  }
+
+  std::pair< std::pair<VisitDisp, VisitChildDisp>, Bnode *>
+  visitChildPre(Bnode *parent, Bnode *child) {
+
+    Bexpression *eparent = parent->castToBexpression();
+    assert(eparent != nullptr);
+
+    // Don't descend into stmts or non-var nodes with values.
+    Bexpression *echild = child->castToBexpression();
+    if (child->isStmt() || (echild->value() != nullptr &&
+                            echild->flavor() != N_Var))
+      return std::make_pair(std::make_pair(ContinueWalk, SkipChild), child);
+
+    // Propagate lvalue property down from parent to child through a memory
+    // operation such as a fieldref or arrayindex if applicable. For example,
+    // for the tree build from go code "x.f1[y]" such as
+    //
+    //             array_index
+    //             /      \.
+    //        field      var(y)
+    //        /
+    //      var(x)
+    //
+    // If the top node (array_index) is in a left-hand-side position,
+    // then we want to flag "field" and "var(x)" nodes as being in an
+    // LHS context also, but not the "var(y)" node (it is not being
+    // assigned).
+    assert(echild != nullptr);
+    if (isLvalue(eparent) && isMem(echild)) {
+      Bexpression *cmem = memArg(eparent);
+      assert(cmem != nullptr);
+      if (cmem == echild)
+        setLvalue(echild);
+    }
+    return std::make_pair(std::make_pair(ContinueWalk, VisitChild), child);
+  }
+
+  // Apply "var pending" tags to var exprs bottom up, once downward propagation
+  // of lvalue context is complete.
+  std::pair<VisitDisp, Bnode *> visitNodePost(Bnode *node)
+  {
+    Bexpression *expr = node->castToBexpression();
+    if (expr == nullptr || (expr->value() && expr->flavor() != N_Var))
+      return std::make_pair(ContinueWalk, expr);
+
+    if (expr->flavor() == N_Var)
+      setVarExprPending(expr, isLvalue(expr), 0);
+
+    // Debugging only. This is intended to provide a way to compare the
+    // tags applied by the frontend vs the tags this visitor generates.
+    if (dumpDiffs_)
+      dumpDiff(expr);
+
+    auto it = varcontext_.find(expr);
+    if (it != varcontext_.end()) {
+      VarContext vc(it->second);
+      bool lvalue = vc.lvalue();
+      if (expr->varExprPending()) {
+        assert(vc.equal(expr->varContext()));
+      } else {
+        expr->setVarExprPending(lvalue, 0);
+      }
+    }
+
+    return std::make_pair(ContinueWalk, expr);
+  }
+
+  // boilerplate
+  std::pair<VisitDisp, Bnode *> visitNodePre(Bnode *node) {
+    return std::make_pair(ContinueWalk, node);
+  }
+
+  // boilerplate
+  std::pair<VisitDisp, Bnode *> visitChildPost(Bnode *parent, Bnode *child) {
+      return std::make_pair(ContinueWalk, child);
+  }
+
+ private:
+
+  // Debugging only.
+  void dumpDiff(Bexpression *expr)
+  {
+    if (expr->value() && expr->flavor() != N_Var)
+      return;
+
+    VarContext oldvc;
+    if (expr->varExprPending())
+      oldvc = expr->varContext();
+
+    VarContext newvc;
+    auto nit = varcontext_.find(expr);
+    if (nit != varcontext_.end())
+      newvc = nit->second;
+
+    if (newvc.equal(oldvc))
+      return;
+
+    std::cerr << "Expr " << expr->id()
+              << " " << expr->flavstr() << " ";
+    if (oldvc.pending()) {
+      std::cerr << "old VC(" << (oldvc.pending() ? "true" : "false")
+                << "," << (oldvc.lvalue() ? "lval" : "rval")
+                << "," << oldvc.addrLevel() << ") ";
+    }
+    if (newvc.pending()) {
+      std::cerr << "new VC(" << (newvc.pending() ? "true" : "false")
+                << "," << (newvc.lvalue() ? "lval" : "rval")
+                << "," << newvc.addrLevel() << ") ";
+    }
+    std::cerr << "\n";
+    expr->dump();
+    std::cerr << "\n";
+  }
+
+  Bexpression *memArg(Bexpression *expr) {
+    const std::vector<Bnode *> &kids = expr->children();
+    switch(expr->flavor()) {
+      case N_StructField:
+      case N_ArrayIndex:
+      case N_Address:
+      case N_Deref:
+      case N_Conversion:
+      case N_PointerOffset:
+        return kids[0]->castToBexpression();
+      default:
+        return nullptr;
+    }
+  }
+
+  bool isMem(Bexpression *expr) {
+    switch(expr->flavor()) {
+      case N_Var:
+      case N_StructField:
+      case N_Address:
+      case N_Deref:
+      case N_Conversion:
+      case N_ArrayIndex:
+      case N_PointerOffset:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  void setLvalue(Bexpression *expr) {
+    assert(lvalue_.find(expr) == lvalue_.end());
+    lvalue_.insert(expr);
+  }
+
+  bool isLvalue(Bexpression *expr) {
+    return lvalue_.find(expr) != lvalue_.end();
+  }
+
+  void setVarExprPending(Bexpression *expr, bool lvalue, unsigned addrLevel) {
+    assert(expr);
+    VarContext vc(lvalue, addrLevel);
+    varcontext_[expr] = vc;
+  }
+
+ private:
+  std::set<Bnode *> lvalue_;
+  std::map<Bexpression *, VarContext> varcontext_;
+  bool dumpDiffs_;
+};
+
 // Helper visitor class for expression folding; removes redundant
 // nodes in preparation for materialization.
 
@@ -1589,7 +1656,7 @@
   std::pair<VisitDisp, Bnode *> visitNodePost(Bnode *node) {
     Bexpression *expr = node->castToBexpression();
     if (expr && expr->value())
-        return std::make_pair(ContinueWalk, node);
+      return std::make_pair(ContinueWalk, node);
     switch(node->flavor()) {
       case N_EmptyStmt:
       case N_LabelStmt:
@@ -1677,19 +1744,36 @@
   }
 
   std::pair<VisitDisp, Bnode *> visitChildPost(Bnode *parent, Bnode *child) {
-      return std::make_pair(ContinueWalk, child);
+    return std::make_pair(ContinueWalk, child);
   }
-   private:
-    Llvm_backend *be_;
+ private:
+  Llvm_backend *be_;
 };
 
-Bexpression *Llvm_backend::materialize(Bexpression *expr)
+Bexpression *Llvm_backend::materialize(Bexpression *expr,
+                                       Varexpr_context lvalueContext)
+
 {
   // Repair any node sharing at this point-- the materializer
   // assumes that any node it visits can be destroyed/replaced
   // without impacting some other portion of the tree.
   enforceTreeIntegrity(expr);
 
+  // TODO:
+  // - don't really need a treewalk in the non-LHS case to apply
+  //   can context tags; it would be simpler to only do the walk
+  //   in the LHS case (and just apply var context tags when
+  //   var expression is initially created)
+  // - an extension of the above: pass in the LHS/RHS context
+  //   and propagate recursively during the materialize() walk,
+  //   instead of having a separate tree-walk here.
+
+  // Locate and tag var expressions within the tree, selecting LHS or
+  // RHS context as appropriate. Needs to be done after the call above
+  // so as to insure that there are no share var expressions.
+  VarContextVisitor vcvis(expr, lvalueContext, false);
+  update_walk_nodes(expr, vcvis);
+
   // Perform some basic folding operations. This is easier to do
   // here so as not to worry about sharing.
   FoldVisitor fvis(this);
diff --git a/llvm-gofrontend/go-llvm.cpp b/llvm-gofrontend/go-llvm.cpp
index 1347e17..56f9dbb 100644
--- a/llvm-gofrontend/go-llvm.cpp
+++ b/llvm-gofrontend/go-llvm.cpp
@@ -877,7 +877,6 @@
 
   Bexpression *varexp = nbuilder_.mkVar(var, location);
   varexp->setTag(var->name().c_str());
-  varexp->setVarExprPending(in_lvalue_pos == VE_lvalue, 0);
   return varexp;
 }
 
@@ -1139,6 +1138,46 @@
   return rval;
 }
 
+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?
+  auto p = makeTempVar(expr, location);
+  Bvariable *var = p.first;
+  Bstatement *einit = p.second;
+
+  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.
+  auto p2 = makeTempVar(val, location);
+  Bvariable *rvar = p2.first;
+  Bstatement *rinit = p2.second;
+  Bexpression *rvex = nbuilder_.mkVar(rvar, location);
+  Bstatement *init = statement_list(std::vector<Bstatement*>{einit, rinit});
+  return nbuilder_.mkCompound(init, rvex, nullptr, location);
+}
+
 // An expression that converts an expression to a different type.
 
 Bexpression *Llvm_backend::convert_expression(Btype *type,
@@ -1158,6 +1197,10 @@
     toType = type->type();
   }
 
+  // Complex -> complex conversion
+  if (type->castToBComplexType())
+    return makeComplexConvertExpr(type, expr, location);
+
   Bexpression *rval = nbuilder_.mkConversion(type, nullptr, expr, location);
   return rval;
 }
@@ -1204,7 +1247,7 @@
   if (bstat == errorStatement() || bexpr == errorExpression())
     return errorExpression();
 
-  Bexpression *rval = nbuilder_.mkCompound(bstat, bexpr, location);
+  Bexpression *rval = nbuilder_.mkCompound(bstat, bexpr, nullptr, location);
   return rval;
 }
 
@@ -1253,6 +1296,73 @@
   return rval;
 }
 
+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?
+  auto p = makeTempVar(left, location), p2 = makeTempVar(right, location);
+  Bvariable *lvar = p.first, *rvar = p2.first;
+  Bstatement *linit = p.second, *rinit = p2.second;
+
+  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 materialize(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.
+  auto p3 = makeTempVar(val, location);
+  Bvariable *vvar = p3.first;
+  Bstatement *vinit = p3.second;
+  Bexpression *vvex = nbuilder_.mkVar(vvar, location);
+  Bstatement *init = statement_list(std::vector<Bstatement*>{linit, rinit, vinit});
+  return compound_expression(init, vvex, location);
+}
+
 // Return an expression for the binary operation LEFT OP RIGHT.
 
 Bexpression *Llvm_backend::binary_expression(Operator op, Bexpression *left,
@@ -1261,6 +1371,14 @@
   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);
+
   // Arbitrarily select the left child type as the type for the binop.
   // This may be revised later during materializeBinary.
   Btype *btype = left->btype();
@@ -1445,7 +1563,7 @@
   if (lhs == errorExpression() || rhs == errorExpression() ||
       bfunction == errorFunction_.get())
     return errorStatement();
-  lhs = materialize(lhs);
+  lhs = materialize(lhs, VE_lvalue);
   rhs = materialize(rhs);
   Bexpression *lhs2 = resolveVarContext(lhs, VE_lvalue);
   Bexpression *rhs2 = rhs;
diff --git a/llvm-gofrontend/go-llvm.h b/llvm-gofrontend/go-llvm.h
index 13a99e3..1f58a1e 100644
--- a/llvm-gofrontend/go-llvm.h
+++ b/llvm-gofrontend/go-llvm.h
@@ -654,8 +654,10 @@
 
  public:
 
-  // Exposed for unit testing.
-  Bexpression *materialize(Bexpression *expr);
+  // Performs a bottom-up walk to materialize LLVM values for each
+  // node in the expression tree. Made public for unit testing.
+  Bexpression *materialize(Bexpression *expr,
+                           Varexpr_context lvalueContext=VE_rvalue);
 
   // Helper routines to materialize llvm::Value's for expression nodes,
   // invoked by routine above. Public primarily because we need to call
diff --git a/unittests/BackendCore/BackendExprTests.cpp b/unittests/BackendCore/BackendExprTests.cpp
index 6110875..e3d2d5c 100644
--- a/unittests/BackendCore/BackendExprTests.cpp
+++ b/unittests/BackendCore/BackendExprTests.cpp
@@ -536,12 +536,14 @@
 
   // Same here.
   Bexpression *ve3 = be->var_expression(loc1, VE_lvalue, loc);
+  Bexpression *ve3r = be->var_expression(loc1, VE_rvalue, loc);
   EXPECT_EQ(repr(ve3->value()), "%loc1 = alloca i64");
-  h.mkExprStmt(ve3);
+  h.mkAssign(ve3, ve3r);
   Bexpression *ve4 = be->var_expression(loc1, VE_lvalue, loc);
+  Bexpression *ve4r = be->var_expression(loc1, VE_rvalue, loc);
   EXPECT_EQ(repr(ve4->value()), "%loc1 = alloca i64");
   EXPECT_NE(ve3, ve4);
-  h.mkExprStmt(ve4);
+  h.mkAssign(ve4, ve4r);
 
   bool broken = h.finish(PreserveDebugInfo);
   EXPECT_FALSE(broken && "Module failed to verify.");