view libinterp/corefcn/pt-jit.cc @ 20654:b65888ec820e draft default tip gccjit

dmalcom gcc jit import
author Stefan Mahr <dac922@gmx.de>
date Fri, 27 Feb 2015 16:59:36 +0100
parents d35201e5ce5d
children
line wrap: on
line source

/*

Copyright (C) 2012-2015 Max Brister

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<http://www.gnu.org/licenses/>.

*/

// Author: Max Brister <max@2bass.com>

#define __STDC_LIMIT_MACROS
#define __STDC_CONSTANT_MACROS

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "debug.h"
#include "defun.h"
#include "ov.h"
#include "pt-all.h"
#include "pt-jit.h"
#include "sighandlers.h"
#include "symtab.h"
#include "variables.h"

#ifdef HAVE_JIT

static bool Vdebug_jit = false;

static bool Vjit_enable = false;

static int Vjit_startcnt = 1000;

static int Vjit_failcnt = 0;

#ifdef HAVE_LLVM
#include <llvm/Analysis/CallGraph.h>
#include <llvm/Analysis/Passes.h>

#ifdef HAVE_LLVM_IR_VERIFIER_H
#include <llvm/IR/Verifier.h>
#else
#include <llvm/Analysis/Verifier.h>
#endif

#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/JIT.h>

#ifdef LEGACY_PASSMANAGER
#include <llvm/IR/LegacyPassManager.h>
#else
#include <llvm/PassManager.h>
#endif

#ifdef HAVE_LLVM_IR_FUNCTION_H
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#else
#include <llvm/LLVMContext.h>
#include <llvm/Module.h>
#endif

#ifdef HAVE_LLVM_SUPPORT_IRBUILDER_H
#include <llvm/Support/IRBuilder.h>
#elif defined(HAVE_LLVM_IR_IRBUILDER_H)
#include <llvm/IR/IRBuilder.h>
#else
#include <llvm/IRBuilder.h>
#endif

#include <llvm/Support/raw_os_ostream.h>
#include <llvm/Support/TargetSelect.h>

#ifdef HAVE_LLVM_IR_DATALAYOUT_H
#include <llvm/IR/DataLayout.h>
#elif defined(HAVE_LLVM_DATALAYOUT_H)
#include <llvm/DataLayout.h>
#else
#include <llvm/Target/TargetData.h>
#endif

#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/Scalar.h>

static llvm::IRBuilder<> builder (llvm::getGlobalContext ());

static llvm::LLVMContext& context = llvm::getGlobalContext ();

#endif /* ifdef HAVE_LLVM */

// -------------------- jit_break_exception --------------------

// jit_break is thrown whenever a branch we are converting has only breaks or
// continues. This is because all code that follows a break or continue is dead.
class jit_break_exception : public std::exception {};

// -------------------- jit_convert --------------------
jit_convert::jit_convert (tree &tee, jit_type *for_bounds)
  : converting_function (false)
{
  initialize (symbol_table::current_scope ());

  if (for_bounds)
    create_variable (next_for_bounds (false), for_bounds);

  try
    {
      visit (tee);
    }
  catch (const jit_break_exception&)
    { }

  // breaks must have been handled by the top level loop
  assert (breaks.empty ());
  assert (continues.empty ());

  block->append (factory.create<jit_branch> (final_block));
  blocks.push_back (final_block);

  for (variable_map::iterator iter = vmap.begin (); iter != vmap.end (); ++iter)
    {
      jit_variable *var = iter->second;
      const std::string& name = var->name ();
      if (name.size () && name[0] != '#')
        final_block->append (factory.create<jit_store_argument> (var));
    }

  final_block->append (factory.create<jit_return> ());
}

jit_convert::jit_convert (octave_user_function& fcn,
                          const std::vector<jit_type *>& args)
  : converting_function (true)
{
  initialize (fcn.scope ());

  tree_parameter_list *plist = fcn.parameter_list ();
  tree_parameter_list *rlist = fcn.return_list ();
  if (plist && plist->takes_varargs ())
    throw jit_fail_exception ("varags not supported");

  if (rlist && (rlist->size () > 1 || rlist->takes_varargs ()))
    throw jit_fail_exception ("multiple returns not supported");

  if (plist)
    {
      tree_parameter_list::iterator piter = plist->begin ();
      for (size_t i = 0; i < args.size (); ++i, ++piter)
        {
          if (piter == plist->end ())
            throw jit_fail_exception ("Too many parameter to function");

          tree_decl_elt *elt = *piter;
          std::string name = elt->name ();
          create_variable (name, args[i]);
        }
    }

  jit_value *return_value = 0;
  bool all_breaking = false;
  if (fcn.is_special_expr ())
    {
      tree_expression *expr = fcn.special_expr ();
      if (expr)
        {
          jit_variable *retvar = get_variable ("#return");
          jit_value *retval = 0;
          try
            {
              retval = visit (expr);
            }
          catch (const jit_break_exception&)
            { }

          if (breaks.size () || continues.size ())
            throw jit_fail_exception ("break/continue not supported in "
                                      "anonymous functions");

          block->append (factory.create<jit_assign> (retvar, retval));
          return_value = retvar;
        }
    }
  else
    {
      try
        {
          visit_statement_list (*fcn.body ());
        }
      catch (const jit_break_exception&)
        {
          all_breaking = true;
        }

      // the user may use break or continue to exit the function
      finish_breaks (final_block, continues);
      finish_breaks (final_block, breaks);
    }

  if (! all_breaking)
    block->append (factory.create<jit_branch> (final_block));

  blocks.push_back (final_block);
  block = final_block;

  if (! return_value && rlist && rlist->size () == 1)
    {
      tree_decl_elt *elt = rlist->front ();
      return_value = get_variable (elt->name ());
    }

  // FIXME: We should use live range analysis to delete variables where needed.
  // For now we just delete everything at the end of the function.
  for (variable_map::iterator iter = vmap.begin (); iter != vmap.end (); ++iter)
    {
      if (iter->second != return_value)
        {
          jit_call *call;
          call = factory.create<jit_call> (&jit_typeinfo::destroy,
                                           iter->second);
          final_block->append (call);
        }
    }

  if (return_value)
    final_block->append (factory.create<jit_return> (return_value));
  else
    final_block->append (factory.create<jit_return> ());
}

void
jit_convert::visit_anon_fcn_handle (tree_anon_fcn_handle&)
{
  throw jit_fail_exception ("No visit_anon_fcn_handle implementation");
}

void
jit_convert::visit_argument_list (tree_argument_list&)
{
  throw jit_fail_exception ("No visit_argument_list implementation");
}

void
jit_convert::visit_binary_expression (tree_binary_expression& be)
{
  if (be.op_type () >= octave_value::num_binary_ops)
    {
      tree_boolean_expression *boole;
      boole = dynamic_cast<tree_boolean_expression *> (&be);
      assert (boole);
      bool is_and = boole->op_type () == tree_boolean_expression::bool_and;

      std::string short_name = next_shortcircut_result ();
      jit_variable *short_result = factory.create<jit_variable> (short_name);
      vmap[short_name] = short_result;

      jit_block *done = factory.create<jit_block> (block->name ());
      tree_expression *lhs = be.lhs ();
      jit_value *lhsv = visit (lhs);
      lhsv = create_checked (&jit_typeinfo::logically_true, lhsv);

      jit_block *short_early = factory.create<jit_block> ("short_early");
      blocks.push_back (short_early);

      jit_block *short_cont = factory.create<jit_block> ("short_cont");

      if (is_and)
        block->append (factory.create<jit_cond_branch> (lhsv, short_cont,
                                                        short_early));
      else
        block->append (factory.create<jit_cond_branch> (lhsv, short_early,
                                                        short_cont));

      block = short_early;

      jit_value *early_result = factory.create<jit_const_bool> (! is_and);
      block->append (factory.create<jit_assign> (short_result, early_result));
      block->append (factory.create<jit_branch> (done));

      blocks.push_back (short_cont);
      block = short_cont;

      tree_expression *rhs = be.rhs ();
      jit_value *rhsv = visit (rhs);
      rhsv = create_checked (&jit_typeinfo::logically_true, rhsv);
      block->append (factory.create<jit_assign> (short_result, rhsv));
      block->append (factory.create<jit_branch> (done));

      blocks.push_back (done);
      block = done;
      result = short_result;
    }
  else
    {
      tree_expression *lhs = be.lhs ();
      jit_value *lhsv = visit (lhs);

      tree_expression *rhs = be.rhs ();
      jit_value *rhsv = visit (rhs);

      const jit_operation& fn = jit_typeinfo::binary_op (be.op_type ());
      result = create_checked (fn, lhsv, rhsv);
    }
}

void
jit_convert::visit_break_command (tree_break_command&)
{
  breaks.push_back (block);
  throw jit_break_exception ();
}

void
jit_convert::visit_colon_expression (tree_colon_expression& expr)
{
  // in the futher we need to add support for classes and deal with rvalues
  jit_value *base = visit (expr.base ());
  jit_value *limit = visit (expr.limit ());
  jit_value *increment;
  tree_expression *tinc = expr.increment ();

  if (tinc)
    increment = visit (tinc);
  else
    increment = factory.create<jit_const_scalar> (1);

  result = block->append (factory.create<jit_call> (jit_typeinfo::make_range,
                                                    base, limit, increment));
}

void
jit_convert::visit_continue_command (tree_continue_command&)
{
  continues.push_back (block);
  throw jit_break_exception ();
}

void
jit_convert::visit_global_command (tree_global_command&)
{
  throw jit_fail_exception ("No visit_global_command implemenation");
}

void
jit_convert::visit_persistent_command (tree_persistent_command&)
{
  throw jit_fail_exception ("No visit_persistent_command implementation");
}

void
jit_convert::visit_decl_elt (tree_decl_elt&)
{
  throw jit_fail_exception ("No visit_decl_elt implementation");
}

void
jit_convert::visit_decl_init_list (tree_decl_init_list&)
{
  throw jit_fail_exception ("No visit_decl_init_list implementation");
}

