//===-- statements.cpp ----------------------------------------------------===//
//
//                         LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//

#include "dmd/errors.h"
#include "dmd/expression.h"
#include "dmd/hdrgen.h"
#include "dmd/id.h"
#include "dmd/identifier.h"
#include "dmd/import.h"
#include "dmd/init.h"
#include "dmd/mangle.h"
#include "dmd/module.h"
#include "dmd/mtype.h"
#include "dmd/root/port.h"
#include "gen/abi/abi.h"
#include "gen/arrays.h"
#include "gen/classes.h"
#include "gen/coverage.h"
#include "gen/dcompute/target.h"
#include "gen/dvalue.h"
#include "gen/funcgenstate.h"
#include "gen/functions.h"
#include "gen/irstate.h"
#include "gen/llvm.h"
#include "gen/llvmhelpers.h"
#include "gen/logger.h"
#include "gen/recursivevisitor.h"
#include "gen/runtime.h"
#include "gen/tollvm.h"
#include "ir/irfunction.h"
#include "ir/irmodule.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/InlineAsm.h"
#include <fstream>
#include <math.h>
#include <stdio.h>

using namespace dmd;

//////////////////////////////////////////////////////////////////////////////
// FIXME: Integrate these functions
void GccAsmStatement_toIR(GccAsmStatement *stmt, IRState *irs);
void AsmStatement_toIR(InlineAsmStatement *stmt, IRState *irs);
void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p);

//////////////////////////////////////////////////////////////////////////////

namespace {
bool isAssertFalse(Expression *e) {
  return e ? e->type == Type::tnoreturn &&
                 (e->op == EXP::halt || e->op == EXP::assert_)
           : false;
}

bool isAssertFalse(Statement *s) {
  if (!s)
    return false;
  if (auto es = s->isExpStatement())
    return isAssertFalse(es->exp);
  else if (auto ss = s->isScopeStatement())
    return isAssertFalse(ss->statement);
  return false;
}
}

//////////////////////////////////////////////////////////////////////////////

/// Used to check if a control-flow stmt body contains any label. A label
/// is considered anything that lets us jump inside the body _apart from_
/// the stmt. That includes case / default statements.
/// It is a StoppableVisitor that stops when a label is found.
/// It's to be passed in a ContainsLabelWalker which recursively
/// walks the tree and updates our `inside_switch` flag accordingly.
struct ContainsLabelVisitor : public StoppableVisitor {
  // If RecursiveWalker finds a SwitchStatement,
  // `insideSwitch` points to that statement.
  SwitchStatement *insideSwitch = nullptr;

  using StoppableVisitor::visit;

  void visit(Statement *stmt) override {}

  void visit(LabelStatement *stmt) override { stop = true; }

  void visit(CaseStatement *stmt) override {
    if (insideSwitch == nullptr)
      stop = true;
  }

  void visit(DefaultStatement *stmt) override {
    if (insideSwitch == nullptr)
      stop = true;
  }

  bool foundLabel() { return stop; }

  void visit(Declaration *) override {}
  void visit(Initializer *) override {}
  void visit(Dsymbol *) override {}
  void visit(Expression *) override {}
};

/// As the RecursiveWalker, but it gets a ContainsLabelVisitor
/// and updates its `insideSwitch` field accordingly.
class ContainsLabelWalker : public RecursiveWalker {
public:
  using RecursiveWalker::visit;

  explicit ContainsLabelWalker(ContainsLabelVisitor *visitor,
                               bool _continueAfterStop = true)
      : RecursiveWalker(visitor, _continueAfterStop) {}

  void visit(SwitchStatement *stmt) override {
    ContainsLabelVisitor *ev = static_cast<ContainsLabelVisitor *>(v);
    SwitchStatement *save = ev->insideSwitch;
    ev->insideSwitch = stmt;
    RecursiveWalker::visit(stmt);
    ev->insideSwitch = save;
  }

  void visit(Expression *) override {}
};

class ToIRVisitor : public Visitor {
  IRState *irs;

public:
  explicit ToIRVisitor(IRState *irs) : irs(irs) {}

  //////////////////////////////////////////////////////////////////////////

  // Import all functions from class Visitor
  using Visitor::visit;

  //////////////////////////////////////////////////////////////////////////

