/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library 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 Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "func.h"
namespace funC {
/*
 *  
 *   ANALYZE AND PREPROCESS ABSTRACT CODE
 * 
 */
void CodeBlob::simplify_var_types() {
  for (TmpVar& var : vars) {
    TypeExpr::remove_indirect(var.v_type);
    var.v_type->recompute_width();
  }
}
int CodeBlob::split_vars(bool strict) {
  int n = var_cnt, changes = 0;
  for (int j = 0; j < var_cnt; j++) {
    TmpVar& var = vars[j];
    if (strict && var.v_type->minw != var.v_type->maxw) {
      throw src::ParseError{var.where.get(), "variable does not have fixed width, cannot manipulate it"};
    }
    std::vector comp_types;
    int k = var.v_type->extract_components(comp_types);
    assert(k <= 254 && n <= 0x7fff00);
    assert((unsigned)k == comp_types.size());
    if (k != 1) {
      var.coord = ~((n << 8) + k);
      for (int i = 0; i < k; i++) {
        auto v = create_var(vars[j].cls, comp_types[i], 0, vars[j].where.get());
        assert(v == n + i);
        assert(vars[v].idx == v);
        vars[v].name = vars[j].name;
        vars[v].coord = ((int)j << 8) + i + 1;
      }
      n += k;
      ++changes;
    } else if (strict && var.v_type->minw != 1) {
      throw src::ParseError{var.where.get(),
                            "cannot work with variable or variable component of width greater than one"};
    }
  }
  if (!changes) {
    return 0;
  }
  for (auto& op : ops) {
    op.split_vars(vars);
  }
  return changes;
}
bool CodeBlob::compute_used_code_vars() {
  VarDescrList empty_var_info;
  return compute_used_code_vars(ops, empty_var_info, true);
}
bool CodeBlob::compute_used_code_vars(std::unique_ptr& ops_ptr, const VarDescrList& var_info, bool edit) const {
  assert(ops_ptr);
  if (!ops_ptr->next) {
    assert(ops_ptr->cl == Op::_Nop);
    return ops_ptr->set_var_info(var_info);
  }
  return compute_used_code_vars(ops_ptr->next, var_info, edit) | ops_ptr->compute_used_vars(*this, edit);
}
bool operator==(const VarDescrList& x, const VarDescrList& y) {
  if (x.size() != y.size()) {
    return false;
  }
  for (std::size_t i = 0; i < x.size(); i++) {
    if (x.list[i].idx != y.list[i].idx || x.list[i].flags != y.list[i].flags) {
      return false;
    }
  }
  return true;
}
bool same_values(const VarDescr& x, const VarDescr& y) {
  if (x.val != y.val || x.int_const.is_null() != y.int_const.is_null()) {
    return false;
  }
  if (x.int_const.not_null() && cmp(x.int_const, y.int_const) != 0) {
    return false;
  }
  return true;
}
bool same_values(const VarDescrList& x, const VarDescrList& y) {
  if (x.size() != y.size()) {
    return false;
  }
  for (std::size_t i = 0; i < x.size(); i++) {
    if (x.list[i].idx != y.list[i].idx || !same_values(x.list[i], y.list[i])) {
      return false;
    }
  }
  return true;
}
bool Op::set_var_info(const VarDescrList& new_var_info) {
  if (var_info == new_var_info) {
    return false;
  }
  var_info = new_var_info;
  return true;
}
bool Op::set_var_info(VarDescrList&& new_var_info) {
  if (var_info == new_var_info) {
    return false;
  }
  var_info = std::move(new_var_info);
  return true;
}
bool Op::set_var_info_except(const VarDescrList& new_var_info, const std::vector& var_list) {
  if (!var_list.size()) {
    return set_var_info(new_var_info);
  }
  VarDescrList tmp_info{new_var_info};
  tmp_info -= var_list;
  return set_var_info(tmp_info);
}
bool Op::set_var_info_except(VarDescrList&& new_var_info, const std::vector& var_list) {
  if (var_list.size()) {
    new_var_info -= var_list;
  }
  return set_var_info(std::move(new_var_info));
}
std::vector sort_unique_vars(const std::vector& var_list) {
  std::vector vars{var_list}, unique_vars;
  std::sort(vars.begin(), vars.end());
  vars.erase(std::unique(vars.begin(), vars.end()), vars.end());
  return vars;
}
VarDescr* VarDescrList::operator[](var_idx_t idx) {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  return it != list.end() && it->idx == idx ? &*it : nullptr;
}
const VarDescr* VarDescrList::operator[](var_idx_t idx) const {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  return it != list.end() && it->idx == idx ? &*it : nullptr;
}
std::size_t VarDescrList::count(const std::vector idx_list) const {
  std::size_t res = 0;
  for (var_idx_t idx : idx_list) {
    if (operator[](idx)) {
      ++res;
    }
  }
  return res;
}
std::size_t VarDescrList::count_used(const std::vector idx_list) const {
  std::size_t res = 0;
  for (var_idx_t idx : idx_list) {
    auto v = operator[](idx);
    if (v && !v->is_unused()) {
      ++res;
    }
  }
  return res;
}
VarDescrList& VarDescrList::operator-=(var_idx_t idx) {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  if (it != list.end() && it->idx == idx) {
    list.erase(it);
  }
  return *this;
}
VarDescrList& VarDescrList::operator-=(const std::vector& idx_list) {
  for (var_idx_t idx : idx_list) {
    *this -= idx;
  }
  return *this;
}
VarDescrList& VarDescrList::add_var(var_idx_t idx, bool unused) {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  if (it == list.end() || it->idx != idx) {
    list.emplace(it, idx, VarDescr::_Last | (unused ? VarDescr::_Unused : 0));
  } else if (it->is_unused() && !unused) {
    it->clear_unused();
  }
  return *this;
}
VarDescrList& VarDescrList::add_vars(const std::vector& idx_list, bool unused) {
  for (var_idx_t idx : idx_list) {
    add_var(idx, unused);
  }
  return *this;
}
VarDescr& VarDescrList::add(var_idx_t idx) {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  if (it == list.end() || it->idx != idx) {
    it = list.emplace(it, idx);
  }
  return *it;
}
VarDescr& VarDescrList::add_newval(var_idx_t idx) {
  auto it = std::lower_bound(list.begin(), list.end(), idx);
  if (it == list.end() || it->idx != idx) {
    return *list.emplace(it, idx);
  } else {
    it->clear_value();
    return *it;
  }
}
VarDescrList& VarDescrList::clear_last() {
  for (auto& var : list) {
    if (var.flags & VarDescr::_Last) {
      var.flags &= ~VarDescr::_Last;
    }
  }
  return *this;
}
VarDescrList VarDescrList::operator+(const VarDescrList& y) const {
  VarDescrList res;
  auto it1 = list.cbegin();
  auto it2 = y.list.cbegin();
  while (it1 != list.cend() && it2 != y.list.cend()) {
    if (it1->idx < it2->idx) {
      res.list.push_back(*it1++);
    } else if (it1->idx > it2->idx) {
      res.list.push_back(*it2++);
    } else {
      res.list.push_back(*it1++);
      res.list.back() += *it2++;
    }
  }
  while (it1 != list.cend()) {
    res.list.push_back(*it1++);
  }
  while (it2 != y.list.cend()) {
    res.list.push_back(*it2++);
  }
  return res;
}
VarDescrList& VarDescrList::operator+=(const VarDescrList& y) {
  return *this = *this + y;
}
VarDescrList VarDescrList::operator|(const VarDescrList& y) const {
  if (y.unreachable) {
    return *this;
  }
  if (unreachable) {
    return y;
  }
  VarDescrList res;
  auto it1 = list.cbegin();
  auto it2 = y.list.cbegin();
  while (it1 != list.cend() && it2 != y.list.cend()) {
    if (it1->idx < it2->idx) {
      it1++;
    } else if (it1->idx > it2->idx) {
      it2++;
    } else {
      res.list.push_back(*it1++);
      res.list.back() |= *it2++;
    }
  }
  return res;
}
VarDescrList& VarDescrList::operator|=(const VarDescrList& y) {
  if (y.unreachable) {
    return *this;
  } else {
    return *this = *this | y;
  }
}
VarDescrList& VarDescrList::operator&=(const VarDescrList& values) {
  for (const VarDescr& vd : values.list) {
    VarDescr* item = operator[](vd.idx);
    if (item) {
      *item &= vd;
    }
  }
  unreachable |= values.unreachable;
  return *this;
}
VarDescrList& VarDescrList::import_values(const VarDescrList& values) {
  if (values.unreachable) {
    set_unreachable();
  } else
    for (auto& vd : list) {
      auto new_vd = values[vd.idx];
      if (new_vd) {
        vd.set_value(*new_vd);
      } else {
        vd.clear_value();
      }
    }
  return *this;
}
bool Op::std_compute_used_vars(bool disabled) {
  // left = OP right
  // var_info := (var_info - left) + right
  VarDescrList new_var_info{next->var_info};
  new_var_info -= left;
  new_var_info.clear_last();
  if (args.size() == right.size() && !disabled) {
    for (const VarDescr& arg : args) {
      new_var_info.add_var(arg.idx, arg.is_unused());
    }
  } else {
    new_var_info.add_vars(right, disabled);
  }
  return set_var_info(std::move(new_var_info));
}
bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
  assert(next);
  const VarDescrList& next_var_info = next->var_info;
  if (cl == _Nop) {
    return set_var_info_except(next_var_info, left);
  }
  switch (cl) {
    case _IntConst:
    case _SliceConst:
    case _GlobVar:
    case _Call:
    case _CallInd:
    case _Tuple:
    case _UnTuple: {
      // left = EXEC right;
      if (!next_var_info.count_used(left) && is_pure()) {
        // all variables in `left` are not needed
        if (edit) {
          disable();
        }
        return std_compute_used_vars(true);
      }
      return std_compute_used_vars();
    }
    case _SetGlob: {
      // GLOB = right
      if (right.empty() && edit) {
        disable();
      }
      return std_compute_used_vars(right.empty());
    }
    case _Let: {
      // left = right
      std::size_t cnt = next_var_info.count_used(left);
      assert(left.size() == right.size());
      auto l_it = left.cbegin(), r_it = right.cbegin();
      VarDescrList new_var_info{next_var_info};
      new_var_info -= left;
      new_var_info.clear_last();
      std::vector new_left, new_right;
      for (; l_it < left.cend(); ++l_it, ++r_it) {
        if (std::find(l_it + 1, left.cend(), *l_it) == left.cend()) {
          auto p = next_var_info[*l_it];
          new_var_info.add_var(*r_it, !p || p->is_unused());
          new_left.push_back(*l_it);
          new_right.push_back(*r_it);
        }
      }
      if (new_left.size() < left.size()) {
        left = std::move(new_left);
        right = std::move(new_right);
      }
      if (!cnt && edit) {
        // all variables in `left` are not needed
        disable();
      }
      return set_var_info(std::move(new_var_info));
    }
    case _Return: {
      // return left
      if (var_info.count(left) == left.size()) {
        return false;
      }
      std::vector unique_vars = sort_unique_vars(left);
      var_info.list.clear();
      for (var_idx_t i : unique_vars) {
        var_info.list.emplace_back(i, VarDescr::_Last);
      }
      return true;
    }
    case _Import: {
      // import left
      std::vector unique_vars = sort_unique_vars(left);
      var_info.list.clear();
      for (var_idx_t i : unique_vars) {
        var_info.list.emplace_back(i, next_var_info[i] ? 0 : VarDescr::_Last);
      }
      return true;
    }
    case _If: {
      // if (left) then block0 else block1
      //  VarDescrList nx_var_info = next_var_info;
      //  nx_var_info.clear_last();
      code.compute_used_code_vars(block0, next_var_info, edit);
      VarDescrList merge_info;
      if (block1) {
        code.compute_used_code_vars(block1, next_var_info, edit);
        merge_info = block0->var_info + block1->var_info;
      } else {
        merge_info = block0->var_info + next_var_info;
      }
      merge_info.clear_last();
      merge_info += left;
      return set_var_info(std::move(merge_info));
    }
    case _While: {
      // while (block0 || left) block1;
      // ... block0 left { block1 block0 left } next
      VarDescrList new_var_info{next_var_info};
      bool changes = false;
      do {
        VarDescrList after_cond{new_var_info};
        after_cond += left;
        code.compute_used_code_vars(block0, after_cond, changes);
        code.compute_used_code_vars(block1, block0->var_info, changes);
        std::size_t n = new_var_info.size();
        new_var_info += block1->var_info;
        new_var_info.clear_last();
        if (changes) {
          break;
        }
        changes = (new_var_info.size() == n);
      } while (changes <= edit);
      new_var_info += left;
      code.compute_used_code_vars(block0, new_var_info, edit);
      return set_var_info(block0->var_info);
    }
    case _Until: {
      // until (block0 || left);
      // .. { block0 left } block0 left next
      VarDescrList after_cond_first{next_var_info};
      after_cond_first += left;
      code.compute_used_code_vars(block0, after_cond_first, false);
      VarDescrList new_var_info{block0->var_info};
      bool changes = false;
      do {
        VarDescrList after_cond{new_var_info};
        after_cond += next_var_info;
        after_cond += left;
        code.compute_used_code_vars(block0, after_cond, changes);
        std::size_t n = new_var_info.size();
        new_var_info += block0->var_info;
        new_var_info.clear_last();
        if (changes) {
          break;
        }
        changes = (new_var_info.size() == n);
      } while (changes <= edit);
      return set_var_info(std::move(new_var_info) + next_var_info);
    }
    case _Repeat: {
      // repeat (left) block0
      // left { block0 } next
      VarDescrList new_var_info{next_var_info};
      bool changes = false;
      do {
        code.compute_used_code_vars(block0, new_var_info, changes);
        std::size_t n = new_var_info.size();
        new_var_info += block0->var_info;
        new_var_info.clear_last();
        if (changes) {
          break;
        }
        changes = (new_var_info.size() == n);
      } while (changes <= edit);
      new_var_info += left;
      return set_var_info(std::move(new_var_info));
    }
    case _Again: {
      // for(;;) block0
      // { block0 }
      VarDescrList new_var_info;
      bool changes = false;
      do {
        code.compute_used_code_vars(block0, new_var_info, changes);
        std::size_t n = new_var_info.size();
        new_var_info += block0->var_info;
        new_var_info.clear_last();
        if (changes) {
          break;
        }
        changes = (new_var_info.size() == n);
      } while (changes <= edit);
      return set_var_info(std::move(new_var_info));
    }
    case _TryCatch: {
      code.compute_used_code_vars(block0, next_var_info, edit);
      code.compute_used_code_vars(block1, next_var_info, edit);
      VarDescrList merge_info = block0->var_info + block1->var_info + next_var_info;
      merge_info -= left;
      merge_info.clear_last();
      return set_var_info(std::move(merge_info));
    }
    default:
      std::cerr << "fatal: unknown operation ?" << cl << "> in compute_used_vars()\n";
      throw src::ParseError{where, "unknown operation"};
  }
}
bool prune_unreachable(std::unique_ptr& ops) {
  if (!ops) {
    return true;
  }
  Op& op = *ops;
  if (op.cl == Op::_Nop) {
    if (op.next) {
      ops = std::move(op.next);
      return prune_unreachable(ops);
    }
    return true;
  }
  bool reach;
  switch (op.cl) {
    case Op::_IntConst:
    case Op::_SliceConst:
    case Op::_GlobVar:
    case Op::_SetGlob:
    case Op::_Call:
    case Op::_CallInd:
    case Op::_Tuple:
    case Op::_UnTuple:
    case Op::_Import:
      reach = true;
      break;
    case Op::_Let: {
      reach = true;
      break;
    }
    case Op::_Return:
      reach = false;
      break;
    case Op::_If: {
      // if left then block0 else block1; ...
      VarDescr* c_var = op.var_info[op.left[0]];
      if (c_var && c_var->always_true()) {
        op.block0->last().next = std::move(op.next);
        ops = std::move(op.block0);
        return prune_unreachable(ops);
      } else if (c_var && c_var->always_false()) {
        op.block1->last().next = std::move(op.next);
        ops = std::move(op.block1);
        return prune_unreachable(ops);
      } else {
        reach = prune_unreachable(op.block0) | prune_unreachable(op.block1);
      }
      break;
    }
    case Op::_While: {
      // while (block0 || left) block1;
      if (!prune_unreachable(op.block0)) {
        // computation of block0 never returns
        ops = std::move(op.block0);
        return prune_unreachable(ops);
      }
      VarDescr* c_var = op.block0->last().var_info[op.left[0]];
      if (c_var && c_var->always_false()) {
        // block1 never executed
        op.block0->last().next = std::move(op.next);
        ops = std::move(op.block0);
        return prune_unreachable(ops);
      } else if (c_var && c_var->always_true()) {
        if (!prune_unreachable(op.block1)) {
          // block1 never returns
          op.block0->last().next = std::move(op.block1);
          ops = std::move(op.block0);
          return false;
        }
        // infinite loop
        op.cl = Op::_Again;
        op.block0->last().next = std::move(op.block1);
        op.left.clear();
        reach = false;
      } else {
        if (!prune_unreachable(op.block1)) {
          // block1 never returns, while equivalent to block0 ; if left then block1 else next
          op.cl = Op::_If;
          std::unique_ptr new_op = std::move(op.block0);
          op.block0 = std::move(op.block1);
          op.block1 = std::make_unique(op.next->where, Op::_Nop);
          new_op->last().next = std::move(ops);
          ops = std::move(new_op);
        }
        reach = true;  // block1 may be never executed
      }
      break;
    }
    case Op::_Repeat: {
      // repeat (left) block0
      VarDescr* c_var = op.var_info[op.left[0]];
      if (c_var && c_var->always_nonpos()) {
        // loop never executed
        ops = std::move(op.next);
        return prune_unreachable(ops);
      }
      if (c_var && c_var->always_pos()) {
        if (!prune_unreachable(op.block0)) {
          // block0 executed at least once, and it never returns
          // replace code with block0
          ops = std::move(op.block0);
          return false;
        }
      } else {
        prune_unreachable(op.block0);
      }
      reach = true;
      break;
    }
    case Op::_Until:
    case Op::_Again: {
      // do block0 until left; ...
      if (!prune_unreachable(op.block0)) {
        // block0 never returns, replace loop by block0
        ops = std::move(op.block0);
        return false;
      }
      reach = true;
      break;
    }
    case Op::_TryCatch: {
      reach = prune_unreachable(op.block0) | prune_unreachable(op.block1);
      break;
    }
    default:
      std::cerr << "fatal: unknown operation ?" << op.cl << ">\n";
      throw src::ParseError{op.where, "unknown operation in prune_unreachable()"};
  }
  if (reach) {
    return prune_unreachable(op.next);
  } else {
    while (op.next->next) {
      op.next = std::move(op.next->next);
    }
    return false;
  }
}
void CodeBlob::prune_unreachable_code() {
  if (prune_unreachable(ops)) {
    throw src::ParseError{loc, "control reaches end of function"};
  }
}
void CodeBlob::fwd_analyze() {
  VarDescrList values;
  assert(ops && ops->cl == Op::_Import);
  for (var_idx_t i : ops->left) {
    values += i;
    if (vars[i].v_type->is_int()) {
      values[i]->val |= VarDescr::_Int;
    }
  }
  ops->fwd_analyze(values);
}
void Op::prepare_args(VarDescrList values) {
  if (args.size() != right.size()) {
    args.clear();
    for (var_idx_t i : right) {
      args.emplace_back(i);
    }
  }
  for (std::size_t i = 0; i < right.size(); i++) {
    const VarDescr* val = values[right[i]];
    if (val) {
      args[i].set_value(*val);
      // args[i].clear_unused();
    } else {
      args[i].clear_value();
    }
    args[i].clear_unused();
  }
}
VarDescrList Op::fwd_analyze(VarDescrList values) {
  var_info.import_values(values);
  switch (cl) {
    case _Nop:
    case _Import:
      break;
    case _Return:
      values.set_unreachable();
      break;
    case _IntConst: {
      values.add_newval(left[0]).set_const(int_const);
      break;
    }
    case _SliceConst: {
      values.add_newval(left[0]).set_const(str_const);
      break;
    }
    case _Call: {
      prepare_args(values);
      auto func = dynamic_cast(fun_ref->value);
      if (func) {
        std::vector res;
        res.reserve(left.size());
        for (var_idx_t i : left) {
          res.emplace_back(i);
        }
        AsmOpList tmp;
        func->compile(tmp, res, args);  // abstract interpretation of res := f (args)
        int j = 0;
        for (var_idx_t i : left) {
          values.add_newval(i).set_value(res[j++]);
        }
      } else {
        for (var_idx_t i : left) {
          values.add_newval(i);
        }
      }
      break;
    }
    case _Tuple:
    case _UnTuple:
    case _GlobVar:
    case _CallInd: {
      for (var_idx_t i : left) {
        values.add_newval(i);
      }
      break;
    }
    case _SetGlob:
      break;
    case _Let: {
      std::vector old_val;
      assert(left.size() == right.size());
      for (std::size_t i = 0; i < right.size(); i++) {
        const VarDescr* ov = values[right[i]];
        if (!ov && verbosity >= 5) {
          std::cerr << "FATAL: error in assignment at right component #" << i << " (no value for _" << right[i] << ")"
                    << std::endl;
          for (auto x : left) {
            std::cerr << '_' << x << " ";
          }
          std::cerr << "= ";
          for (auto x : right) {
            std::cerr << '_' << x << " ";
          }
          std::cerr << std::endl;
        }
        // assert(ov);
        if (ov) {
          old_val.push_back(*ov);
        } else {
          old_val.emplace_back();
        }
      }
      for (std::size_t i = 0; i < left.size(); i++) {
        values.add_newval(left[i]).set_value(std::move(old_val[i]));
      }
      break;
    }
    case _If: {
      VarDescrList val1 = block0->fwd_analyze(values);
      VarDescrList val2 = block1 ? block1->fwd_analyze(std::move(values)) : std::move(values);
      values = val1 | val2;
      break;
    }
    case _Repeat: {
      bool atl1 = (values[left[0]] && values[left[0]]->always_pos());
      VarDescrList next_values = block0->fwd_analyze(values);
      while (true) {
        VarDescrList new_values = values | next_values;
        if (same_values(new_values, values)) {
          break;
        }
        values = std::move(new_values);
        next_values = block0->fwd_analyze(values);
      }
      if (atl1) {
        values = std::move(next_values);
      }
      break;
    }
    case _While: {
      auto values0 = values;
      values = block0->fwd_analyze(values);
      if (values[left[0]] && values[left[0]]->always_false()) {
        // block1 never executed
        block1->fwd_analyze(values);
        break;
      }
      while (true) {
        VarDescrList next_values = values | block0->fwd_analyze(values0 | block1->fwd_analyze(values));
        if (same_values(next_values, values)) {
          break;
        }
        values = std::move(next_values);
      }
      break;
    }
    case _Until:
    case _Again: {
      while (true) {
        VarDescrList next_values = values | block0->fwd_analyze(values);
        if (same_values(next_values, values)) {
          break;
        }
        values = std::move(next_values);
      }
      values = block0->fwd_analyze(values);
      break;
    }
    case _TryCatch: {
      VarDescrList val1 = block0->fwd_analyze(values);
      VarDescrList val2 = block1->fwd_analyze(std::move(values));
      values = val1 | val2;
      break;
    }
    default:
      std::cerr << "fatal: unknown operation ?" << cl << ">\n";
      throw src::ParseError{where, "unknown operation in fwd_analyze()"};
  }
  if (next) {
    return next->fwd_analyze(std::move(values));
  } else {
    return values;
  }
}
bool Op::set_noreturn(bool nr) {
  if (nr) {
    flags |= _NoReturn;
  } else {
    flags &= ~_NoReturn;
  }
  return nr;
}
bool Op::mark_noreturn() {
  switch (cl) {
    case _Nop:
      if (!next) {
        return set_noreturn(false);
      }
      // fallthrough
    case _Import:
    case _IntConst:
    case _SliceConst:
    case _Let:
    case _Tuple:
    case _UnTuple:
    case _SetGlob:
    case _GlobVar:
    case _CallInd:
    case _Call:
      return set_noreturn(next->mark_noreturn());
    case _Return:
      return set_noreturn(true);
    case _If:
    case _TryCatch:
      return set_noreturn((block0->mark_noreturn() & (block1 && block1->mark_noreturn())) | next->mark_noreturn());
    case _Again:
      block0->mark_noreturn();
      return set_noreturn(false);
    case _Until:
      return set_noreturn(block0->mark_noreturn() | next->mark_noreturn());
    case _While:
      block1->mark_noreturn();
      return set_noreturn(block0->mark_noreturn() | next->mark_noreturn());
    case _Repeat:
      block0->mark_noreturn();
      return set_noreturn(next->mark_noreturn());
    default:
      std::cerr << "fatal: unknown operation ?" << cl << ">\n";
      throw src::ParseError{where, "unknown operation in mark_noreturn()"};
  }
}
void CodeBlob::mark_noreturn() {
  ops->mark_noreturn();
}
}  // namespace funC