void
jit_convert::visit_simple_for_command (tree_simple_for_command& cmd)
{
  // Note we do an initial check to see if the loop will run atleast once.
  // This allows us to get better type inference bounds on variables defined
  // and used only inside the for loop (e.g. the index variable)

  // If we are a nested for loop we need to store the previous breaks
  unwind_protect prot;
  prot.protect_var (breaks);
  prot.protect_var (continues);
  breaks.clear ();
  continues.clear ();

  // we need a variable for our iterator, because it is used in multiple blocks
  std::string iter_name = next_iterator ();
  jit_variable *iterator = factory.create<jit_variable> (iter_name);
  factory.create<jit_variable> (iter_name);
  vmap[iter_name] = iterator;

  jit_block *body = factory.create<jit_block> ("for_body");
  jit_block *tail = factory.create<jit_block> ("for_tail");

  // do control expression, iter init, and condition check in prev_block (block)
  // if we are the top level for loop, the bounds is an input argument.
  jit_value *control = find_variable (next_for_bounds ());
  if (! control)
    control = visit (cmd.control_expr ());
  jit_call *init_iter = factory.create<jit_call> (jit_typeinfo::for_init,
                                                  control);
  block->append (init_iter);
  block->append (factory.create<jit_assign> (iterator, init_iter));

  jit_call *check = factory.create<jit_call> (jit_typeinfo::for_check, control,
                                              iterator);
  block->append (check);
  block->append (factory.create<jit_cond_branch> (check, body, tail));

  blocks.push_back (body);
  block = body;

  // compute the syntactical iterator
  jit_call *idx_rhs = factory.create<jit_call> (jit_typeinfo::for_index,
                                                control, iterator);
  block->append (idx_rhs);
  do_assign (cmd.left_hand_side (), idx_rhs);

  // do loop
  tree_statement_list *pt_body = cmd.body ();
  bool all_breaking = false;
  try
    {
      pt_body->accept (*this);
    }
  catch (const jit_break_exception&)
    {
      if (continues.empty ())
        {
          // WTF are you doing user? Every branch was a break, why did you have
          // a loop??? Users are silly people...
          finish_breaks (tail, breaks);
          blocks.push_back (tail);
          block = tail;
          return;
        }

      all_breaking = true;
    }

  // check our condition, continues jump to this block
  jit_block *check_block = factory.create<jit_block> ("for_check");
  blocks.push_back (check_block);

  jit_block *interrupt_check = factory.create<jit_block> ("for_interrupt");
  blocks.push_back (interrupt_check);

  if (! all_breaking)
    block->append (factory.create<jit_branch> (check_block));
  finish_breaks (check_block, continues);

  block = check_block;
  const jit_operation& add_fn = jit_typeinfo::binary_op (octave_value::op_add);
  jit_value *one = factory.create<jit_const_index> (1);
  jit_call *iter_inc = factory.create<jit_call> (add_fn, iterator, one);
  block->append (iter_inc);
  block->append (factory.create<jit_assign> (iterator, iter_inc));
  check = block->append (factory.create<jit_call> (jit_typeinfo::for_check,
                                                   control, iterator));
  block->append (factory.create<jit_cond_branch> (check, interrupt_check,
                                                  tail));

  block = interrupt_check;
  jit_error_check *ec
    = factory.create<jit_error_check> (jit_error_check::var_interrupt,
                                       body, final_block);
  block->append (ec);

  // breaks will go to our tail
  blocks.push_back (tail);
  finish_breaks (tail, breaks);
  block = tail;
}

void
jit_convert::visit_complex_for_command (tree_complex_for_command&)
{
  throw jit_fail_exception ("No visit_complex_for_command implementation");
}

void
jit_convert::visit_octave_user_script (octave_user_script&)
{
  throw jit_fail_exception ("No visit_octave_user_script implementation");
}

void
jit_convert::visit_octave_user_function (octave_user_function&)
{
  throw jit_fail_exception ("No visit_octave_user_function implementation");
}

void
jit_convert::visit_octave_user_function_header (octave_user_function&)
{
  throw jit_fail_exception ("No visit_octave_user_function_header implementation");
}

void
jit_convert::visit_octave_user_function_trailer (octave_user_function&)
{
  throw jit_fail_exception ("No visit_octave_user_function_trailer implementation");
}

void
jit_convert::visit_function_def (tree_function_def&)
{
  throw jit_fail_exception ("No visit_function_def implementation");
}

void
jit_convert::visit_identifier (tree_identifier& ti)
{
  if (ti.has_magic_end ())
    {
      if (!end_context.size ())
        throw jit_fail_exception ("Illegal end");
      result = block->append (factory.create<jit_magic_end> (end_context));
    }
  else
    {
      jit_variable *var = get_variable (ti.name ());
      jit_instruction *instr;
      instr = factory.create<jit_call> (&jit_typeinfo::grab, var);
      result = block->append (instr);
    }
}

void
jit_convert::visit_if_clause (tree_if_clause&)
{
  throw jit_fail_exception ("No visit_if_clause implementation");
}

void
jit_convert::visit_if_command (tree_if_command& cmd)
{
  tree_if_command_list *lst = cmd.cmd_list ();
  assert (lst); // jwe: Can this be null?
  lst->accept (*this);
}

void
jit_convert::visit_if_command_list (tree_if_command_list& lst)
{
  tree_if_clause *last = lst.back ();
  size_t last_else = static_cast<size_t> (last->is_else_clause ());

  // entry_blocks represents the block you need to enter in order to execute
  // the condition check for the ith clause. For the else, it is simple the
  // else body. If there is no else body, then it is padded with the tail
  std::vector<jit_block *> entry_blocks (lst.size () + 1 - last_else);
  entry_blocks[0] = block;

  // we need to construct blocks first, because they have jumps to eachother
  tree_if_command_list::iterator iter = lst.begin ();
  ++iter;
  for (size_t i = 1; iter != lst.end (); ++iter, ++i)
    {
      tree_if_clause *tic = *iter;
      if (tic->is_else_clause ())
        entry_blocks[i] = factory.create<jit_block> ("else");
      else
        entry_blocks[i] = factory.create<jit_block> ("ifelse_cond");
    }

  jit_block *tail = factory.create<jit_block> ("if_tail");
  if (! last_else)
    entry_blocks[entry_blocks.size () - 1] = tail;


  // each branch in the if statement will have different breaks/continues
  block_list current_breaks = breaks;
  block_list current_continues = continues;
  breaks.clear ();
  continues.clear ();

  size_t num_incomming = 0; // number of incomming blocks to our tail
  iter = lst.begin ();
  for (size_t i = 0; iter != lst.end (); ++iter, ++i)
    {
      tree_if_clause *tic = *iter;
      block = entry_blocks[i];
      assert (block);

      if (i) // the first block is prev_block, so it has already been added
        blocks.push_back (entry_blocks[i]);

      if (! tic->is_else_clause ())
        {
          tree_expression *expr = tic->condition ();
          jit_value *cond = visit (expr);
          jit_call *check = create_checked (&jit_typeinfo::logically_true,
                                            cond);
          jit_block *body = factory.create<jit_block> (i == 0 ? "if_body"
                                                              : "ifelse_body");
          blocks.push_back (body);

          jit_instruction *br = factory.create<jit_cond_branch> (check, body,
                                                        entry_blocks[i + 1]);
          block->append (br);
          block = body;
        }

      tree_statement_list *stmt_lst = tic->commands ();
      assert (stmt_lst); // jwe: Can this be null?

      try
        {
          stmt_lst->accept (*this);
          ++num_incomming;
          block->append (factory.create<jit_branch> (tail));
        }
      catch (const jit_break_exception&)
        { }

      current_breaks.splice (current_breaks.end (), breaks);
      current_continues.splice (current_continues.end (), continues);
    }

  breaks.splice (breaks.end (), current_breaks);
  continues.splice (continues.end (), current_continues);

  if (num_incomming || ! last_else)
    {
      blocks.push_back (tail);
      block = tail;
    }
  else
    // every branch broke, so we don't have a tail
    throw jit_break_exception ();
}

void
jit_convert::visit_index_expression (tree_index_expression& exp)
{
  result = resolve (exp);
}

void
jit_convert::visit_matrix (tree_matrix&)
{
  throw jit_fail_exception ("No visit_matrix implementation");
}

void
jit_convert::visit_cell (tree_cell&)
{
  throw jit_fail_exception ("No visit_cell implementation");
}

void
jit_convert::visit_multi_assignment (tree_multi_assignment&)
{
  throw jit_fail_exception ("No visit_multi_assignment implementation");
}

void
jit_convert::visit_no_op_command (tree_no_op_command&)
{
  throw jit_fail_exception ("No visit_no_op_command implementation");
}

void
jit_convert::visit_constant (tree_constant& tc)
{
  octave_value v = tc.rvalue1 ();
  jit_type *ty = jit_typeinfo::type_of (v);

  if (ty == jit_typeinfo::get_scalar ())
    {
      double dv = v.double_value ();
      result = factory.create<jit_const_scalar> (dv);
    }
  else if (ty == jit_typeinfo::get_range ())
    {
      Range rv = v.range_value ();
      result = factory.create<jit_const_range> (rv);
    }
  else if (ty == jit_typeinfo::get_complex ())
    {
      Complex cv = v.complex_value ();
      result = factory.create<jit_const_complex> (cv);
    }
  else
    throw jit_fail_exception ("Unknown constant");
}

void
jit_convert::visit_fcn_handle (tree_fcn_handle&)
{
  throw jit_fail_exception ("No visit_fcn_handle implementation");
}

void
jit_convert::visit_funcall (tree_funcall&)
{
  throw jit_fail_exception ();
}

void
jit_convert::visit_parameter_list (tree_parameter_list&)
{
  throw jit_fail_exception ("No visit_parameter_list implementation");
}

void
jit_convert::visit_postfix_expression (tree_postfix_expression& tpe)
{
  octave_value::unary_op etype = tpe.op_type ();
  tree_expression *operand = tpe.operand ();
  jit_value *operandv = visit (operand);

  const jit_operation& fn = jit_typeinfo::unary_op (etype);
  result = create_checked (fn, operandv);

  if (etype == octave_value::op_incr || etype == octave_value::op_decr)
    {
      jit_value *ret = create_checked (&jit_typeinfo::grab, operandv);
      do_assign (operand, result);
      result = ret;
    }
}

void
jit_convert::visit_prefix_expression (tree_prefix_expression& tpe)
{
  octave_value::unary_op etype = tpe.op_type ();
  tree_expression *operand = tpe.operand ();
  const jit_operation& fn = jit_typeinfo::unary_op (etype);
  result = create_checked (fn, visit (operand));

  if (etype == octave_value::op_incr || etype == octave_value::op_decr)
    do_assign (operand, result);
}

void
jit_convert::visit_return_command (tree_return_command&)
{
  throw jit_fail_exception ("No visit_return_command implementation");
}

void
jit_convert::visit_return_list (tree_return_list&)
{
  throw jit_fail_exception ("No visit_return_list implementation");
}

void
jit_convert::visit_simple_assignment (tree_simple_assignment& tsa)
{
  tree_expression *rhs = tsa.right_hand_side ();
  jit_value *rhsv = visit (rhs);
  octave_value::assign_op op = tsa.op_type ();

  if (op != octave_value::op_asn_eq)
    {
      // do the equivlent binary operation, then assign. This is always correct,
      // but isn't always optimal.
      tree_expression *lhs = tsa.left_hand_side ();
      jit_value *lhsv = visit (lhs);
      octave_value::binary_op bop = octave_value::assign_op_to_binary_op (op);
      const jit_operation& fn = jit_typeinfo::binary_op (bop);
      rhsv = create_checked (fn, lhsv, rhsv);
    }

  result = do_assign (tsa.left_hand_side (), rhsv);
}

void
jit_convert::visit_statement (tree_statement& stmt)
{
  tree_command *cmd = stmt.command ();
  tree_expression *expr = stmt.expression ();

  if (cmd)
    visit (cmd);
  else
    {
      // stolen from tree_evaluator::visit_statement
      bool do_bind_ans = false;

      if (expr->is_identifier ())
        {
          tree_identifier *id = dynamic_cast<tree_identifier *> (expr);

          do_bind_ans = (! id->is_variable ());
        }
      else
        do_bind_ans = (! expr->is_assignment_expression ());

      jit_value *expr_result = visit (expr);

      if (do_bind_ans)
        do_assign ("ans", expr_result, expr->print_result ());
      else if (expr->is_identifier () && expr->print_result ())
        {
          // FIXME: ugly hack, we need to come up with a way to pass
          // nargout to visit_identifier
          const jit_operation& fn = jit_typeinfo::print_value ();
          jit_const_string *name = factory.create<jit_const_string>
                                    (expr->name ());
          block->append (factory.create<jit_call> (fn, name, expr_result));
        }
    }
}

void
jit_convert::visit_statement_list (tree_statement_list& lst)
{
  for (tree_statement_list::iterator iter = lst.begin (); iter != lst.end();
       ++iter)
    {
      tree_statement *elt = *iter;
      // jwe: Can this ever be null?
      assert (elt);
      elt->accept (*this);
    }
}

void
jit_convert::visit_switch_case (tree_switch_case&)
{
  throw jit_fail_exception ("No visit_switch_case implementation");
}