  void visit(CompoundStatement *stmt) override {
    IF_LOG Logger::println("CompoundStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    for (auto s : *stmt->statements) {
      if (s) {
        s->accept(this);
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ReturnStatement *stmt) override {
    IF_LOG Logger::println("ReturnStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    // The LLVM value to return, or null for void returns.
    LLValue *returnValue = nullptr;

    auto &funcGen = irs->funcGen();
    IrFunction *const f = &funcGen.irFunc;
    FuncDeclaration *const fd = f->decl;
    llvm::FunctionType *funcType = f->getLLVMFuncType();

    emitInstrumentationFnLeave(fd);

    const auto cleanupScopeBeforeExpression =
        funcGen.scopes.currentCleanupScope();

    // is there a return value expression?
    const bool isMainFunc = isAnyMainFunction(fd);
    if (stmt->exp || isMainFunc) {
      // We clean up manually (*not* using toElemDtor) as the expression might
      // be an lvalue pointing into a temporary, and we may need a load. So we
      // need to make sure to destruct any temporaries after all of that.

      const auto rt = f->type->next;
      const auto rtb = rt->toBasetype();

      if (!stmt->exp) {
        // implicitly return 0 for the main function
        returnValue = LLConstant::getNullValue(funcType->getReturnType());
      } else if ((rtb->ty == TY::Tvoid || rtb->ty == TY::Tnoreturn) &&
                 !isMainFunc) {
        // evaluate expression for side effects
        assert(stmt->exp->type->toBasetype()->ty == TY::Tvoid ||
               stmt->exp->type->toBasetype()->ty == TY::Tnoreturn);
        toElem(stmt->exp);
      } else if (funcType->getReturnType()->isVoidTy()) {
        // if the IR function's return type is void (but not the D one), it uses
        // sret
        assert(!f->type->isref());

        LLValue *sretPointer = f->sretArg;
        assert(sretPointer);

        assert(!f->irFty.arg_sret->rewrite &&
               "ABI shouldn't have to rewrite sret returns");
        DLValue returnValue(rt, sretPointer);

        // try to construct the return value in-place
        const bool constructed = toInPlaceConstruction(&returnValue, stmt->exp);
        if (!constructed) {
          DValue *e = toElem(stmt->exp);

          // store the return value unless NRVO already used the sret pointer
          if (!e->isLVal() || DtoLVal(e) != sretPointer) {
            // call postblit if the expression is a D lvalue
            // exceptions: NRVO and special __result variable (out contracts)
            bool doPostblit = !(fd->isNRVO() && fd->nrvo_var);
            if (doPostblit) {
              if (auto ve = stmt->exp->isVarExp())
                if (ve->var->isResult())
                  doPostblit = false;
            }

            DtoAssign(stmt->loc, &returnValue, e, EXP::blit);
            if (doPostblit)
              callPostblit(stmt->loc, stmt->exp, sretPointer);
          }
        }
      } else {
        // the return type is not void, so this is a normal "register" return
        if (stmt->exp->op == EXP::null_) {
          stmt->exp->type = rt;
        }
        DValue *dval = nullptr;
        // call postblit if necessary
        if (!f->type->isref()) {
          dval = toElem(stmt->exp);
          LLValue *vthis =
              (DtoIsInMemoryOnly(dval->type) ? DtoLVal(dval) : DtoRVal(dval));
          callPostblit(stmt->loc, stmt->exp, vthis);
        } else {
          Expression *ae = stmt->exp;
          dval = toElem(ae);
        }
        // do abi specific transformations on the return value
        returnValue = getIrFunc(fd)->irFty.putRet(dval);

        // Hack around LDC assuming structs and static arrays are in memory:
        // If the function returns a struct or a static array, and the return
        // value is a pointer to a struct or a static array, load from it
        // before returning.
        if (returnValue->getType() != funcType->getReturnType() &&
            DtoIsInMemoryOnly(rt) && isaPointer(returnValue)) {
          Logger::println("Loading value for return");
          returnValue = DtoLoad(funcType->getReturnType(), returnValue);
        }
      }
    } else {
      // no return value expression means it's a void function.
      assert(funcType->getReturnType()->isVoidTy());
    }

    // If there are no cleanups to run, we try to keep the IR simple and
    // just directly emit the return instruction. If there are cleanups to run
    // first, we need to store the return value to a stack slot, in which case
    // we can use a shared return bb for all these cases.
    const bool useRetValSlot = funcGen.scopes.currentCleanupScope() != 0;
    const bool sharedRetBlockExists = !!funcGen.retBlock;
    if (useRetValSlot) {
      if (!sharedRetBlockExists) {
        funcGen.retBlock = irs->insertBB("return");
        if (returnValue) {
          funcGen.retValSlot =
              DtoRawAlloca(returnValue->getType(), 0, "return.slot");
        }
      }

      // Create the store to the slot at the end of our current basic
      // block, before we run the cleanups.
      if (returnValue) {
        irs->ir->CreateStore(returnValue, funcGen.retValSlot);
      }

      // Now run the cleanups.
      funcGen.scopes.runCleanups(0, funcGen.retBlock);
      // Pop the cleanups pushed during evaluation of the return expression.
      funcGen.scopes.popCleanups(cleanupScopeBeforeExpression);

      irs->ir->SetInsertPoint(funcGen.retBlock);
    }

    // If we need to emit the actual return instruction, do so.
    if (!useRetValSlot || !sharedRetBlockExists) {
      if (returnValue) {
        // Hack: the frontend generates 'return 0;' as last statement of
        // 'void main()'. But the debug location is missing. Use the end
        // of function as debug location.
        if (isAnyMainFunction(fd) && !stmt->loc.linnum()) {
          irs->DBuilder.EmitStopPoint(fd->endloc);
        }

        irs->ir->CreateRet(
            useRetValSlot ? DtoLoad(funcType->getReturnType(), funcGen.retValSlot)
                          : returnValue);
      } else {
        irs->ir->CreateRetVoid();
      }
    }

    // Finally, create a new predecessor-less dummy bb as the current IRScope
    // to make sure we do not emit any extra instructions after the terminating
    // instruction (ret or branch to return bb), which would be illegal IR.
    irs->ir->SetInsertPoint(irs->insertBB("dummy.afterreturn"));
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ExpStatement *stmt) override {
    IF_LOG Logger::println("ExpStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    if (auto e = stmt->exp) {
      if (e->hasCode() &&
          !isAssertFalse(e)) { // `assert(0)` not meant to be covered
        emitCoverageLinecountInc(stmt->loc);
      }

      DValue *elem;
      // a cast(void) around the expression is allowed, but doesn't require any
      // code
      if (e->op == EXP::cast_ && e->type == Type::tvoid) {
        elem = toElemDtor(static_cast<CastExp *>(e)->e1);
      } else {
        elem = toElemDtor(e);
      }
      delete elem;
    }
  }

  //////////////////////////////////////////////////////////////////////////

  bool dcomputeReflectMatches(CallExp *ce) {
    auto arg1 = (DComputeTarget::ID)(*ce->arguments)[0]->toInteger();
    auto arg2 = (*ce->arguments)[1]->toInteger();
    auto dct = irs->dcomputetarget;
    if (!dct) {
      return arg1 == DComputeTarget::ID::Host;
    } else {
      return arg1 == dct->target &&
             (!arg2 || arg2 == static_cast<dinteger_t>(dct->tversion));
    }
  }

  //////////////////////////////////////////////////////////////////////////

  bool containsLabel(Statement *stmt) {
    if (!stmt)
      return false;
    ContainsLabelVisitor labelChecker;
    ContainsLabelWalker walker(&labelChecker, false);
    stmt->accept(&walker);
    return labelChecker.foundLabel();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(IfStatement *stmt) override {
    IF_LOG Logger::println("IfStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);
    auto truecount = PGO.getRegionCount(stmt);
    auto elsecount = PGO.getCurrentRegionCount() - truecount;
    auto brweights = PGO.createProfileWeights(truecount, elsecount);

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);
    emitCoverageLinecountInc(stmt->loc);
    // Open a new scope for the optional condition variable (`if (auto i = ...)`)
    irs->funcGen().localVariableLifetimeAnnotator.pushScope();


    // This is a (dirty) hack to get codegen time conditional
    // compilation, on account of the fact that we are trying
    // to target multiple backends "simultaneously" with one
    // pass through the front end, to have a single "static"
    // context.
    if (auto ce = stmt->condition->isCallExp()) {
      if (ce->f && ce->f->ident == Id::dcReflect) {
        if (dcomputeReflectMatches(ce))
          stmt->ifbody->accept(this);
        else if (stmt->elsebody)
          stmt->elsebody->accept(this);
        return;
      }
    }
    DValue *cond_e = toElemDtor(stmt->condition);
    LLValue *cond_val = DtoRVal(cond_e);
    if (!cond_val->getType()->isIntegerTy(1)) {
      IF_LOG Logger::cout() << "if conditional: " << *cond_val << std::endl;
      cond_val = DtoRVal(DtoCast(stmt->loc, cond_e, Type::tbool));
    }

    // Is it constant, and is its value certainly `false` or `true`?
    // Note: It must be a simple constant for which it is sufficient to
    // determine true/false by calling `isZeroValue()`. This is not the case for
    // complex llvm::Constant such as a CompareConstantExpr where `isZeroValue()
    // == false` does not imply that the value is always `true`. See LDC issue
    // #4556.
    if (llvm::ConstantInt *const_val = llvm::dyn_cast<llvm::ConstantInt>(cond_val)) {
      Statement *executed = stmt->ifbody;
      Statement *skipped = stmt->elsebody;
      if (const_val->isZeroValue()) {
        std::swap(executed, skipped);
      }
      if (!containsLabel(skipped)) {
        IF_LOG Logger::cout() << "Constant true/false condition - elide. (cond_val: " << *cond_val << ')' << std::endl;

        if (executed) {
          irs->DBuilder.EmitBlockStart(executed->loc);
        }
        // True condition, the branch is taken so emit counter increment.
        if (!const_val->isZeroValue()) {
          PGO.emitCounterIncrement(stmt);
        }
        if (executed) {
          executed->accept(this);
          irs->DBuilder.EmitBlockEnd();
        }
        // end the dwarf lexical block
        irs->DBuilder.EmitBlockEnd();
        return;
      }
    }
    llvm::BasicBlock *ifbb = irs->insertBB("if");
    llvm::BasicBlock *endbb = irs->insertBBAfter(ifbb, "endif");
    llvm::BasicBlock *elsebb =
        stmt->elsebody ? irs->insertBBAfter(ifbb, "else") : endbb;

    auto brinstr =
        llvm::BranchInst::Create(ifbb, elsebb, cond_val, irs->scopebb());
    PGO.addBranchWeights(brinstr, brweights);

    // replace current scope
    irs->ir->SetInsertPoint(ifbb);

    // do scoped statements

    if (stmt->ifbody) {
      irs->DBuilder.EmitBlockStart(stmt->ifbody->loc);
      PGO.emitCounterIncrement(stmt);
      stmt->ifbody->accept(this);
      irs->DBuilder.EmitBlockEnd();
    }
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(endbb, irs->scopebb());
    }

    if (stmt->elsebody) {
      irs->ir->SetInsertPoint(elsebb);
      irs->DBuilder.EmitBlockStart(stmt->elsebody->loc);
      stmt->elsebody->accept(this);
      if (!irs->scopereturned()) {
        llvm::BranchInst::Create(endbb, irs->scopebb());
      }
      irs->DBuilder.EmitBlockEnd();
    }

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();

    // rewrite the scope
    irs->ir->SetInsertPoint(endbb);
    // Close the scope for the optional condition variable. This is suboptimal,
    // because the condition variable is not in scope in the else block.
    irs->funcGen().localVariableLifetimeAnnotator.popScope();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ScopeStatement *stmt) override {
    IF_LOG Logger::println("ScopeStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    if (stmt->statement) {
      irs->funcGen().localVariableLifetimeAnnotator.pushScope();
      irs->DBuilder.EmitBlockStart(stmt->statement->loc);
      stmt->statement->accept(this);
      irs->DBuilder.EmitBlockEnd();
      irs->funcGen().localVariableLifetimeAnnotator.popScope();
    }
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(WhileStatement *stmt) override {
    IF_LOG Logger::println("WhileStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);

    // create while blocks

    llvm::BasicBlock *whilebb = irs->insertBB("whilecond");
    llvm::BasicBlock *whilebodybb = irs->insertBBAfter(whilebb, "whilebody");
    llvm::BasicBlock *endbb = irs->insertBBAfter(whilebodybb, "endwhile");

    // move into the while block
    irs->ir->CreateBr(whilebb);

    // replace current scope
    irs->ir->SetInsertPoint(whilebb);

    // create the condition
    emitCoverageLinecountInc(stmt->condition->loc);
    DValue *cond_e = toElemDtor(stmt->condition);
    LLValue *cond_val = DtoRVal(DtoCast(stmt->loc, cond_e, Type::tbool));
    delete cond_e;

    // conditional branch
    auto branchinst =
        llvm::BranchInst::Create(whilebodybb, endbb, cond_val, irs->scopebb());
    {
      auto loopcount = PGO.getRegionCount(stmt);
      auto brweights =
          PGO.createProfileWeightsWhileLoop(stmt->condition, loopcount);
      PGO.addBranchWeights(branchinst, brweights);
    }

    // rewrite scope
    irs->ir->SetInsertPoint(whilebodybb);

    // while body code
    irs->funcGen().jumpTargets.pushLoopTarget(stmt, whilebb, endbb);
    PGO.emitCounterIncrement(stmt);
    if (stmt->_body) {
      stmt->_body->accept(this);
    }
    irs->funcGen().jumpTargets.popLoopTarget();

    // loop
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(whilebb, irs->scopebb());
    }

    // rewrite the scope
    irs->ir->SetInsertPoint(endbb);

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(DoStatement *stmt) override {
    IF_LOG Logger::println("DoStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    auto entryCount = PGO.setCurrentStmt(stmt);

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);

    // create while blocks
    llvm::BasicBlock *dowhilebb = irs->insertBB("dowhile");
    llvm::BasicBlock *condbb = irs->insertBBAfter(dowhilebb, "dowhilecond");
    llvm::BasicBlock *endbb = irs->insertBBAfter(condbb, "enddowhile");

    // move into the while block
    assert(!irs->scopereturned());
    llvm::BranchInst::Create(dowhilebb, irs->scopebb());

    // replace current scope
    irs->ir->SetInsertPoint(dowhilebb);

    // do-while body code
    irs->funcGen().jumpTargets.pushLoopTarget(stmt, condbb, endbb);
    PGO.emitCounterIncrement(stmt);
    if (stmt->_body) {
      stmt->_body->accept(this);
    }
    irs->funcGen().jumpTargets.popLoopTarget();

    // branch to condition block
    llvm::BranchInst::Create(condbb, irs->scopebb());
    irs->ir->SetInsertPoint(condbb);

    // create the condition
    emitCoverageLinecountInc(stmt->condition->loc);
    DValue *cond_e = toElemDtor(stmt->condition);
    LLValue *cond_val = DtoRVal(DtoCast(stmt->loc, cond_e, Type::tbool));
    delete cond_e;

    // conditional branch
    auto branchinst =
        llvm::BranchInst::Create(dowhilebb, endbb, cond_val, irs->scopebb());
    {
      // The region counter includes fallthrough from the previous statement.
      // Subtract parent count to get the true branch count of the loop
      // conditional.
      auto loopcount = PGO.getRegionCount(stmt) - entryCount;
      auto brweights =
          PGO.createProfileWeightsWhileLoop(stmt->condition, loopcount);
      PGO.addBranchWeights(branchinst, brweights);
    }

    // rewrite the scope
    irs->ir->SetInsertPoint(endbb);

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ForStatement *stmt) override {
    IF_LOG Logger::println("ForStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // start new dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);
    irs->funcGen().localVariableLifetimeAnnotator.pushScope();

    // create for blocks
    llvm::BasicBlock *forbb = irs->insertBB("forcond");
    llvm::BasicBlock *forbodybb = irs->insertBBAfter(forbb, "forbody");
    llvm::BasicBlock *forincbb = irs->insertBBAfter(forbodybb, "forinc");
    llvm::BasicBlock *endbb = irs->insertBBAfter(forincbb, "endfor");

    // init
    if (stmt->_init != nullptr) {
      stmt->_init->accept(this);
    }

    // move into the for condition block, ie. start the loop
    assert(!irs->scopereturned());
    llvm::BranchInst::Create(forbb, irs->scopebb());

    // In case of loops that have been rewritten to a composite statement
    // containing the initializers and then the actual loop, we need to
    // register the former as target scope start.
    Statement *scopeStart = stmt->getRelatedLabeled();
    while (ScopeStatement *scope = scopeStart->isScopeStatement()) {
      scopeStart = scope->statement;
    }
    irs->funcGen().jumpTargets.pushLoopTarget(scopeStart, forincbb, endbb);

    // replace current scope
    irs->ir->SetInsertPoint(forbb);

    // create the condition
    llvm::Value *cond_val;
    if (stmt->condition) {
      emitCoverageLinecountInc(stmt->condition->loc);
      DValue *cond_e = toElemDtor(stmt->condition);
      cond_val = DtoRVal(DtoCast(stmt->loc, cond_e, Type::tbool));
      delete cond_e;
    } else {
      cond_val = DtoConstBool(true);
    }

    // conditional branch
    assert(!irs->scopereturned());
    auto branchinst =
        llvm::BranchInst::Create(forbodybb, endbb, cond_val, irs->scopebb());
    {
      auto brweights = PGO.createProfileWeightsForLoop(stmt);
      PGO.addBranchWeights(branchinst, brweights);
    }

    // rewrite scope
    irs->ir->SetInsertPoint(forbodybb);

    // do for body code
    PGO.emitCounterIncrement(stmt);
    if (stmt->_body) {
      stmt->_body->accept(this);
    }

    // move into the for increment block
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(forincbb, irs->scopebb());
    }
    irs->ir->SetInsertPoint(forincbb);

    // increment
    if (stmt->increment) {
      emitCoverageLinecountInc(stmt->increment->loc);
      DValue *inc = toElemDtor(stmt->increment);
      delete inc;
    }

    // loop
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(forbb, irs->scopebb());
    }

    irs->funcGen().jumpTargets.popLoopTarget();

    // rewrite the scope
    irs->ir->SetInsertPoint(endbb);

    // end the dwarf lexical block
    irs->funcGen().localVariableLifetimeAnnotator.popScope();
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(BreakStatement *stmt) override {
    IF_LOG Logger::println("BreakStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // don't emit two terminators in a row
    // happens just before DMD generated default statements if the last case
    // terminates
    if (irs->scopereturned()) {
      return;
    }

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    if (stmt->ident) {
      IF_LOG Logger::println("ident = %s", stmt->ident->toChars());

      // Get the loop or break statement the label refers to
      Statement *targetStatement = stmt->target->statement;
      ScopeStatement *tmp;
      while ((tmp = targetStatement->isScopeStatement())) {
        targetStatement = tmp->statement;
      }

      irs->funcGen().jumpTargets.breakToStatement(targetStatement);
    } else {
      irs->funcGen().jumpTargets.breakToClosest();
    }

    // the break terminated this basicblock, start a new one
    llvm::BasicBlock *bb = irs->insertBB("afterbreak");
    irs->ir->SetInsertPoint(bb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ContinueStatement *stmt) override {
    IF_LOG Logger::println("ContinueStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    if (stmt->ident) {
      IF_LOG Logger::println("ident = %s", stmt->ident->toChars());

      // get the loop statement the label refers to
      Statement *targetLoopStatement = stmt->target->statement;
      ScopeStatement *tmp;
      while ((tmp = targetLoopStatement->isScopeStatement())) {
        targetLoopStatement = tmp->statement;
      }

      irs->funcGen().jumpTargets.continueWithLoop(targetLoopStatement);
    } else {
      irs->funcGen().jumpTargets.continueWithClosest();
    }

    // the continue terminated this basicblock, start a new one
    llvm::BasicBlock *bb = irs->insertBB("aftercontinue");
    irs->ir->SetInsertPoint(bb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ScopeGuardStatement *stmt) override {
    error(stmt->loc,
          "Internal Compiler Error: ScopeGuardStatement should have been "
          "lowered by frontend.");
    fatal();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(TryFinallyStatement *stmt) override {
    IF_LOG Logger::println("TryFinallyStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    /*auto entryCount = */ PGO.setCurrentStmt(stmt);

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    // We only need to consider exception handling/cleanup issues if there
    // is both a try and a finally block. If not, just directly emit what
    // is present.
    if (!stmt->_body || !stmt->finalbody) {
      if (stmt->_body) {
        irs->DBuilder.EmitBlockStart(stmt->_body->loc);
        stmt->_body->accept(this);
        irs->DBuilder.EmitBlockEnd();
      } else if (stmt->finalbody) {
        irs->DBuilder.EmitBlockStart(stmt->finalbody->loc);
        stmt->finalbody->accept(this);
        irs->DBuilder.EmitBlockEnd();
      }
      return;
    }

    // We'll append the "try" part to the current basic block later. No need
    // for an extra one (we'd need to branch to it unconditionally anyway).
    llvm::BasicBlock *trybb = irs->scopebb();

    llvm::BasicBlock *finallybb = irs->insertBB("finally");
    // Create a block to branch to after successfully running the try block
    // and any cleanups.
    llvm::BasicBlock *successbb =
        irs->scopereturned() ? nullptr
                             : irs->insertBBAfter(finallybb, "try.success");

    // Emit the finally block and set up the cleanup scope for it.
    irs->ir->SetInsertPoint(finallybb);
    irs->DBuilder.EmitBlockStart(stmt->finalbody->loc);
    stmt->finalbody->accept(this);
    irs->DBuilder.EmitBlockEnd();
    CleanupCursor cleanupBefore;

    // For @compute code, don't emit any exception handling as there are no
    // exceptions anyway.
    const bool computeCode = !!irs->dcomputetarget;
    if (!computeCode) {
      cleanupBefore = irs->funcGen().scopes.currentCleanupScope();
      irs->funcGen().scopes.pushCleanup(finallybb, irs->scopebb());
    }
    // Emit the try block.
    irs->ir->SetInsertPoint(trybb);

    assert(stmt->_body);
    irs->DBuilder.EmitBlockStart(stmt->_body->loc);
    stmt->_body->accept(this);
    irs->DBuilder.EmitBlockEnd();

    if (successbb) {
      if (!computeCode)
        irs->funcGen().scopes.runCleanups(cleanupBefore, successbb);
      irs->ir->SetInsertPoint(successbb);
      // PGO counter tracks the continuation of the try-finally statement
      PGO.emitCounterIncrement(stmt);
    }
    if (!computeCode)
      irs->funcGen().scopes.popCleanups(cleanupBefore);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(TryCatchStatement *stmt) override {
    IF_LOG Logger::println("TryCatchStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;
    assert(!irs->dcomputetarget);

    auto &PGO = irs->funcGen().pgo;

    // Emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    // We'll append the "try" part to the current basic block later. No need
    // for an extra one (we'd need to branch to it unconditionally anyway).
    llvm::BasicBlock *trybb = irs->scopebb();

    // Create a basic block to branch to after leaving the try or an
    // associated catch block successfully.
    llvm::BasicBlock *endbb = irs->insertBB("try.success.or.caught");

    irs->funcGen().scopes.pushTryCatch(stmt, endbb);

    // Emit the try block.
    irs->ir->SetInsertPoint(trybb);

    assert(stmt->_body);
    irs->DBuilder.EmitBlockStart(stmt->_body->loc);
    stmt->_body->accept(this);
    irs->DBuilder.EmitBlockEnd();

    if (!irs->scopereturned())
      llvm::BranchInst::Create(endbb, irs->scopebb());

    irs->funcGen().scopes.popTryCatch();

    irs->ir->SetInsertPoint(endbb);

    // PGO counter tracks the continuation of the try statement
    PGO.emitCounterIncrement(stmt);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ThrowStatement *stmt) override {
    IF_LOG Logger::println("ThrowStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;
    assert(!irs->dcomputetarget);

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // emit dwarf stop point
    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    assert(stmt->exp);
    DtoThrow(stmt->loc, toElemDtor(stmt->exp));
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(SwitchStatement *stmt) override {
    IF_LOG Logger::println("SwitchStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &funcGen = irs->funcGen();

    auto &PGO = funcGen.pgo;
    PGO.setCurrentStmt(stmt);
    const auto incomingPGORegionCount = PGO.getCurrentRegionCount();

    irs->DBuilder.EmitStopPoint(stmt->loc);
    emitCoverageLinecountInc(stmt->loc);
    llvm::BasicBlock *const oldbb = irs->scopebb();

    // The cases of the switch statement, in codegen order.
    auto cases = stmt->cases;
    const auto caseCount = cases->length;

    // llvm::Values for the case indices. Might not be llvm::Constants for
    // runtime-initialised immutable globals as case indices, in which case we
    // need to emit a `br` chain instead of `switch`.
    llvm::SmallVector<llvm::Value *, 16> indices;
    indices.reserve(caseCount);
    bool useSwitchInst = true;

    for (auto cs : *cases) {
      auto ce = cs->exp;
      if (auto ceConst = tryToConstElem(ce, irs)) {
        indices.push_back(ceConst);
      } else {
        indices.push_back(DtoRVal(toElemDtor(ce)));
        useSwitchInst = false;
      }
    }
    assert(indices.size() == caseCount);

    // body block.
    // FIXME: that block is never used
    llvm::BasicBlock *bodybb = irs->insertBB("switchbody");

    // end (break point)
    llvm::BasicBlock *endbb = irs->insertBBAfter(bodybb, "switchend");

    // default
    auto defaultTargetBB = endbb;
    if (stmt->sdefault) {
      Logger::println("has default");
      defaultTargetBB =
          funcGen.switchTargets.getOrCreate(stmt->sdefault, "default", *irs);
    }

    // do switch body
    assert(stmt->_body);
    irs->ir->SetInsertPoint(bodybb);
    funcGen.jumpTargets.pushBreakTarget(stmt, endbb);
    stmt->_body->accept(this);
    funcGen.jumpTargets.popBreakTarget();
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(endbb, irs->scopebb());
    }

    irs->ir->SetInsertPoint(oldbb);
    if (useSwitchInst) {
      // The case index value.
      LLValue *condVal = DtoRVal(toElemDtor(stmt->condition));

      // Create switch and add the cases.
      // For PGO instrumentation, we need to add counters /before/ the case
      // statement bodies, because the counters should only count the jumps
      // directly from the switch statement and not "goto default", etc.
      llvm::SwitchInst *si;
      if (!PGO.emitsInstrumentation()) {
        si = llvm::SwitchInst::Create(condVal, defaultTargetBB, caseCount,
                                      irs->scopebb());
        for (size_t i = 0; i < caseCount; ++i) {
          si->addCase(isaConstantInt(indices[i]),
                      funcGen.switchTargets.get((*cases)[i]));
        }
      } else {
        auto switchbb = irs->scopebb();
        // Add PGO instrumentation.
        // Create "default" counter bb.
        {
          llvm::BasicBlock *defaultcntr =
              irs->insertBBBefore(defaultTargetBB, "defaultcntr");
          irs->ir->SetInsertPoint(defaultcntr);
          if (stmt->sdefault)
              PGO.emitCounterIncrement(stmt->sdefault);
          llvm::BranchInst::Create(defaultTargetBB, defaultcntr);
          // Create switch
          si = llvm::SwitchInst::Create(condVal, defaultcntr, caseCount,
                                        switchbb);
        }

        // Create and add case counter bbs.
        for (size_t i = 0; i < caseCount; ++i) {
          const auto cs = (*cases)[i];
          const auto body = funcGen.switchTargets.get(cs);

          auto casecntr = irs->insertBBBefore(body, "casecntr");
          irs->ir->SetInsertPoint(casecntr);
          PGO.emitCounterIncrement(cs);
          llvm::BranchInst::Create(body, casecntr);
          si->addCase(isaConstantInt(indices[i]), casecntr);
        }
      }

      // Apply PGO switch branch weights:
      {
        // Get case statements execution counts from profile data.
        std::vector<uint64_t> case_prof_counts;
        case_prof_counts.push_back(
            stmt->sdefault ? PGO.getRegionCount(stmt->sdefault) : 0);
        for (auto cs : *cases) {
          auto w = PGO.getRegionCount(cs);
          case_prof_counts.push_back(w);
        }

        auto brweights = PGO.createProfileWeights(case_prof_counts);
        PGO.addBranchWeights(si, brweights);
      }
    } else {
      // We can't use switch, so we will use a bunch of br instructions
      // instead.

      DValue *cond = toElemDtor(stmt->condition);
      LLValue *condVal = DtoRVal(cond);

      llvm::BasicBlock *nextbb = irs->insertBBBefore(endbb, "checkcase");
      llvm::BranchInst::Create(nextbb, irs->scopebb());

      if (stmt->sdefault && PGO.emitsInstrumentation()) {
        // Prepend extra BB to "default:" to increment profiling counter.
        llvm::BasicBlock *defaultcntr =
            irs->insertBBBefore(defaultTargetBB, "defaultcntr");
        irs->ir->SetInsertPoint(defaultcntr);
        PGO.emitCounterIncrement(stmt->sdefault);
        llvm::BranchInst::Create(defaultTargetBB, defaultcntr);
        defaultTargetBB = defaultcntr;
      }

      irs->ir->SetInsertPoint(nextbb);
      auto failedCompareCount = incomingPGORegionCount;
      for (size_t i = 0; i < caseCount; ++i) {
        LLValue *cmp = irs->ir->CreateICmp(llvm::ICmpInst::ICMP_EQ, indices[i],
                                           condVal, "checkcase");
        nextbb = irs->insertBBBefore(endbb, "checkcase");

        // Add case counters for PGO in front of case body
        const auto cs = (*cases)[i];
        auto casejumptargetbb = funcGen.switchTargets.get(cs);
        if (PGO.emitsInstrumentation()) {
          llvm::BasicBlock *casecntr =
              irs->insertBBBefore(casejumptargetbb, "casecntr");
          const auto savedInsertPoint = irs->saveInsertPoint();
          irs->ir->SetInsertPoint(casecntr);
          PGO.emitCounterIncrement(cs);
          llvm::BranchInst::Create(casejumptargetbb, casecntr);
          casejumptargetbb = casecntr;
        }

        // Create the comparison branch for this case
        auto branchinst = llvm::BranchInst::Create(casejumptargetbb, nextbb,
                                                   cmp, irs->scopebb());

        // Calculate and apply PGO branch weights
        {
          auto trueCount = PGO.getRegionCount(cs);
          assert(trueCount <= failedCompareCount &&
                 "Higher branch count than switch incoming count!");
          failedCompareCount -= trueCount;
          auto brweights =
              PGO.createProfileWeights(trueCount, failedCompareCount);
          PGO.addBranchWeights(branchinst, brweights);
        }

        irs->ir->SetInsertPoint(nextbb);
      }

      llvm::BranchInst::Create(defaultTargetBB, irs->scopebb());
    }

    irs->ir->SetInsertPoint(endbb);
    // PGO counter tracks exit point of switch statement:
    PGO.emitCounterIncrement(stmt);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(CaseStatement *stmt) override {
    IF_LOG Logger::println("CaseStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &funcGen = irs->funcGen();
    auto &PGO = funcGen.pgo;
    PGO.setCurrentStmt(stmt);

    const auto body = funcGen.switchTargets.getOrCreate(stmt, "case", *irs);
    // The BB may have already been created by a `goto case` statement.
    // Move it after the current scope BB for lexical order.
    body->moveAfter(irs->scopebb());

    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(body, irs->scopebb());
    }

    irs->ir->SetInsertPoint(body);

    assert(stmt->statement);
    irs->DBuilder.EmitBlockStart(stmt->statement->loc);
    if (!isAssertFalse(stmt->statement)) {
      emitCoverageLinecountInc(stmt->loc);
    }
    if (stmt->gototarget) {
      PGO.emitCounterIncrement(PGO.getCounterPtr(stmt, 1));
    }
    stmt->statement->accept(this);
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(DefaultStatement *stmt) override {
    IF_LOG Logger::println("DefaultStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &funcGen = irs->funcGen();
    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    const auto body = funcGen.switchTargets.getOrCreate(stmt, "default", *irs);
    // The BB may have already been created.
    // Move it after the current scope BB for lexical order.
    body->moveAfter(irs->scopebb());

    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(body, irs->scopebb());
    }

    irs->ir->SetInsertPoint(body);

    assert(stmt->statement);
    irs->DBuilder.EmitBlockStart(stmt->statement->loc);
    if (!isAssertFalse(stmt->statement)) {
      emitCoverageLinecountInc(stmt->loc);
    }
    if (stmt->gototarget) {
      PGO.emitCounterIncrement(PGO.getCounterPtr(stmt, 1));
    }
    stmt->statement->accept(this);
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(UnrolledLoopStatement *stmt) override {
    IF_LOG Logger::println("UnrolledLoopStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // if no statements, there's nothing to do
    if (!stmt->statements || !stmt->statements->length) {
      return;
    }

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);

    // DMD doesn't fold stuff like continue/break, and since this isn't really a
    // loop we have to keep track of each statement and jump to the next/end
    // on continue/break

    // create end block
    llvm::BasicBlock *endbb = irs->insertBB("unrolledend");

    // create a block for each statement
    size_t nstmt = stmt->statements->length;
    llvm::SmallVector<llvm::BasicBlock *, 4> blocks(nstmt, nullptr);
    for (size_t i = 0; i < nstmt; i++)
      blocks[i] = irs->insertBBBefore(endbb, "unrolledstmt");

    // enter first stmt
    if (!irs->scopereturned()) {
      irs->ir->CreateBr(blocks[0]);
    }

    // do statements
    Statement **stmts = &(*stmt->statements)[0];

    for (size_t i = 0; i < nstmt; i++) {
      Statement *s = stmts[i];

      // get blocks
      llvm::BasicBlock *thisbb = blocks[i];
      llvm::BasicBlock *nextbb = (i + 1 == nstmt) ? endbb : blocks[i + 1];

      // update scope
      irs->ir->SetInsertPoint(thisbb);

      // push loop scope
      // continue goes to next statement, break goes to end
      irs->funcGen().jumpTargets.pushLoopTarget(stmt, nextbb, endbb);

      PGO.emitCounterIncrement(s);

      // do statement
      s->accept(this);

      // pop loop scope
      irs->funcGen().jumpTargets.popLoopTarget();

      // next stmt
      if (!irs->scopereturned()) {
        irs->ir->CreateBr(nextbb);
      }
    }

    irs->ir->SetInsertPoint(endbb);

    // PGO counter tracks the continuation after the loop
    PGO.emitCounterIncrement(stmt);

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ForeachStatement *stmt) override {
    IF_LOG Logger::println("ForeachStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);

    // assert(arguments->length == 1);
    assert(stmt->value != 0);
    assert(stmt->aggr != 0);
    assert(stmt->func != 0);

    // Argument* arg = static_cast<Argument*>(arguments->data[0]);
    // Logger::println("Argument is %s", arg->toChars());

    IF_LOG Logger::println("aggr = %s", stmt->aggr->toChars());

    // key
    LLType *keytype = stmt->key ? DtoType(stmt->key->type) : DtoSize_t();
    LLValue *keyvar;
    if (stmt->key) {
      keyvar = DtoRawVarDeclaration(stmt->key);
    } else {
      keyvar = DtoRawAlloca(keytype, 0, "foreachkey");
    }
    LLValue *zerokey = LLConstantInt::get(keytype, 0, false);

    // value
    IF_LOG Logger::println("value = %s", stmt->value->toPrettyChars());
    LLValue *valvar = nullptr;
    if (!stmt->value->isRef() && !stmt->value->isOut()) {
      // Create a local variable to serve as the value.
      DtoRawVarDeclaration(stmt->value);
      valvar = getIrLocal(stmt->value)->value;
    }

    // what to iterate
    DValue *aggrval = toElemDtor(stmt->aggr);

    // get length and pointer
    LLValue *niters = DtoArrayLen(aggrval);
    LLValue *val = DtoArrayPtr(aggrval);

    if (niters->getType() != keytype) {
      size_t sz1 = getTypeBitSize(niters->getType());
      size_t sz2 = getTypeBitSize(keytype);
      if (sz1 < sz2) {
        niters = irs->ir->CreateZExt(niters, keytype, "foreachtrunckey");
      } else if (sz1 > sz2) {
        niters = irs->ir->CreateTrunc(niters, keytype, "foreachtrunckey");
      } else {
        niters = irs->ir->CreateBitCast(niters, keytype, "foreachtrunckey");
      }
    }

    if (stmt->op == TOK::foreach_) {
      new llvm::StoreInst(zerokey, keyvar, irs->scopebb());
    } else {
      new llvm::StoreInst(niters, keyvar, irs->scopebb());
    }

    llvm::BasicBlock *condbb = irs->insertBB("foreachcond");
    llvm::BasicBlock *bodybb = irs->insertBBAfter(condbb, "foreachbody");
    llvm::BasicBlock *nextbb = irs->insertBBAfter(bodybb, "foreachnext");
    llvm::BasicBlock *endbb = irs->insertBBAfter(nextbb, "foreachend");

    llvm::BranchInst::Create(condbb, irs->scopebb());

    // condition
    irs->ir->SetInsertPoint(condbb);

    LLValue *done = nullptr;
    LLValue *load = DtoLoad(keytype, keyvar);
    if (stmt->op == TOK::foreach_) {
      done = irs->ir->CreateICmpULT(load, niters);
    } else if (stmt->op == TOK::foreach_reverse_) {
      done = irs->ir->CreateICmpUGT(load, zerokey);
      load = irs->ir->CreateSub(load, LLConstantInt::get(keytype, 1, false));
      DtoStore(load, keyvar);
    }
    auto branchinst =
        llvm::BranchInst::Create(bodybb, endbb, done, irs->scopebb());
    {
      auto brweights = PGO.createProfileWeightsForeach(stmt);
      PGO.addBranchWeights(branchinst, brweights);
    }

    // init body
    irs->ir->SetInsertPoint(bodybb);
    PGO.emitCounterIncrement(stmt);

    // get value for this iteration
    LLValue *loadedKey = DtoLoad(keytype, keyvar);
    LLValue *gep = DtoGEP1(DtoMemType(aggrval->type->nextOf()), val, loadedKey);

    if (!stmt->value->isRef() && !stmt->value->isOut()) {
      // Copy value to local variable, and use it as the value variable.
      DLValue dst(stmt->value->type, valvar);
      DLValue src(stmt->value->type, gep);
      DtoAssign(stmt->loc, &dst, &src, EXP::assign);
      getIrLocal(stmt->value)->value = valvar;
    } else {
      // Use the GEP as the address of the value variable.
      DtoRawVarDeclaration(stmt->value, gep);
    }

    // emit body
    irs->funcGen().jumpTargets.pushLoopTarget(stmt, nextbb, endbb);
    if (stmt->_body) {
      stmt->_body->accept(this);
    }
    irs->funcGen().jumpTargets.popLoopTarget();

    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(nextbb, irs->scopebb());
    }

    // next
    irs->ir->SetInsertPoint(nextbb);
    if (stmt->op == TOK::foreach_) {
      LLValue *load = DtoLoad(keytype, keyvar);
      load = irs->ir->CreateAdd(load, LLConstantInt::get(keytype, 1, false));
      DtoStore(load, keyvar);
    }
    llvm::BranchInst::Create(condbb, irs->scopebb());

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();

    // end
    irs->ir->SetInsertPoint(endbb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ForeachRangeStatement *stmt) override {
    IF_LOG Logger::println("ForeachRangeStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // start a dwarf lexical block
    irs->DBuilder.EmitBlockStart(stmt->loc);

    // evaluate lwr/upr
    assert(stmt->lwr->type->isintegral());
    LLValue *lower = DtoRVal(toElemDtor(stmt->lwr));
    assert(stmt->upr->type->isintegral());
    LLValue *upper = DtoRVal(toElemDtor(stmt->upr));

    // handle key
    assert(stmt->key->type->isintegral());
    LLValue *keyval  = DtoRawVarDeclaration(stmt->key);
    LLType  *keytype = DtoType(stmt->key->type);
    // store initial value in key
    if (stmt->op == TOK::foreach_) {
      DtoStore(lower, keyval);
    } else {
      DtoStore(upper, keyval);
    }

    // set up the block we'll need
    llvm::BasicBlock *condbb = irs->insertBB("foreachrange_cond");
    llvm::BasicBlock *bodybb = irs->insertBBAfter(condbb, "foreachrange_body");
    llvm::BasicBlock *nextbb = irs->insertBBAfter(bodybb, "foreachrange_next");
    llvm::BasicBlock *endbb = irs->insertBBAfter(nextbb, "foreachrange_end");

    // jump to condition
    llvm::BranchInst::Create(condbb, irs->scopebb());

    // CONDITION
    irs->ir->SetInsertPoint(condbb);

    // first we test that lwr < upr
    lower = DtoLoad(keytype, keyval);
    assert(lower->getType() == upper->getType());
    llvm::ICmpInst::Predicate cmpop;
    if (isLLVMUnsigned(stmt->key->type)) {
      cmpop = (stmt->op == TOK::foreach_) ? llvm::ICmpInst::ICMP_ULT
                                          : llvm::ICmpInst::ICMP_UGT;
    } else {
      cmpop = (stmt->op == TOK::foreach_) ? llvm::ICmpInst::ICMP_SLT
                                          : llvm::ICmpInst::ICMP_SGT;
    }
    LLValue *cond = irs->ir->CreateICmp(cmpop, lower, upper);

    // jump to the body if range is ok, to the end if not
    auto branchinst =
        llvm::BranchInst::Create(bodybb, endbb, cond, irs->scopebb());
    {
      auto brweights = PGO.createProfileWeightsForeachRange(stmt);
      PGO.addBranchWeights(branchinst, brweights);
    }

    // BODY
    irs->ir->SetInsertPoint(bodybb);
    PGO.emitCounterIncrement(stmt);

    // reverse foreach decrements here
    if (stmt->op == TOK::foreach_reverse_) {
      LLValue *v = DtoLoad(keytype, keyval);
      LLValue *one = LLConstantInt::get(v->getType(), 1, false);
      v = irs->ir->CreateSub(v, one);
      DtoStore(v, keyval);
    }

    // emit body
    irs->funcGen().jumpTargets.pushLoopTarget(stmt, nextbb, endbb);
    if (stmt->_body) {
      stmt->_body->accept(this);
    }
    irs->funcGen().jumpTargets.popLoopTarget();

    // jump to next iteration
    if (!irs->scopereturned()) {
      llvm::BranchInst::Create(nextbb, irs->scopebb());
    }

    // NEXT
    irs->ir->SetInsertPoint(nextbb);

    // forward foreach increments here
    if (stmt->op == TOK::foreach_) {
      LLValue *v = DtoLoad(keytype, keyval);
      LLValue *one = LLConstantInt::get(v->getType(), 1, false);
      v = irs->ir->CreateAdd(v, one);
      DtoStore(v, keyval);
    }

    // jump to condition
    llvm::BranchInst::Create(condbb, irs->scopebb());

    // end the dwarf lexical block
    irs->DBuilder.EmitBlockEnd();

    // END
    irs->ir->SetInsertPoint(endbb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(LabelStatement *stmt) override {
    IF_LOG Logger::println("LabelStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    // if it's an inline asm label, we don't create a basicblock, just emit it
    // in the asm
    if (irs->asmBlock) {
      auto a = new IRAsmStmt;
      std::stringstream label;
      printLabelName(label, mangleExact(irs->func()->decl),
                     stmt->ident->toChars());
      label << ":";
      a->code = label.str();
      irs->asmBlock->s.push_back(a);
      irs->asmBlock->internalLabels.push_back(stmt->ident);

      // disable inlining
      irs->func()->setNeverInline();
    } else {
      llvm::BasicBlock *labelBB =
          irs->insertBB(llvm::Twine("label.") + stmt->ident->toChars());
      irs->funcGen().jumpTargets.addLabelTarget(stmt->ident, labelBB);

      if (!irs->scopereturned()) {
        llvm::BranchInst::Create(labelBB, irs->scopebb());
      }

      irs->ir->SetInsertPoint(labelBB);
    }

    PGO.emitCounterIncrement(stmt);
    // statement == nullptr when the label is at the end of function
    if (stmt->statement) {
      stmt->statement->accept(this);
    }
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(GotoStatement *stmt) override {
    IF_LOG Logger::println("GotoStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    DtoGoto(stmt->loc, stmt->label);

    // TODO: Should not be needed.
    llvm::BasicBlock *bb = irs->insertBB("aftergoto");
    irs->ir->SetInsertPoint(bb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(GotoDefaultStatement *stmt) override {
    IF_LOG Logger::println("GotoDefaultStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &funcGen = irs->funcGen();
    auto &PGO = funcGen.pgo;
    PGO.setCurrentStmt(stmt);

    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    assert(!irs->scopereturned());

    const auto defaultBB = funcGen.switchTargets.get(stmt->sw->sdefault);
    llvm::BranchInst::Create(defaultBB, irs->scopebb());

    // TODO: Should not be needed.
    llvm::BasicBlock *bb = irs->insertBB("aftergotodefault");
    irs->ir->SetInsertPoint(bb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(GotoCaseStatement *stmt) override {
    IF_LOG Logger::println("GotoCaseStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;

    auto &funcGen = irs->funcGen();
    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    irs->DBuilder.EmitStopPoint(stmt->loc);

    emitCoverageLinecountInc(stmt->loc);

    assert(!irs->scopereturned());

    const auto caseBB =
        funcGen.switchTargets.getOrCreate(stmt->cs, "goto_case", *irs);
    llvm::BranchInst::Create(caseBB, irs->scopebb());

    // TODO: Should not be needed.
    llvm::BasicBlock *bb = irs->insertBB("aftergotocase");
    irs->ir->SetInsertPoint(bb);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(WithStatement *stmt) override {
    IF_LOG Logger::println("WithStatement::toIR(): %s", stmt->loc.toChars());
    LOG_SCOPE;

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    irs->DBuilder.EmitBlockStart(stmt->loc);

    assert(stmt->exp);

    // with(..) can either be used with expressions or with symbols
    // wthis == null indicates the symbol form
    if (stmt->wthis) {
      LLValue *mem = DtoRawVarDeclaration(stmt->wthis);
      DValue *e = toElemDtor(stmt->exp);
      LLValue *val = (DtoIsInMemoryOnly(e->type) ? DtoLVal(e) : DtoRVal(e));
      DtoStore(val, mem);
    }

    if (stmt->_body) {
      stmt->_body->accept(this);
    }

    irs->DBuilder.EmitBlockEnd();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(SwitchErrorStatement *stmt) override {
    IF_LOG Logger::println("SwitchErrorStatement::toIR(): %s",
                           stmt->loc.toChars());
    LOG_SCOPE;
    assert(!irs->dcomputetarget);

    auto &PGO = irs->funcGen().pgo;
    PGO.setCurrentStmt(stmt);

    if (global.params.checkAction == CHECKACTION_C) {
      auto module = irs->func()->decl->getModule();
      DtoCAssert(module, stmt->loc, DtoConstCString("no switch default"));
      return;
    }

    // `stmt->exp` is a CallExpression to `object.__switch_error!()`
    assert(stmt->exp);
    toElemDtor(stmt->exp);

    gIR->ir->CreateUnreachable();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(InlineAsmStatement *stmt) override {
    assert(!irs->dcomputetarget);
    AsmStatement_toIR(stmt, irs);
  }

  void visit(GccAsmStatement *stmt) override {
    assert(!irs->dcomputetarget);
    GccAsmStatement_toIR(stmt, irs);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(CompoundAsmStatement *stmt) override {
    assert(!irs->dcomputetarget);
    CompoundAsmStatement_toIR(stmt, irs);
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(ImportStatement *stmt) override {
    for (auto s : *stmt->imports) {
      assert(s->isImport());
      irs->DBuilder.EmitImport(static_cast<Import *>(s));
    }
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(Statement *stmt) override {
    error(stmt->loc, "Statement type Statement not implemented: `%s`",
          stmt->toChars());
    fatal();
  }

  //////////////////////////////////////////////////////////////////////////

  void visit(PragmaStatement *stmt) override {
    error(stmt->loc, "Statement type PragmaStatement not implemented: `%s`",
          stmt->toChars());
    fatal();
  }
};

//////////////////////////////////////////////////////////////////////////////

void Statement_toIR(Statement *s, IRState *irs) {
  ToIRVisitor v(irs);
  s->accept(&v);
}