void
jit_convert::visit_switch_case_list (tree_switch_case_list&)
{
  throw jit_fail_exception ("No visit_switch_case_list implementation");
}

void
jit_convert::visit_switch_command (tree_switch_command& cmd)
{
  tree_switch_case_list *lst = cmd.case_list ();

  // always visit switch expression
  tree_expression *expr = cmd.switch_value ();
  assert (expr && "Switch value can not be null");
  jit_value *value = visit (expr);
  assert (value);

  size_t case_blocks_num = lst->size ();

  if (! case_blocks_num)  // there's nothing to do
    return;

  // check for otherwise, it's interpreted as last 'else' condition
  size_t has_otherwise = 0;
  tree_switch_case *last = lst->back ();
  if (last->is_default_case ())
    has_otherwise = 1;

  std::vector<jit_block *> entry_blocks (case_blocks_num + 1 - has_otherwise);

  // the first entry point is always the actual block. afterward new blocks
  // are created for every case and the otherwise branch
  entry_blocks[0] = block;
  for (size_t i = 1; i < case_blocks_num; ++i)
    entry_blocks[i] = factory.create<jit_block> ("case_cond");

  jit_block *tail = factory.create<jit_block> ("switch_tail");

  // if there's no otherwise branch, the the 'else' of the last branch
  // has to point to the tail
  if (! has_otherwise)
    entry_blocks[entry_blocks.size()-1] = tail;

  // each branch in the case statement will have different breaks/continues
  block_list current_breaks = breaks;
  block_list current_continues = continues;
  breaks.clear ();
  continues.clear ();

  size_t num_incomming = 0; // number of incomming blocks to our tail

  tree_switch_case_list::iterator iter = lst->begin ();
  for (size_t i = 0; i < case_blocks_num; ++iter, ++i)
    {
      tree_switch_case *twc = *iter;
      block = entry_blocks[i]; // case_cond
      assert (block);

      if (i)
        blocks.push_back (entry_blocks[i]);  // first block already pushed

      if (! twc->is_default_case ())
        {
          // compare result of switch expression with actual case label
          tree_expression *te = twc->case_label ();
          jit_value *label = visit (te);
          assert(label);

          const jit_operation& fn = jit_typeinfo::binary_op (octave_value::op_eq);
          jit_value *cond = create_checked (fn, value, label);
          assert(cond);

          jit_call *check = create_checked (&jit_typeinfo::logically_true,
                                            cond);

          jit_block *body = factory.create<jit_block> ("case_body");
          blocks.push_back (body);

          block->append (factory.create<jit_cond_branch> (check, body,
                                                          entry_blocks[i+1]));
          block = body; // case_body
        }

      tree_statement_list *stmt_lst = twc->commands ();
      assert(stmt_lst);

      try
        {
          stmt_lst->accept (*this);
          num_incomming++;
          block->append (factory.create<jit_branch> (tail));
        }
      catch (const jit_break_exception&)
        { }

      // each branch in the case statement will have different breaks/continues
      current_breaks.splice (current_breaks.end (), breaks);
      current_continues.splice (current_continues.end (), continues);
    }

  // each branch in the case statement will have different breaks/continues
  breaks.splice (breaks.end (), current_breaks);
  continues.splice (continues.end (), current_continues);

  if (num_incomming || ! has_otherwise)
    {
      blocks.push_back (tail);
      block = tail; // switch_tail
    }
  else
    throw jit_break_exception ();   // every branch broke
}

void
jit_convert::visit_try_catch_command (tree_try_catch_command&)
{
  throw jit_fail_exception ("No visit_try_catch_command implementation");
}

void
jit_convert::visit_unwind_protect_command (tree_unwind_protect_command&)
{
  throw jit_fail_exception ("No visit_unwind_protect_command implementation");
}

void
jit_convert::visit_while_command (tree_while_command& wc)
{
  unwind_protect prot;
  prot.protect_var (breaks);
  prot.protect_var (continues);
  breaks.clear ();
  continues.clear ();

  jit_block *cond_check = factory.create<jit_block> ("while_cond_check");
  block->append (factory.create<jit_branch> (cond_check));
  blocks.push_back (cond_check);
  block = cond_check;

  tree_expression *expr = wc.condition ();
  assert (expr && "While expression can not be null");
  jit_value *check = visit (expr);
  check = create_checked (&jit_typeinfo::logically_true, check);

  jit_block *body = factory.create<jit_block> ("while_body");
  blocks.push_back (body);

  jit_block *tail = factory.create<jit_block> ("while_tail");
  block->append (factory.create<jit_cond_branch> (check, body, tail));
  block = body;

  tree_statement_list *loop_body = wc.body ();
  bool all_breaking = false;
  if (loop_body)
    {
      try
        {
          loop_body->accept (*this);
        }
      catch (const jit_break_exception&)
        {
          all_breaking = true;
        }
    }

  finish_breaks (tail, breaks);

  if (! all_breaking || continues.size ())
    {
      jit_block *interrupt_check
        = factory.create<jit_block> ("interrupt_check");
      blocks.push_back (interrupt_check);
      finish_breaks (interrupt_check, continues);
      if (! all_breaking)
        block->append (factory.create<jit_branch> (interrupt_check));

      block = interrupt_check;
      jit_error_check *ec
        = factory.create<jit_error_check> (jit_error_check::var_interrupt,
                                           cond_check, final_block);
      block->append (ec);
    }

  blocks.push_back (tail);
  block = tail;
}

void
jit_convert::visit_do_until_command (tree_do_until_command& duc)
{
  unwind_protect prot;
  prot.protect_var (breaks);
  prot.protect_var (continues);
  breaks.clear ();
  continues.clear ();

  jit_block *body = factory.create<jit_block> ("do_until_body");
  jit_block *cond_check = factory.create<jit_block> ("do_until_cond_check");
  jit_block *tail = factory.create<jit_block> ("do_until_tail");

  block->append (factory.create<jit_branch> (body));
  blocks.push_back (body);
  block = body;

  tree_statement_list *loop_body = duc.body ();
  bool all_breaking = false;
  if (loop_body)
    {
      try
        {
          loop_body->accept (*this);
        }
      catch (const jit_break_exception&)
        {
          all_breaking = true;
        }
    }

  finish_breaks (tail, breaks);

  if (! all_breaking || continues.size ())
    {
      jit_block *interrupt_check
        = factory.create<jit_block> ("interrupt_check");
      blocks.push_back (interrupt_check);
      finish_breaks (interrupt_check, continues);
      if (! all_breaking)
        block->append (factory.create<jit_branch> (interrupt_check));

      block = interrupt_check;
      jit_error_check *ec
        = factory.create<jit_error_check> (jit_error_check::var_interrupt,
                                           cond_check, final_block);
      block->append (ec);

      blocks.push_back (cond_check);
      block = cond_check;

      tree_expression *expr = duc.condition ();
      assert (expr && "Do-Until expression can not be null");
      jit_value *check = visit (expr);
      check = create_checked (&jit_typeinfo::logically_true, check);

      block->append (factory.create<jit_cond_branch> (check, tail, body));
    }

  blocks.push_back (tail);
  block = tail;
}

void
jit_convert::initialize (symbol_table::scope_id s)
{
  scope = s;
  iterator_count = 0;
  for_bounds_count = 0;
  short_count = 0;
  jit_instruction::reset_ids ();

  entry_block = factory.create<jit_block> ("body");
  final_block = factory.create<jit_block> ("final");
  blocks.push_back (entry_block);
  entry_block->mark_alive ();
  block = entry_block;
}

jit_call *
jit_convert::create_checked_impl (jit_call *ret)
{
  block->append (ret);

  jit_block *normal = factory.create<jit_block> (block->name ());
  jit_error_check *check
    = factory.create<jit_error_check> (jit_error_check::var_error_state, ret,
                                       normal, final_block);
  block->append (check);
  blocks.push_back (normal);
  block = normal;

  return ret;
}

jit_variable *
jit_convert::find_variable (const std::string& vname) const
{
  variable_map::const_iterator iter;
  iter = vmap.find (vname);
  return iter != vmap.end () ? iter->second : 0;
}

jit_variable *
jit_convert::get_variable (const std::string& vname)
{
  jit_variable *ret = find_variable (vname);
  if (ret)
    return ret;

  symbol_table::symbol_record record = symbol_table::find_symbol (vname, scope);
  if (record.is_persistent () || record.is_global ())
    throw jit_fail_exception ("Persistent and global not yet supported");

  if (converting_function)
    return create_variable (vname, jit_typeinfo::get_any (), false);
  else
    {
      octave_value val = record.varval ();
      if (val.is_undefined ())
        val = symbol_table::find_function (vname);

      jit_type *type = jit_typeinfo::type_of (val);
      bounds.push_back (type_bound (type, vname));

      return create_variable (vname, type);
    }
}

jit_variable *
jit_convert::create_variable (const std::string& vname, jit_type *type,
                              bool isarg)
{
  jit_variable *var = factory.create<jit_variable> (vname);

  if (isarg)
    {
      jit_extract_argument *extract;
      extract = factory.create<jit_extract_argument> (type, var);
      entry_block->prepend (extract);
    }
  else
    {
      jit_call *init = factory.create<jit_call> (&jit_typeinfo::create_undef);
      jit_assign *assign = factory.create<jit_assign> (var, init);
      entry_block->prepend (assign);
      entry_block->prepend (init);
    }

  return vmap[vname] = var;
}

std::string
jit_convert::next_name (const char *prefix, size_t& count, bool inc)
{
  std::stringstream ss;
  ss << prefix << count;
  if (inc)
    ++count;
  return ss.str ();
}

jit_instruction *
jit_convert::resolve (tree_index_expression& exp, jit_value *extra_arg,
                      bool lhs)
{
  std::string type = exp.type_tags ();
  if (! (type.size () == 1 && type[0] == '('))
    throw jit_fail_exception ("Unsupported index operation");

  std::list<tree_argument_list *> args = exp.arg_lists ();
  if (args.size () != 1)
    throw jit_fail_exception ("Bad number of arguments in "
                              "tree_index_expression");

  tree_argument_list *arg_list = args.front ();
  if (! arg_list)
    throw jit_fail_exception ("null argument list");

  if (arg_list->size () < 1)
    throw jit_fail_exception ("Empty arg_list");

  tree_expression *tree_object = exp.expression ();
  jit_value *object;
  if (lhs)
    {
      tree_identifier *id = dynamic_cast<tree_identifier *> (tree_object);
      if (! id)
        throw jit_fail_exception ("expected identifier");
      object = get_variable (id->name ());
    }
  else
    object = visit (tree_object);

  size_t narg = arg_list->size ();
  tree_argument_list::iterator iter = arg_list->begin ();
  bool have_extra = extra_arg;
  std::vector<jit_value *> call_args (narg + 1 + have_extra);
  call_args[0] = object;

  for (size_t idx = 0; iter != arg_list->end (); ++idx, ++iter)
    {
      unwind_protect prot;
      prot.add_method (&end_context,
                       &std::vector<jit_magic_end::context>::pop_back);

      jit_magic_end::context ctx (factory, object, idx, narg);
      end_context.push_back (ctx);
      call_args[idx + 1] = visit (*iter);
    }

  if (extra_arg)
    call_args[call_args.size () - 1] = extra_arg;

  const jit_operation& fres = lhs ? jit_typeinfo::paren_subsasgn ()
                                  : jit_typeinfo::paren_subsref ();

  return create_checked (fres, call_args);
}

jit_value *
jit_convert::do_assign (tree_expression *exp, jit_value *rhs, bool artificial)
{
  if (! exp)
    throw jit_fail_exception ("NULL lhs in assign");

  if (isa<tree_identifier> (exp))
    return do_assign (exp->name (), rhs, exp->print_result (), artificial);
  else if (tree_index_expression *idx
           = dynamic_cast<tree_index_expression *> (exp))
    {
      jit_value *new_object = resolve (*idx, rhs, true);
      do_assign (idx->expression (), new_object, true);

      // FIXME: Will not work for values that must be release/grabed
      return rhs;
    }
  else
    throw jit_fail_exception ("Unsupported assignment");
}

jit_value *
jit_convert::do_assign (const std::string& lhs, jit_value *rhs,
                        bool print, bool artificial)
{
  jit_variable *var = get_variable (lhs);
  jit_assign *assign = block->append (factory.create<jit_assign> (var, rhs));

  if (artificial)
    assign->mark_artificial ();

  if (print)
    {
      const jit_operation& print_fn = jit_typeinfo::print_value ();
      jit_const_string *name = factory.create<jit_const_string> (lhs);
      block->append (factory.create<jit_call> (print_fn, name, var));
    }

  return var;
}

jit_value *
jit_convert::visit (tree& tee)
{
  unwind_protect prot;
  prot.protect_var (result);

  tee.accept (*this);
  return result;
}

void
jit_convert::finish_breaks (jit_block *dest, const block_list& lst)
{
  for (block_list::const_iterator iter = lst.begin (); iter != lst.end ();
       ++iter)
    {
      jit_block *b = *iter;
      b->append (factory.create<jit_branch> (dest));
    }
}

// -------------------- jit_convert_gcc --------------------
#ifdef HAVE_GCCJIT
jit_convert_gcc::jit_convert_gcc ()
  : m_blocks (NULL),
    m_constants (NULL),
    m_ctxt (jit_typeinfo::create_gccjit_child_context ()),
    m_func (),
    m_extracted_params (),
    m_block_map (),
    m_locals_for_insns (),
    m_block_stack (),
    m_log_visits (false)
{
  // FIXME: error-handling

  m_ctxt.set_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
                         3);

  // Diagnostics, debugging etc:
  if (1)
    {
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO,
                              1);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE,
                              0);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
                              1);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_SUMMARY,
                              1);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
                              0);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
                              1);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_SELFCHECK_GC,
                              0);
      m_ctxt.set_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES,
                              1);
    }
}

jit_convert_gcc::~jit_convert_gcc ()
{
  m_ctxt.release ();
}

gcc_jit_result *
jit_convert_gcc::convert_loop (const jit_block_list& blocks,
                               const std::list<jit_value *>& constants)
{
  converting_function = false;

  m_blocks = &blocks;
  m_constants = &constants;

  jit_type *any = jit_typeinfo::get_any ();
  // argument is an array of octave_base_value*, or octave_base_value**
  gccjit::type arg_type = any->to_gccjit (); // this is octave_base_value*
  arg_type = arg_type.get_pointer ();

  // for now just init arguments from entry, later we will have to do something
  // more interesting
  jit_block *entry_block = m_blocks->front ();
  for (jit_block::iterator iter = entry_block->begin ();
       iter != entry_block->end (); ++iter)
    if (jit_extract_argument *extract
        = dynamic_cast<jit_extract_argument *> (*iter))
      {
        m_argument_index[extract->name ()] = m_argument_vec.size ();
        m_argument_vec.push_back (std::make_pair (extract->name (), true));
      }

  /* We have a single param, confusingly named "params" (an array).  */
  params = m_ctxt.new_param (arg_type, "params");
  
  std::vector<gccjit::param> params_of_fn (1, params);

  m_func = m_ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
                                m_ctxt.get_type (GCC_JIT_TYPE_VOID),
                                "loop",
                                params_of_fn,
                                0);

  gccjit::block initial_block = m_func.new_block ("initial");

  /* Create gccjit::blocks for each jit_block, and for each edge.

     We're converting from an SSA representation back to a non-SSA
     representation, hence we need to convert phi nodes into regular
     assignments.  To do this, introduce a gccjit::block for each edge, to
     handle the phis along each edges.  Jumps to blocks in the JIT-IR
     will become jumps to edge-handling gccjit::blocks in our CFG; each
     such "edge" block will end with a jump to the "real" gccjit::block.  */
  std::list<jit_block *>::const_iterator block_iter;
  for (block_iter = m_blocks->begin ();
       block_iter != m_blocks->end ();
       ++block_iter)
    {
      jit_block *jblock = *block_iter;
      
      // Find predecessor blocks
      for (jit_use *use = jblock->first_use (); use; use = use->next ())
        {
          jit_block *pred = use->user_parent ();

          /* Create edge-handling gccjit::block.  */
          m_blocks_for_edges[std::make_pair(pred, jblock)] =
            m_func.new_block (std::string ("edge_from_")
                              + pred->name_and_id ()
                              + std::string ("_to_")
                              + jblock->name_and_id ());
        }

      /* The "real" gccjit::block.  */
      m_block_map[jblock] =
        m_func.new_block (jblock->name_and_id ());
    }

  /* It's an SSA representation, so we have another pass here that
     creates a local for every statement.  */
  for (block_iter = m_blocks->begin ();
       block_iter != m_blocks->end ();
       ++block_iter)
    for (jit_block::iterator insn_iter = (*block_iter)->begin ();
         insn_iter != (*block_iter)->end ();
         ++insn_iter)
      {
        jit_instruction *insn = *insn_iter;

        /* Some insns have NULL type e.g. conditional branches; it only
           makes sense to create a local for those with actual types.  */
        if (insn->type ())
          {
            std::stringstream ss;
            // FIXME: perhaps make these identifiers look less weird?
            // that said, the short_print vfunc conveys more information
            ss << "(";
            insn->short_print (ss);
            ss << ")";

            m_locals_for_insns[insn] =
              m_func.new_local (insn->type_gccjit (),
                                ss.str ().c_str ());

            insn->stash_rvalue (m_locals_for_insns[insn]);
          }
      }

  // constants aren't in the IR, we visit those first
  for (std::list<jit_value *>::const_iterator iter = m_constants->begin ();
       iter != m_constants->end (); ++iter)
    if (! isa<jit_instruction> (*iter))
      visit (*iter);

  /* Prelude: extract params from the "params" array.  */
  if (m_with_comments)
    initial_block.add_comment ("prelude");
  for (int i = 0; i < m_argument_vec.size (); ++i)
    {
      gccjit::lvalue extracted_param =
        m_func.new_local (any->to_gccjit (),
                          "extracted_param_" + m_argument_vec[i].first);
      m_extracted_params.push_back (extracted_param);
      initial_block.add_assignment (
        extracted_param,
        m_ctxt.new_array_access (
          params,
          m_ctxt.new_rvalue (m_ctxt.get_type (GCC_JIT_TYPE_INT),
                             i)));
    }
  initial_block.end_with_jump (m_block_map[*m_blocks->begin ()]);

  // convert all instructions
  for (block_iter = m_blocks->begin (); block_iter != m_blocks->end (); ++block_iter)
    visit (**block_iter);

  m_ctxt.dump_to_file ("/tmp/loop-dump.c", true);

  m_func.dump_to_dot ("/tmp/loop.dot");

  return m_ctxt.compile ();
}

#if 0
gccjit::lvalue
jit_convert_gcc::as_lvalue (jit_value *jval)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::as_lvalue (jit_value *jval): " << *jval << "\n";

  if (!jval->has_lvalue ())
    {
      gccjit::lvalue local =
        m_func.new_local (/* FIXME: */
                          m_ctxt.get_type (GCC_JIT_TYPE_INT),
                          jval->print_string ().c_str ());
      jval->stash_lvalue (local);
    }

  return jval->as_lvalue ();
}
#endif

gccjit::rvalue
jit_convert_gcc::as_rvalue (jit_value *jval)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::as_rvalue (jit_value *jval): " << *jval << "\n";
  //std::map<jit_value *, gcc_jit_rvalue *> m_rvalues;

  if (jit_instruction *insn = dynamic_cast<jit_instruction *> (jval))
    {
      return m_locals_for_insns[insn];
    }

  //  return gcc_jit_lvalue_as_rvalue (as_lvalue (jval));
  return jval->as_rvalue ();
}

void
jit_convert_gcc::add_assignment_for_insn (jit_instruction &insn,
                                          gccjit::rvalue val)
{
  //insn.stash_rvalue (val);
  if (insn.type ())
    get_current_block ().add_assignment (m_locals_for_insns[&insn],
                                         val);
  //TODO: use location of insn
}

gccjit::block
jit_convert_gcc::get_block_for_edge (jit_block *src, jit_block *dest)
{
  return m_blocks_for_edges[std::make_pair (src, dest)];
}

gccjit::block
jit_convert_gcc::get_current_block ()
{
  assert (!m_block_stack.empty ());
  return m_block_stack.top ();
}

void
jit_convert_gcc::push_block (gccjit::block block)
{
  m_block_stack.push (block);
}

void
jit_convert_gcc::pop_block ()
{
  m_block_stack.pop ();
}

  //std::vector<gccjit::block> m_current_block_stack;


void
jit_convert_gcc::visit (jit_const_string& cs)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_string& cs): " << cs << "\n";
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (cs.value ());
  cs.stash_rvalue (rvalue);
}

void
jit_convert_gcc::visit (jit_const_bool& cb)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_bool& cb): " << cb << "\n";
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (cb.type_gccjit (),
                                             cb.value ());
  cb.stash_rvalue (rvalue);
}

void
jit_convert_gcc::visit (jit_const_scalar& cs)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_scalar& cs): " << cs << "\n";
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (cs.type_gccjit (),
                                             cs.value ());
  cs.stash_rvalue (rvalue);
}

void
jit_convert_gcc::visit (jit_const_complex& cc)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_complex& cc): " << cc << "\n";
#if 0
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (cc.type_gccjit (),
                                             cc.value ());
  cc.stash_rvalue (rvalue);
#else
  throw jit_fail_exception ("TODO");
#endif
}

void jit_convert_gcc::visit (jit_const_index& ci)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_index& ci): " << ci << "\n";
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (ci.type_gccjit (),
                                             ci.value ());
  ci.stash_rvalue (rvalue);
}

void
jit_convert_gcc::visit (jit_const_range& cr)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_const_range& cr): " << cr << "\n";
#if 0
  gccjit::rvalue rvalue = m_ctxt.new_rvalue (cr.type_gccjit (),
                                             cr.value ());
  cr.stash_rvalue (rvalue);
#else
  throw jit_fail_exception ("TODO");
#endif
}

void
jit_convert_gcc::visit (jit_block& b)
{
  /* First, handle the phi nodes by populating the edge-handling blocks
     with assignments that set up the phi nodes's lvalues based on the incoming
     edge, then jumping to the "real" block for this block.  */
  for (jit_block::iterator insn_iter = b.begin ();
       insn_iter != b.end ();
       ++insn_iter)
    if (jit_phi *phi = dynamic_cast<jit_phi *> (*insn_iter))
      {
        for (size_t i = 0; i < phi->argument_count (); ++i)
          {
            jit_block *pred = phi->incomming (i);
            push_block (m_blocks_for_edges[std::make_pair(pred, &b)]);
            if (m_with_comments)
              {
                std::ostringstream os;
                os << "incoming edge " << i << " for: ";
                phi->print (os);
                get_current_block ().add_comment (os.str ());
              }
            get_current_block ().add_assignment (
              m_locals_for_insns[phi],
              m_locals_for_insns[static_cast <jit_instruction *> (phi->argument (i))]);
            pop_block ();
          }
      }

  /* Terminate the blocks for edges leading to this block.  */
  jit_use *use;
  for (use = b.first_use (); use; use = use->next ())
    {
      jit_block *pred = use->user_parent ();
      gccjit::block block_for_edge =
        m_blocks_for_edges[std::make_pair(pred, &b)];
      block_for_edge.end_with_jump (m_block_map[&b]);
    }
  
  /* Now generate a block for all of the non-phi instructions.  */
  push_block (m_block_map[&b]);
  for (jit_block::iterator insn_iter = b.begin ();
       insn_iter != b.end ();
       ++insn_iter)
    {
      // We've handled phi nodes above; skip them:
      if (dynamic_cast<jit_phi *> (*insn_iter))
        continue;

      if (m_with_comments)
        get_current_block ().add_comment (std::string ("jit_instruction:   ")
                                          + (*insn_iter)->print_string ());
      visit (*insn_iter);
    }
  pop_block ();
}

void
jit_convert_gcc::visit (jit_branch& b)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_branch& b): " << b << "\n";

  jit_block *this_block = b.parent (); 
  jit_block *next = b.successor ();

  get_current_block ().end_with_jump (get_block_for_edge (this_block, next));
}

void
jit_convert_gcc::visit (jit_cond_branch& cb)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_cond_branch& cb): " << cb << "\n";

  gccjit::rvalue boolval = as_rvalue (cb.cond ());

  jit_block *this_block = cb.parent ();
  get_current_block ().end_with_conditional (
    boolval,
    get_block_for_edge (this_block, cb.successor (0)),
    get_block_for_edge (this_block, cb.successor (1)));
}

void
jit_convert_gcc::visit (jit_call& call)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_call& call): " << call << "\n";

  const jit_function& ol = call.overload ();

  std::vector<jit_value *> args (call.arguments ().size ());
  for (size_t i = 0; i < args.size (); ++i)
    args[i] = call.argument (i);

  gccjit::rvalue ret = ol.call (m_ctxt, get_current_block (), args);
  add_assignment_for_insn (call, ret);
}

void
jit_convert_gcc::visit (jit_extract_argument& extract)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_extract_argument& extract): " << extract << "\n";

  const std::string& argname = extract.name ();

  // Get the stored (octave_base_value *)
  gccjit::lvalue param = m_extracted_params[m_argument_index[argname]];

  // Convert it to a more specialized type:
  const jit_function& ol = extract.overload ();
  std::vector<gccjit::rvalue> args (1, param);
  gccjit::rvalue extracted = ol.call (m_ctxt, get_current_block (), args);
  add_assignment_for_insn (extract,
                           extracted);
}

void
jit_convert_gcc::visit (jit_store_argument& store)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_store_argument& store): " << store << "\n";

  const jit_function& ol = store.overload ();

  std::vector<jit_value *> args (1);
  args[0] = store.result ();

  gccjit::rvalue ret = ol.call (m_ctxt, get_current_block (), args);
  int index = m_argument_index[store.name ()];
  get_current_block ().add_assignment (
    // "params[index] = ..."
    m_ctxt.new_array_access (
      params,
      m_ctxt.new_rvalue (m_ctxt.get_type (GCC_JIT_TYPE_INT),
                         index)),
    // "... = ret;"
    ret);
}

void
jit_convert_gcc::visit (jit_return& ret)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_return& ret): " << ret << "\n";
  jit_value *res = ret.result ();

  if (converting_function)
    throw jit_fail_exception ("TODO");
  else
    {
      if (res)
        get_current_block ().end_with_return (res->as_rvalue ());
      else
        get_current_block ().end_with_return ();
    }
}

void
jit_convert_gcc::visit (jit_phi& phi)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_phi& phi): " << phi << "\n";
  throw jit_fail_exception ("TODO");
}

void
jit_convert_gcc::visit (jit_variable& var)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_variable&): " << var << "\n";
  throw jit_fail_exception ("TODO");
}

void
jit_convert_gcc::visit (jit_error_check& check)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_error_check& check): " << check << "\n";

  gccjit::rvalue cond;

  switch (check.check_variable ())
    {
    case jit_error_check::var_error_state:
      cond = jit_typeinfo::insert_error_check (m_func);
      break;
    case jit_error_check::var_interrupt:
      cond = jit_typeinfo::insert_interrupt_check (m_func);
      break;
    default:
      panic_impossible ();
    }

  jit_block *this_block = check.parent ();
  get_current_block ().end_with_conditional (
    cond,
    get_block_for_edge (this_block, check.successor (0)),
    get_block_for_edge (this_block, check.successor (1)));
}

void
jit_convert_gcc::visit (jit_assign& assign)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_assign& assign): " << assign << "\n";

  jit_value *jsrc = assign.src ();

  add_assignment_for_insn (assign,
                           as_rvalue (jsrc));
}

void
jit_convert_gcc::visit (jit_argument& arg)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_argument&): " << arg << "\n";
  throw jit_fail_exception ("TODO");
}

void
jit_convert_gcc::visit (jit_magic_end& me)
{
  if (m_log_visits)
    std::cerr << "jit_convert_gcc::visit (jit_magic_end& me): " << me << "\n";
  throw jit_fail_exception ("TODO");
}
#endif /* HAVE_GCCJIT */

// -------------------- jit_convert_llvm --------------------

#ifdef HAVE_LLVM

llvm::Function *
jit_convert_llvm::convert_loop (llvm::Module *module,
                                const jit_block_list& blocks,
                                const std::list<jit_value *>& constants)
{
  converting_function = false;

  // for now just init arguments from entry, later we will have to do something
  // more interesting
  jit_block *entry_block = blocks.front ();
  for (jit_block::iterator iter = entry_block->begin ();
       iter != entry_block->end (); ++iter)
    if (jit_extract_argument *extract
        = dynamic_cast<jit_extract_argument *> (*iter))
      argument_vec.push_back (std::make_pair (extract->name (), true));


  jit_type *any = jit_typeinfo::get_any ();

  // argument is an array of octave_base_value*, or octave_base_value**
  llvm::Type *arg_type = any->to_llvm (); // this is octave_base_value*
  arg_type = arg_type->getPointerTo ();
  llvm::FunctionType *ft;
  ft = llvm::FunctionType::get (llvm::Type::getVoidTy (context), arg_type,
                                false);
  function = llvm::Function::Create (ft, llvm::Function::ExternalLinkage,
                                     "foobar", module);

  try
    {
      prelude = llvm::BasicBlock::Create (context, "prelude", function);
      builder.SetInsertPoint (prelude);

      llvm::Value *arg = function->arg_begin ();
      for (size_t i = 0; i < argument_vec.size (); ++i)
        {
          llvm::Value *loaded_arg = builder.CreateConstInBoundsGEP1_32 (arg, i);
          arguments[argument_vec[i].first] = loaded_arg;
        }

      convert (blocks, constants);
    }
  catch (const jit_fail_exception& e)
    {
      function->eraseFromParent ();
      throw;
    }

  return function;
}


jit_function
jit_convert_llvm::convert_function (llvm::Module *module,
                                    const jit_block_list& blocks,
                                    const std::list<jit_value *>& constants,
                                    octave_user_function& fcn,
                                    const std::vector<jit_type *>& args)
{
  converting_function = true;

  jit_block *final_block = blocks.back ();
  jit_return *ret = dynamic_cast<jit_return *> (final_block->back ());
  assert (ret);

  creating = jit_function (module,
#ifdef HAVE_GCCJIT
                           gccjit::context (), // FIXME?
#endif
                           jit_convention::internal,
                           "foobar", ret->result_type (), args);
  function = creating.to_llvm ();

  try
    {
      prelude = creating.new_block ("prelude");
      builder.SetInsertPoint (prelude);

      tree_parameter_list *plist = fcn.parameter_list ();
      if (plist)
        {
          tree_parameter_list::iterator piter = plist->begin ();
          tree_parameter_list::iterator pend = plist->end ();
          for (size_t i = 0; i < args.size () && piter != pend; ++i, ++piter)
            {
              tree_decl_elt *elt = *piter;
              std::string arg_name = elt->name ();
              arguments[arg_name] = creating.argument (builder, i);
            }
        }

      convert (blocks, constants);
    }
  catch (const jit_fail_exception& e)
    {
      function->eraseFromParent ();
      throw;
    }

  return creating;
}

void
jit_convert_llvm::convert (const jit_block_list& blocks,
                           const std::list<jit_value *>& constants)
{
  std::list<jit_block *>::const_iterator biter;
  for (biter = blocks.begin (); biter != blocks.end (); ++biter)
    {
      jit_block *jblock = *biter;
      llvm::BasicBlock *block = llvm::BasicBlock::Create (context,
                                                          jblock->name (),
                                                          function);
      jblock->stash_llvm (block);
    }

  jit_block *first = *blocks.begin ();
  builder.CreateBr (first->to_llvm ());

  // constants aren't in the IR, we visit those first
  for (std::list<jit_value *>::const_iterator iter = constants.begin ();
       iter != constants.end (); ++iter)
    if (! isa<jit_instruction> (*iter))
      visit (*iter);

  // convert all instructions
  for (biter = blocks.begin (); biter != blocks.end (); ++biter)
    visit (*biter);

  // now finish phi nodes
  for (biter = blocks.begin (); biter != blocks.end (); ++biter)
    {
      jit_block& block = **biter;
      for (jit_block::iterator piter = block.begin ();
           piter != block.end () && isa<jit_phi> (*piter); ++piter)
        {
          jit_instruction *phi = *piter;
          finish_phi (static_cast<jit_phi *> (phi));
        }
    }
}

void
jit_convert_llvm::finish_phi (jit_phi *phi)
{
  llvm::PHINode *llvm_phi = phi->to_llvm ();
  for (size_t i = 0; i < phi->argument_count (); ++i)
    {
      llvm::BasicBlock *pred = phi->incomming_llvm (i);
      llvm_phi->addIncoming (phi->argument_llvm (i), pred);
    }
}

void
jit_convert_llvm::visit (jit_const_string& cs)
{
  cs.stash_llvm (builder.CreateGlobalStringPtr (cs.value ()));
}

void
jit_convert_llvm::visit (jit_const_bool& cb)
{
  cb.stash_llvm (llvm::ConstantInt::get (cb.type_llvm (), cb.value ()));
}

void
jit_convert_llvm::visit (jit_const_scalar& cs)
{
  cs.stash_llvm (llvm::ConstantFP::get (cs.type_llvm (), cs.value ()));
}

void
jit_convert_llvm::visit (jit_const_complex& cc)
{
  llvm::Type *scalar_t = jit_typeinfo::get_scalar_llvm ();
  Complex value = cc.value ();
  llvm::Value *real = llvm::ConstantFP::get (scalar_t, value.real ());
  llvm::Value *imag = llvm::ConstantFP::get (scalar_t, value.imag ());
  cc.stash_llvm (jit_typeinfo::create_complex (real, imag));
}

void jit_convert_llvm::visit (jit_const_index& ci)
{
  ci.stash_llvm (llvm::ConstantInt::get (ci.type_llvm (), ci.value ()));
}

void
jit_convert_llvm::visit (jit_const_range& cr)
{
  llvm::StructType *stype = llvm::cast<llvm::StructType>(cr.type_llvm ());
  llvm::Type *scalar_t = jit_typeinfo::get_scalar_llvm ();
  llvm::Type *idx = jit_typeinfo::get_index_llvm ();
  const jit_range& rng = cr.value ();

  llvm::Constant *constants[4];
  constants[0] = llvm::ConstantFP::get (scalar_t, rng.base);
  constants[1] = llvm::ConstantFP::get (scalar_t, rng.limit);
  constants[2] = llvm::ConstantFP::get (scalar_t, rng.inc);
  constants[3] = llvm::ConstantInt::get (idx, rng.nelem);

  llvm::Value *as_llvm;
  as_llvm = llvm::ConstantStruct::get (stype,
                                       llvm::makeArrayRef (constants, 4));
  cr.stash_llvm (as_llvm);
}

void
jit_convert_llvm::visit (jit_block& b)
{
  llvm::BasicBlock *block = b.to_llvm ();
  builder.SetInsertPoint (block);
  for (jit_block::iterator iter = b.begin (); iter != b.end (); ++iter)
    visit (*iter);
}

void
jit_convert_llvm::visit (jit_branch& b)
{
  b.stash_llvm (builder.CreateBr (b.successor_llvm ()));
}

void
jit_convert_llvm::visit (jit_cond_branch& cb)
{
  llvm::Value *cond = cb.cond_llvm ();
  llvm::Value *br;
  br = builder.CreateCondBr (cond, cb.successor_llvm (0),
                             cb.successor_llvm (1));
  cb.stash_llvm (br);
}

void
jit_convert_llvm::visit (jit_call& call)
{
  const jit_function& ol = call.overload ();

  std::vector<jit_value *> args (call.arguments ().size ());
  for (size_t i = 0; i < args.size (); ++i)
    args[i] = call.argument (i);

  llvm::Value *ret = ol.call (builder, args);
  call.stash_llvm (ret);
}

void
jit_convert_llvm::visit (jit_extract_argument& extract)
{
  llvm::Value *arg = arguments[extract.name ()];
  assert (arg);

  if (converting_function)
    extract.stash_llvm (arg);
  else
    {
      arg = builder.CreateLoad (arg);

      const jit_function& ol = extract.overload ();
      extract.stash_llvm (ol.call (builder, arg));
    }
}

void
jit_convert_llvm::visit (jit_store_argument& store)
{
  const jit_function& ol = store.overload ();
  llvm::Value *arg_value = ol.call (builder, store.result ());
  llvm::Value *arg = arguments[store.name ()];
  store.stash_llvm (builder.CreateStore (arg_value, arg));
}

void
jit_convert_llvm::visit (jit_return& ret)
{
  jit_value *res = ret.result ();

  if (converting_function)
    creating.do_return (builder, res->to_llvm (), false);
  else
    {
      if (res)
        builder.CreateRet (res->to_llvm ());
      else
        builder.CreateRetVoid ();
    }
}

void
jit_convert_llvm::visit (jit_phi& phi)
{
  // we might not have converted all incoming branches, so we don't
  // set incomming branches now
  llvm::PHINode *node = llvm::PHINode::Create (phi.type_llvm (),
                                               phi.argument_count ());
  builder.Insert (node);
  phi.stash_llvm (node);
}

void
jit_convert_llvm::visit (jit_variable&)
{
  throw jit_fail_exception ("ERROR: SSA construction should remove all variables");
}

void
jit_convert_llvm::visit (jit_error_check& check)
{
  llvm::Value *cond;

  switch (check.check_variable ())
    {
    case jit_error_check::var_error_state:
      cond = jit_typeinfo::insert_error_check (builder);
      break;
    case jit_error_check::var_interrupt:
      cond = jit_typeinfo::insert_interrupt_check (builder);
      break;
    default:
      panic_impossible ();
    }

  llvm::Value *br = builder.CreateCondBr (cond, check.successor_llvm (0),
                                          check.successor_llvm (1));
  check.stash_llvm (br);
}

void
jit_convert_llvm::visit (jit_assign& assign)
{
  jit_value *new_value = assign.src ();
  assign.stash_llvm (new_value->to_llvm ());

  if (assign.artificial ())
    return;

  jit_value *overwrite = assign.overwrite ();
  if (isa<jit_assign_base> (overwrite))
    {
      const jit_function& ol = jit_typeinfo::get_release (overwrite->type ());
      if (ol.valid ())
        ol.call (builder, overwrite);
    }
}

void
jit_convert_llvm::visit (jit_argument&)
{}

void
jit_convert_llvm::visit (jit_magic_end& me)
{
  const jit_function& ol = me.overload ();

  jit_magic_end::context ctx = me.resolve_context ();
  llvm::Value *ret = ol.call (builder, ctx.value, ctx.index, ctx.count);
  me.stash_llvm (ret);
}

#endif /* ifdef HAVE_LLVM */

// -------------------- jit_infer --------------------
jit_infer::jit_infer (jit_factory& afactory, jit_block_list& ablocks,
                      const variable_map& avmap)
  : blocks (ablocks), factory (afactory), vmap (avmap) { }

void
jit_infer::infer (void)
{
  construct_ssa ();

  // initialize the worklist to instructions derived from constants
  const std::list<jit_value *>& constants = factory.constants ();
  for (std::list<jit_value *>::const_iterator iter = constants.begin ();
       iter != constants.end (); ++iter)
    append_users (*iter);

  // the entry block terminator may be a regular branch statement
  if (entry_block ().terminator ())
    push_worklist (entry_block ().terminator ());

  // FIXME: Describe algorithm here
  while (worklist.size ())
    {
      jit_instruction *next = worklist.front ();
      worklist.pop_front ();
      next->stash_in_worklist (false);

      if (next->infer ())
        {
          // terminators need to be handles specially
          if (jit_terminator *term = dynamic_cast<jit_terminator *> (next))
            append_users_term (term);
          else
            append_users (next);
        }
    }

  remove_dead ();
  blocks.label ();
  place_releases ();
  simplify_phi ();
}

void
jit_infer::append_users (jit_value *v)
{
  for (jit_use *use = v->first_use (); use; use = use->next ())
    push_worklist (use->user ());
}

void
jit_infer::append_users_term (jit_terminator *term)
{
  for (size_t i = 0; i < term->successor_count (); ++i)
    {
      if (term->alive (i))
        {
          jit_block *succ = term->successor (i);
          for (jit_block::iterator iter = succ->begin ();
               iter != succ->end () && isa<jit_phi> (*iter); ++iter)
            push_worklist (*iter);

          jit_terminator *sterm = succ->terminator ();
          if (sterm)
            push_worklist (sterm);
        }
    }
}

void
jit_infer::construct_ssa (void)
{
  blocks.label ();
  final_block ().compute_idom (entry_block ());
  entry_block ().compute_df ();
  entry_block ().create_dom_tree ();

  // insert phi nodes where needed, this is done on a per variable basis
  for (variable_map::const_iterator iter = vmap.begin (); iter != vmap.end ();
       ++iter)
    {
      jit_block::df_set visited, added_phi;
      std::list<jit_block *> ssa_worklist;
      iter->second->use_blocks (visited);
      ssa_worklist.insert (ssa_worklist.begin (), visited.begin (),
                           visited.end ());

      while (ssa_worklist.size ())
        {
          jit_block *b = ssa_worklist.front ();
          ssa_worklist.pop_front ();

          for (jit_block::df_iterator diter = b->df_begin ();
               diter != b->df_end (); ++diter)
            {
              jit_block *dblock = *diter;
              if (! added_phi.count (dblock))
                {
                  jit_phi *phi = factory.create<jit_phi> (iter->second,
                                                          dblock->use_count ());
                  dblock->prepend (phi);
                  added_phi.insert (dblock);
                }

              if (! visited.count (dblock))
                {
                  ssa_worklist.push_back (dblock);
                  visited.insert (dblock);
                }
            }
        }
    }

  do_construct_ssa (entry_block (), entry_block ().visit_count ());
}

void
jit_infer::do_construct_ssa (jit_block& ablock, size_t avisit_count)
{
  if (ablock.visited (avisit_count))
    return;

  // replace variables with their current SSA value
  for (jit_block::iterator iter = ablock.begin (); iter != ablock.end ();
       ++iter)
    {
      jit_instruction *instr = *iter;
      instr->construct_ssa ();
      instr->push_variable ();
    }

  // finish phi nodes of successors
  for (size_t i = 0; i < ablock.successor_count (); ++i)
    {
      jit_block *finish = ablock.successor (i);

      for (jit_block::iterator iter = finish->begin ();
           iter != finish->end () && isa<jit_phi> (*iter);)
        {
          jit_phi *phi = static_cast<jit_phi *> (*iter);
          jit_variable *var = phi->dest ();
          ++iter;

          if (var->has_top ())
            phi->add_incomming (&ablock, var->top ());
          else
            {
              // temporaries may have extranious phi nodes which can be removed
              assert (! phi->use_count ());
              assert (var->name ().size () && var->name ()[0] == '#');
              phi->remove ();
            }
        }
    }

  for (size_t i = 0; i < ablock.dom_successor_count (); ++i)
    do_construct_ssa (*ablock.dom_successor (i), avisit_count);

  ablock.pop_all ();
}

void
jit_infer::place_releases (void)
{
  std::set<jit_value *> temporaries;
  for (jit_block_list::iterator iter = blocks.begin (); iter != blocks.end ();
       ++iter)
    {
      jit_block& ablock = **iter;
      if (ablock.id () != jit_block::NO_ID)
        {
          release_temp (ablock, temporaries);
          release_dead_phi (ablock);
        }
    }
}

void
jit_infer::push_worklist (jit_instruction *instr)
{
  if (! instr->in_worklist ())
    {
      instr->stash_in_worklist (true);
      worklist.push_back (instr);
    }
}

void
jit_infer::remove_dead ()
{
  jit_block_list::iterator biter;
  for (biter = blocks.begin (); biter != blocks.end (); ++biter)
    {
      jit_block *b = *biter;
      if (b->alive ())
        {
          for (jit_block::iterator iter = b->begin ();
               iter != b->end () && isa<jit_phi> (*iter);)
            {
              jit_phi *phi = static_cast<jit_phi *> (*iter);
              if (phi->prune ())
                iter = b->remove (iter);
              else
                ++iter;
            }
        }
    }

  for (biter = blocks.begin (); biter != blocks.end ();)
    {
      jit_block *b = *biter;
      if (b->alive ())
        {
          // FIXME: A special case for jit_error_check, if we generalize to
          // we will need to change!
          jit_terminator *term = b->terminator ();
          if (term && term->successor_count () == 2 && ! term->alive (0))
            {
              jit_block *succ = term->successor (1);
              term->remove ();
              jit_branch *abreak = factory.create<jit_branch> (succ);
              b->append (abreak);
              abreak->infer ();
            }

          ++biter;
        }
      else
        {
          jit_terminator *term = b->terminator ();
          if (term)
            term->remove ();
          biter = blocks.erase (biter);
        }
    }
}

void
jit_infer::release_dead_phi (jit_block& ablock)
{
  jit_block::iterator iter = ablock.begin ();
  while (iter != ablock.end () && isa<jit_phi> (*iter))
    {
      jit_phi *phi = static_cast<jit_phi *> (*iter);
      ++iter;

      jit_use *use = phi->first_use ();
      if (phi->use_count () == 1 && isa<jit_assign> (use->user ()))
        {
          // instead of releasing on assign, release on all incomming branches,
          // this can get rid of casts inside loops
          for (size_t i = 0; i < phi->argument_count (); ++i)
            {
              jit_value *arg = phi->argument (i);
              if (! arg->needs_release ())
                continue;

              jit_block *inc = phi->incomming (i);
              jit_block *split = inc->maybe_split (factory, blocks, ablock);
              jit_terminator *term = split->terminator ();
              jit_call *release
                = factory.create<jit_call> (jit_typeinfo::release, arg);
              release->infer ();
              split->insert_before (term, release);
            }

          phi->replace_with (0);
          phi->remove ();
        }
    }
}

void
jit_infer::release_temp (jit_block& ablock, std::set<jit_value *>& temp)
{
  for (jit_block::iterator iter = ablock.begin (); iter != ablock.end ();
       ++iter)
    {
      jit_instruction *instr = *iter;

      // check for temporaries that require release and live across
      // multiple blocks
      if (instr->needs_release ())
        {
          jit_block *fu_block = instr->first_use_block ();
          if (fu_block && fu_block != &ablock && instr->needs_release ())
            temp.insert (instr);
        }

      if (isa<jit_call> (instr))
        {
          // place releases for temporary arguments
          for (size_t i = 0; i < instr->argument_count (); ++i)
            {
              jit_value *arg = instr->argument (i);
              if (! arg->needs_release ())
                continue;

              jit_call *release
                = factory.create<jit_call> (&jit_typeinfo::release, arg);
              release->infer ();
              ablock.insert_after (iter, release);
              ++iter;
              temp.erase (arg);
            }
        }
    }

  if (! temp.size () || ! isa<jit_error_check> (ablock.terminator ()))
    return;

  // FIXME: If we support try/catch or unwind_protect final_block
  //        may not be the destination
  jit_block *split = ablock.maybe_split (factory, blocks, final_block ());
  jit_terminator *term = split->terminator ();
  for (std::set<jit_value *>::const_iterator iter = temp.begin ();
       iter != temp.end (); ++iter)
    {
      jit_value *value = *iter;
      jit_call *release
        = factory.create<jit_call> (&jit_typeinfo::release, value);
      split->insert_before (term, release);
      release->infer ();
    }
}

void
jit_infer::simplify_phi (void)
{
  for (jit_block_list::iterator biter = blocks.begin (); biter != blocks.end ();
       ++biter)
    {
      jit_block &ablock = **biter;
      for (jit_block::iterator iter = ablock.begin ();
           iter != ablock.end () && isa<jit_phi> (*iter); ++iter)
        simplify_phi (*static_cast<jit_phi *> (*iter));
    }
}

void
jit_infer::simplify_phi (jit_phi& phi)
{
  jit_block& pblock = *phi.parent ();
  const jit_operation& cast_fn = jit_typeinfo::cast (phi.type ());
  jit_variable *dest = phi.dest ();
  for (size_t i = 0; i < phi.argument_count (); ++i)
    {
      jit_value *arg = phi.argument (i);
      if (arg->type () != phi.type ())
        {
          jit_block *pred = phi.incomming (i);
          jit_block *split = pred->maybe_split (factory, blocks, pblock);
          jit_terminator *term = split->terminator ();
          jit_instruction *cast = factory.create<jit_call> (cast_fn, arg);
          jit_assign *assign = factory.create<jit_assign> (dest, cast);

          split->insert_before (term, cast);
          split->insert_before (term, assign);
          cast->infer ();
          assign->infer ();
          phi.stash_argument (i, assign);
        }
    }
}

// -------------------- tree_jit --------------------

tree_jit::tree_jit (void) : module (0), engine (0)
{
}

tree_jit::~tree_jit (void)
{}

bool
tree_jit::execute (tree_simple_for_command& cmd, const octave_value& bounds)
{
  return instance ().do_execute (cmd, bounds);
}

bool
tree_jit::execute (tree_while_command& cmd)
{
  return instance ().do_execute (cmd);
}

bool
tree_jit::execute (octave_user_function& fcn, const octave_value_list& args,
                   octave_value_list& retval)
{
  return instance ().do_execute (fcn, args, retval);
}

tree_jit&
tree_jit::instance (void)
{
  static tree_jit ret;
  return ret;
}

bool
tree_jit::initialize (void)
{
#ifdef HAVE_LLVM
  if (engine)
    return true;

  if (! module)
    {
      llvm::InitializeNativeTarget ();
      module = new llvm::Module ("octave", context);
    }

  // sometimes this fails pre main
  engine = llvm::ExecutionEngine::createJIT (module);

  if (! engine)
    return false;

#ifdef LEGACY_PASSMANAGER
  module_pass_manager = new llvm::legacy::PassManager ();
  pass_manager = new llvm::legacy::FunctionPassManager (module);
#else
  module_pass_manager = new llvm::PassManager ();
  pass_manager = new llvm::FunctionPassManager (module);
#endif
  module_pass_manager->add (llvm::createAlwaysInlinerPass ());

#ifdef HAVE_LLVM_DATALAYOUT
  pass_manager->add (new llvm::DataLayout (*engine->getDataLayout ()));
#else
  pass_manager->add (new llvm::TargetData (*engine->getTargetData ()));
#endif
  pass_manager->add (llvm::createCFGSimplificationPass ());
  pass_manager->add (llvm::createBasicAliasAnalysisPass ());
  pass_manager->add (llvm::createPromoteMemoryToRegisterPass ());
  pass_manager->add (llvm::createInstructionCombiningPass ());
  pass_manager->add (llvm::createReassociatePass ());
  pass_manager->add (llvm::createGVNPass ());
  pass_manager->add (llvm::createCFGSimplificationPass ());
  pass_manager->doInitialization ();
#endif /* ifdef HAVE_LLVM */

  jit_typeinfo::initialize (
#ifdef HAVE_LLVM
                            module, engine
#endif
);

  return true;
}

bool
tree_jit::do_execute (tree_simple_for_command& cmd, const octave_value& bounds)
{
  size_t tc = trip_count (bounds);
  if (! tc || ! initialize () || ! enabled ())
    return false;

  jit_info::vmap extra_vars;
  extra_vars["#for_bounds0"] = &bounds;

  jit_info *info = cmd.get_info ();
  if (! info || ! info->match (extra_vars))
    {
      if (tc < static_cast<size_t> (Vjit_startcnt))
        return false;

      delete info;
      info = new jit_info (*this, cmd, bounds);
      cmd.stash_info (info);
    }

  return info->execute (extra_vars);
}

bool
tree_jit::do_execute (tree_while_command& cmd)
{
  if (! initialize () || ! enabled ())
    return false;

  jit_info *info = cmd.get_info ();
  if (! info || ! info->match ())
    {
      delete info;
      info = new jit_info (*this, cmd);
      cmd.stash_info (info);
    }

  return info->execute ();
}

bool
tree_jit::do_execute (octave_user_function& fcn, const octave_value_list& args,
                      octave_value_list& retval)
{
  if (! initialize () || ! enabled ())
    return false;

  jit_function_info *info = fcn.get_info ();
  if (! info || ! info->match (args))
    {
      delete info;
      info = new jit_function_info (*this, fcn, args);
      fcn.stash_info (info);
    }

  return info->execute (args, retval);
}

bool
tree_jit::enabled (void)
{
  // Ideally, we should only disable JIT if there is a breakpoint in the code we
  // are about to run. However, we can't figure this out in O(1) time, so we
  // conservatively check for the existence of any breakpoints.
  return Vjit_enable && ! bp_table::have_breakpoints ()
         && ! Vdebug_on_interrupt && ! Vdebug_on_error;
}

size_t
tree_jit::trip_count (const octave_value& bounds) const
{
  if (bounds.is_range ())
    {
      Range rng = bounds.range_value ();
      return rng.numel ();
    }

  // unsupported type
  return 0;
}

#ifdef HAVE_LLVM
void
tree_jit::optimize (llvm::Function *fn)
{
  if (Vdebug_jit)
    llvm::verifyModule (*module);

  module_pass_manager->run (*module);
  pass_manager->run (*fn);

  if (Vdebug_jit)
    {
      std::string error;
#ifdef RAW_FD_OSTREAM_ARG_IS_LLVM_SYS_FS
      llvm::raw_fd_ostream fout ("test.bc", error,
                                 llvm::sys::fs::F_Binary);
#else
      llvm::raw_fd_ostream fout ("test.bc", error,
                                 llvm::raw_fd_ostream::F_Binary);
#endif
      llvm::WriteBitcodeToFile (module, fout);
    }
}
#endif /* ifdef HAVE_LLVM */

// -------------------- jit_function_info --------------------
#ifdef HAVE_JIT
jit_function_info::jit_function_info (tree_jit& tjit,
                                      octave_user_function& fcn,
                                      const octave_value_list& ov_args)
  : argument_types (ov_args.length ()), function (0)
{
  size_t nargs = ov_args.length ();
  for (size_t i = 0; i < nargs; ++i)
    argument_types[i] = jit_typeinfo::type_of (ov_args(i));

  jit_function raw_fn;
  jit_function wrapper;

  try
    {
      jit_convert conv (fcn, argument_types);

      if (Vdebug_jit)
        {
          jit_block_list& blocks = conv.get_blocks ();
          blocks.label ();
          std::cout << "-------------------- Compiling function ";
          std::cout << "--------------------\n";

          tree_print_code tpc (std::cout);
          tpc.visit_octave_user_function_header (fcn);
          tpc.visit_statement_list (*fcn.body ());
          tpc.visit_octave_user_function_trailer (fcn);
          blocks.print (std::cout, "octave jit ir");
        }

      jit_infer infer (conv.get_factory (), conv.get_blocks (),
                       conv.get_variable_map ());
      infer.infer ();

      if (Vdebug_jit)
        {
          jit_block_list& blocks = infer.get_blocks ();
          blocks.label ();
          std::cout << "-------------------- Compiling function ";
          std::cout << "--------------------\n";

          tree_print_code tpc (std::cout);
          tpc.visit_octave_user_function_header (fcn);
          tpc.visit_statement_list (*fcn.body ());
          tpc.visit_octave_user_function_trailer (fcn);
          blocks.print (std::cout, "octave jit ir");
        }

      jit_factory& factory = conv.get_factory ();
      llvm::Module *module = tjit.get_module ();
      jit_convert_llvm to_llvm;
      raw_fn = to_llvm.convert_function (module, infer.get_blocks (),
                                         factory.constants (), fcn,
                                         argument_types);

      if (Vdebug_jit)
        {
          std::cout << "-------------------- raw function ";
          std::cout << "--------------------\n";
          std::cout << *raw_fn.to_llvm () << std::endl;
          llvm::verifyFunction (*raw_fn.to_llvm ());
        }

      std::string wrapper_name = fcn.name () + "_wrapper";
      jit_type *any_t = jit_typeinfo::get_any ();
      std::vector<jit_type *> wrapper_args (1, jit_typeinfo::get_any_ptr ());
      wrapper = jit_function (module,
#ifdef HAVE_GCCJIT
                              gccjit::context (), // FIXME
#endif
                              jit_convention::internal, wrapper_name,
                              any_t, wrapper_args);

      llvm::BasicBlock *wrapper_body = wrapper.new_block ();
      builder.SetInsertPoint (wrapper_body);

      llvm::Value *wrapper_arg = wrapper.argument (builder, 0);
      std::vector<llvm::Value *> raw_args (nargs);
      for (size_t i = 0; i < nargs; ++i)
        {
          llvm::Value *arg;
          arg = builder.CreateConstInBoundsGEP1_32 (wrapper_arg, i);
          arg = builder.CreateLoad (arg);

          jit_type *arg_type = argument_types[i];
          const jit_function& cast = jit_typeinfo::cast (arg_type, any_t);
          raw_args[i] = cast.call (builder, arg);
        }

      llvm::Value *result = raw_fn.call (builder, raw_args);
      if (raw_fn.result ())
        {
          jit_type *raw_result_t = raw_fn.result ();
          const jit_function& cast = jit_typeinfo::cast (any_t, raw_result_t);
          result = cast.call (builder, result);
        }
      else
        {
          llvm::Value *zero = builder.getInt32 (0);
          result = builder.CreateBitCast (zero, any_t->to_llvm ());
        }

      wrapper.do_return (builder, result);

      llvm::Function *llvm_function = wrapper.to_llvm ();
      tjit.optimize (llvm_function);

      if (Vdebug_jit)
        {
          std::cout << "-------------------- optimized and wrapped ";
          std::cout << "--------------------\n";
          std::cout << *llvm_function << std::endl;
          llvm::verifyFunction (*llvm_function);
        }

      llvm::ExecutionEngine* engine = tjit.get_engine ();
      void *void_fn = engine->getPointerToFunction (llvm_function);
      function = reinterpret_cast<jited_function> (void_fn);
    }
  catch (const jit_fail_exception& e)
    {
      argument_types.clear ();

      if (Vdebug_jit)
        {
          if (e.known ())
            std::cout << "jit fail: " << e.what () << std::endl;
        }

      Vjit_failcnt++;

      wrapper.erase ();
      raw_fn.erase ();
    }
}
#endif /* ifdef HAVE_JIT */

bool
jit_function_info::execute (const octave_value_list& ov_args,
                            octave_value_list& retval) const
{
  if (! function)
    return false;

  // TODO figure out a way to delete ov_args so we avoid duplicating refcount
  size_t nargs = ov_args.length ();
  std::vector<octave_base_value *> args (nargs);
  for (size_t i = 0; i < nargs; ++i)
    {
      octave_base_value *obv = ov_args(i).internal_rep ();
      obv->grab ();
      args[i] = obv;
    }

  octave_base_value *ret = function (&args[0]);
  if (ret)
    retval(0) = octave_value (ret);

  octave_quit ();

  return true;
}

bool
jit_function_info::match (const octave_value_list& ov_args) const
{
  if (! function)
    return true;

  size_t nargs = ov_args.length ();
  if (nargs != argument_types.size ())
    return false;

  for (size_t i = 0; i < nargs; ++i)
    if (jit_typeinfo::type_of (ov_args(i)) != argument_types[i])
      return false;

  return true;
}

// -------------------- jit_info --------------------
#ifdef HAVE_LLVM
jit_info::jit_info (tree_jit& tjit, tree& tee)
  : engine (tjit.get_engine ()), function (0), llvm_function (0)
#ifdef HAVE_GCCJIT
    , gccjit_result (NULL)
#endif
{
  compile (tjit, tee);
}

jit_info::jit_info (tree_jit& tjit, tree& tee, const octave_value& for_bounds)
  : engine (tjit.get_engine ()), function (0), llvm_function (0)
#ifdef HAVE_GCCJIT
    , gccjit_result (NULL)
#endif
{
  compile (tjit, tee, jit_typeinfo::type_of (for_bounds));
}

jit_info::~jit_info (void)
{
  if (llvm_function)
    llvm_function->eraseFromParent ();
#ifdef HAVE_GCCJIT
  if (gccjit_result)
    gcc_jit_result_release (gccjit_result);
#endif
}
#endif

bool
jit_info::execute (const vmap& extra_vars) const
{
  if (! function)
    return false;

  std::vector<octave_base_value *> real_arguments (arguments.size ());
  for (size_t i = 0; i < arguments.size (); ++i)
    {
      if (arguments[i].second)
        {
          octave_value current = find (extra_vars, arguments[i].first);
          octave_base_value *obv = current.internal_rep ();
          obv->grab ();
          real_arguments[i] = obv;
        }
    }

  function (&real_arguments[0]);

  for (size_t i = 0; i < arguments.size (); ++i)
    {
      const std::string& name = arguments[i].first;

      // do not store for loop bounds temporary
      if (name.size () && name[0] != '#')
        symbol_table::assign (arguments[i].first, real_arguments[i]);
    }

  octave_quit ();

  return true;
}

bool
jit_info::match (const vmap& extra_vars) const
{
  if (! function)
    return true;

  for (size_t i = 0; i < bounds.size (); ++i)
    {
      const std::string& arg_name = bounds[i].second;
      octave_value value = find (extra_vars, arg_name);
      jit_type *type = jit_typeinfo::type_of (value);

      // FIXME: Check for a parent relationship
      if (type != bounds[i].first)
        return false;
    }

  return true;
}

#ifdef HAVE_LLVM
void
jit_info::compile (tree_jit& tjit, tree& tee, jit_type *for_bounds)
{
  try
    {
      jit_convert conv (tee, for_bounds);

      if (Vdebug_jit)
        {
          jit_block_list& blocks = conv.get_blocks ();
          blocks.label ();
          std::cout << "-------------------- Compiling tree --------------------\n";
          std::cout << tee.str_print_code () << std::endl;
          blocks.print (std::cout, "octave jit ir");
        }

      jit_infer infer (conv.get_factory (), conv.get_blocks (),
                       conv.get_variable_map ());

      infer.infer ();

      if (Vdebug_jit)
        {
          jit_block_list& blocks = infer.get_blocks ();
          blocks.label ();
          std::cout << "-------------------- Compiling tree --------------------\n";
          std::cout << tee.str_print_code () << std::endl;
          blocks.print (std::cout, "octave jit ir");
        }

      jit_factory& factory = conv.get_factory ();

#ifdef HAVE_GCCJIT
      jit_convert_gcc to_gcc;
      gccjit_result = to_gcc.convert_loop (infer.get_blocks (),
                                           factory.constants ());
#endif

      jit_convert_llvm to_llvm;
      llvm_function = to_llvm.convert_loop (tjit.get_module (),
                                            infer.get_blocks (),
                                            factory.constants ());
      arguments = to_llvm.get_arguments ();
      bounds = conv.get_bounds ();
    }
  catch (const jit_fail_exception& e)
    {
      if (Vdebug_jit)
        {
          if (e.known ())
            std::cout << "jit fail: " << e.what () << std::endl;
        }

      Vjit_failcnt++;

    }
#ifdef HAVE_GCCJIT
  if (gccjit_result)
    {
      if (Vdebug_jit)
        {
          std::cout << "-------------------- gccjit --------------------";
          void *void_fn = gcc_jit_result_get_code (gccjit_result, "loop");
          function = reinterpret_cast<jited_function> (void_fn);
        }      
      printf ("using gccjit function\n");
    }
  else
#endif
  if (llvm_function)
    {
      if (Vdebug_jit)
        {
          std::cout << "-------------------- llvm ir --------------------";
          std::cout << *llvm_function << std::endl;
          llvm::verifyFunction (*llvm_function);
        }

      tjit.optimize (llvm_function);

      if (Vdebug_jit)
        {
          std::cout << "-------------------- optimized llvm ir "
                    << "--------------------\n";
          std::cout << *llvm_function << std::endl;
        }

      void *void_fn = engine->getPointerToFunction (llvm_function);
      function = reinterpret_cast<jited_function> (void_fn);
    }
}
#endif

octave_value
jit_info::find (const vmap& extra_vars, const std::string& vname) const
{
  vmap::const_iterator iter = extra_vars.find (vname);
  return iter == extra_vars.end () ? symbol_table::varval (vname)
                                   : *iter->second;
}

#endif /* if HAVE_JIT */

#if defined (HAVE_JIT)
#define UNUSED_WITHOUT_JIT(x) x
#else
#define UNUSED_WITHOUT_JIT(x) x GCC_ATTR_UNUSED
#endif

DEFUN (jit_failcnt, UNUSED_WITHOUT_JIT (args),
       UNUSED_WITHOUT_JIT (nargout),
       "-*- texinfo -*-\n\
@deftypefn  {Built-in Function} {@var{val} =} jit_failcnt ()\n\
@deftypefnx {Built-in Function} {@var{old_val} =} jit_failcnt (@var{new_val})\n\
@deftypefnx {Built-in Function} {} jit_failcnt (@var{new_val}, \"local\")\n\
Query or set the internal variable that counts the number of JIT fail\n\
exceptions for Octave's JIT compiler.\n\
\n\
When called from inside a function with the @qcode{\"local\"} option, the\n\
variable is changed locally for the function and any subroutines it calls.\n\
The original variable value is restored when exiting the function.\n\
@seealso{jit_enable, jit_startcnt, debug_jit}\n\
@end deftypefn")
{
#if defined (HAVE_JIT)
  return SET_INTERNAL_VARIABLE (jit_failcnt);
#else
  warning ("jit_failcnt: JIT compiling not available in this version of Octave");
  return octave_value ();
#endif
}

DEFUN (debug_jit, UNUSED_WITHOUT_JIT (args),
       UNUSED_WITHOUT_JIT (nargout),
       "-*- texinfo -*-\n\
@deftypefn  {Built-in Function} {@var{val} =} debug_jit ()\n\
@deftypefnx {Built-in Function} {@var{old_val} =} debug_jit (@var{new_val})\n\
@deftypefnx {Built-in Function} {} debug_jit (@var{new_val}, \"local\")\n\
Query or set the internal variable that determines whether\n\
debugging/tracing is enabled for Octave's JIT compiler.\n\
\n\
When called from inside a function with the @qcode{\"local\"} option, the\n\
variable is changed locally for the function and any subroutines it calls.\n\
The original variable value is restored when exiting the function.\n\
@seealso{jit_enable, jit_startcnt}\n\
@end deftypefn")
{
#if defined (HAVE_JIT)
  return SET_INTERNAL_VARIABLE (debug_jit);
#else
  warning ("debug_jit: JIT compiling not available in this version of Octave");
  return octave_value ();
#endif
}

DEFUN (jit_enable, UNUSED_WITHOUT_JIT (args),
       UNUSED_WITHOUT_JIT (nargout),
       "-*- texinfo -*-\n\
@deftypefn  {Built-in Function} {@var{val} =} jit_enable ()\n\
@deftypefnx {Built-in Function} {@var{old_val} =} jit_enable (@var{new_val})\n\
@deftypefnx {Built-in Function} {} jit_enable (@var{new_val}, \"local\")\n\
Query or set the internal variable that enables Octave's JIT compiler.\n\
\n\
When called from inside a function with the @qcode{\"local\"} option, the\n\
variable is changed locally for the function and any subroutines it calls.\n\
The original variable value is restored when exiting the function.\n\
@seealso{jit_startcnt, debug_jit}\n\
@end deftypefn")
{
#if defined (HAVE_JIT)
  return SET_INTERNAL_VARIABLE (jit_enable);
#else
  warning ("jit_enable: JIT compiling not available in this version of Octave");
  return octave_value ();
#endif
}

DEFUN (jit_startcnt, UNUSED_WITHOUT_JIT (args),
       UNUSED_WITHOUT_JIT (nargout),
       "-*- texinfo -*-\n\
@deftypefn  {Built-in Function} {@var{val} =} jit_startcnt ()\n\
@deftypefnx {Built-in Function} {@var{old_val} =} jit_startcnt (@var{new_val})\n\
@deftypefnx {Built-in Function} {} jit_startcnt (@var{new_val}, \"local\")\n\
Query or set the internal variable that determines whether JIT compilation\n\
will take place for a specific loop.\n\
\n\
Because compilation is a costly operation it does not make sense to employ\n\
JIT when the loop count is low.  By default only loops with greater than\n\
1000 iterations will be accelerated.\n\
\n\
When called from inside a function with the @qcode{\"local\"} option, the\n\
variable is changed locally for the function and any subroutines it calls.\n\
The original variable value is restored when exiting the function.\n\
@seealso{jit_enable, jit_failcnt, debug_jit}\n\
@end deftypefn")
{
#if defined (HAVE_JIT)
  return SET_INTERNAL_VARIABLE_WITH_LIMITS (jit_startcnt, 1,
                                            std::numeric_limits<int>::max ());
#else
  warning ("jit_enable: JIT compiling not available in this version of Octave");
  return octave_value ();
#endif
}