Example #1
0
adapt_type::adapt_type(const ndt::type &operand_type,
                       const ndt::type &value_type, const nd::string &op)
    : base_expr_type(
          adapt_type_id, expr_kind, operand_type.get_data_size(),
          operand_type.get_data_alignment(),
          inherited_flags(value_type.get_flags(), operand_type.get_flags()), 0),
      m_value_type(value_type), m_operand_type(operand_type), m_op(op)
{
  if (!value_type.is_builtin() &&
      value_type.extended()->adapt_type(operand_type.value_type(), op,
                                        m_forward, m_reverse)) {
  } else if (!operand_type.value_type().is_builtin() &&
             operand_type.value_type().extended()->reverse_adapt_type(
                 value_type, op, m_forward, m_reverse)) {
  } else {
    stringstream ss;
    ss << "Cannot create type ";
    print_type(ss);
    throw type_error(ss.str());
  }

  // If the operand is an expression, make a buffering arrfunc
  if (m_operand_type.get_kind() == expr_kind && !m_forward.is_null() &&
      m_operand_type != m_forward.get_type()->get_arg_type(0)) {
    m_forward = make_chain_arrfunc(
        make_arrfunc_from_assignment(m_forward.get_type()->get_arg_type(0),
                                     m_operand_type, assign_error_default),
        m_forward, m_forward.get_type()->get_arg_type(0));
  }
}
Example #2
0
ndt::type view_type::with_replaced_storage_type(const ndt::type& replacement_type) const
{
    if (m_operand_type.get_kind() == expression_kind) {
        return ndt::type(new view_type(m_value_type,
                        static_cast<const base_expression_type *>(m_operand_type.extended())->with_replaced_storage_type(replacement_type)), false);
    } else {
        if (m_operand_type != replacement_type.value_type()) {
            std::stringstream ss;
            ss << "Cannot chain types, because the view's storage type, " << m_operand_type;
            ss << ", does not match the replacement's value type, " << replacement_type.value_type();
            throw std::runtime_error(ss.str());
        }
        return ndt::type(new view_type(m_value_type, replacement_type), false);
    }
}
Example #3
0
view_type::view_type(const ndt::type& value_type, const ndt::type& operand_type)
    : base_expression_type(view_type_id, expression_kind, operand_type.get_data_size(),
                    operand_type.get_data_alignment(),
                    inherited_flags(value_type.get_flags(), operand_type.get_flags()),
                    operand_type.get_metadata_size()),
            m_value_type(value_type), m_operand_type(operand_type)
{
    if (value_type.get_data_size() != operand_type.value_type().get_data_size()) {
        std::stringstream ss;
        ss << "view_type: Cannot view " << operand_type.value_type() << " as " << value_type << " because they have different sizes";
        throw std::runtime_error(ss.str());
    }
    if (!value_type.is_pod()) {
        throw std::runtime_error("view_type: Only POD types are supported");
    }
}
Example #4
0
byteswap_type::byteswap_type(const ndt::type& value_type, const ndt::type& operand_type)
    : base_expr_type(byteswap_type_id, expr_kind, operand_type.get_data_size(),
                    operand_type.get_data_alignment(), type_flag_scalar, 0),
            m_value_type(value_type), m_operand_type(operand_type)
{
    // Only a bytes type be the operand to the byteswap
    if (operand_type.value_type().get_type_id() != fixedbytes_type_id) {
        std::stringstream ss;
        ss << "byteswap_type: The operand to the type must have a value type of bytes, not " << operand_type.value_type();
        throw dynd::type_error(ss.str());
    }
    // Automatically realign if needed
    if (operand_type.value_type().get_data_alignment() < value_type.get_data_alignment()) {
        m_operand_type = ndt::make_view(operand_type,
                        ndt::make_fixedbytes(operand_type.get_data_size(), value_type.get_data_alignment()));
    }
}
Example #5
0
 /**
  * Makes a conversion type to convert from the operand_type to the value_type.
  * If the value_type has expression_kind, it chains operand_type.value_type()
  * into value_type.storage_type().
  */
 inline ndt::type make_convert(const ndt::type& value_type, const ndt::type& operand_type,
                 assign_error_mode errmode = assign_error_default) {
     if (operand_type.value_type() != value_type) {
         if (value_type.get_kind() != expression_kind) {
             // Create a conversion type when the value kind is different
             return ndt::type(new convert_type(value_type, operand_type, errmode), false);
         } else if (value_type.storage_type() == operand_type.value_type()) {
             // No conversion required at the connection
             return static_cast<const base_expression_type *>(
                             value_type.extended())->with_replaced_storage_type(operand_type);
         } else {
             // A conversion required at the connection
             return static_cast<const base_expression_type *>(
                             value_type.extended())->with_replaced_storage_type(
                                 ndt::type(new convert_type(
                                     value_type.storage_type(), operand_type, errmode), false));
         }
     } else {
         return operand_type;
     }
 }
Example #6
0
ndt::type convert_type::with_replaced_storage_type(const ndt::type& replacement_type) const
{
    if (m_operand_type.get_kind() == expr_kind) {
        return ndt::type(
            new convert_type(
                m_value_type,
                m_operand_type.tcast<base_expr_type>()
                    ->with_replaced_storage_type(replacement_type)),
            false);
    } else {
        if (m_operand_type != replacement_type.value_type()) {
            std::stringstream ss;
            ss << "Cannot chain expression types, because the conversion's "
                  "storage type, " << m_operand_type
               << ", does not match the replacement's value type, "
               << replacement_type.value_type();
            throw std::runtime_error(ss.str());
        }
        return ndt::type(new convert_type(m_value_type, replacement_type),
                         false);
    }
}
Example #7
0
void nd::detail::check_arg(const ndt::callable_type *af_tp, intptr_t i,
                           const ndt::type &actual_tp,
                           const char *actual_arrmeta,
                           std::map<nd::string, ndt::type> &tp_vars)
{
  ndt::type expected_tp = af_tp->get_pos_type(i);
  if (!expected_tp.match(NULL, actual_tp.value_type(), actual_arrmeta,
                         tp_vars)) {
    std::stringstream ss;
    ss << "positional argument " << i << " to callable does not match, ";
    ss << "expected " << expected_tp << ", received " << actual_tp;
    throw std::invalid_argument(ss.str());
  }
}
size_t dynd::make_expression_comparison_kernel(
                ckernel_builder *out, size_t offset_out,
                const ndt::type& src0_dt, const char *src0_metadata,
                const ndt::type& src1_dt, const char *src1_metadata,
                comparison_type_t comptype,
                const eval::eval_context *ectx)
{
    size_t current_offset = offset_out + sizeof(buffered_kernel_extra);
    out->ensure_capacity(current_offset);
    buffered_kernel_extra *e = out->get_at<buffered_kernel_extra>(offset_out);
    e->base.set_function<binary_single_predicate_t>(&buffered_kernel_extra::kernel);
    e->base.destructor = &buffered_kernel_extra::destruct;
    // Initialize the information for buffering the operands
    if (src0_dt.get_kind() == expression_kind) {
        e->init_buffer(0, src0_dt.value_type());
        e->buf[0].kernel_offset = current_offset - offset_out;
        current_offset = make_assignment_kernel(out, current_offset,
                        src0_dt.value_type(), e->buf[0].metadata,
                        src0_dt, src0_metadata,
                        kernel_request_single, assign_error_none, ectx);
        // Have to re-retrieve 'e', because creating another kernel may invalidate it
        e = out->get_at<buffered_kernel_extra>(offset_out);
    }
    if (src1_dt.get_kind() == expression_kind) {
        e->init_buffer(1, src1_dt.value_type());
        e->buf[1].kernel_offset = current_offset - offset_out;
        current_offset = make_assignment_kernel(out, current_offset,
                        src1_dt.value_type(), e->buf[1].metadata,
                        src1_dt, src1_metadata,
                        kernel_request_single, assign_error_none, ectx);
        // Have to re-retrieve 'e', because creating another kernel may invalidate it
        e = out->get_at<buffered_kernel_extra>(offset_out);
    }
    // Allocate the data for the buffers
    if (e->buf[0].kernel_offset != 0) {
        current_offset = inc_to_alignment(current_offset, src0_dt.get_data_alignment());
        e->buf[0].data_offset = current_offset - offset_out;
        current_offset += e->buf[0].data_size;
    }
    if (e->buf[1].kernel_offset != 0) {
        current_offset = inc_to_alignment(current_offset, src1_dt.get_data_alignment());
        e->buf[1].data_offset = current_offset - offset_out;
        current_offset += e->buf[1].data_size;
    }
    out->ensure_capacity(current_offset);
    // Have to re-retrieve 'e', because allocating the buffer data may invalidate it
    e = out->get_at<buffered_kernel_extra>(offset_out);
    e->cmp_kernel_offset = current_offset - offset_out;
    return make_comparison_kernel(out, current_offset,
                    src0_dt.value_type(),
                    (e->buf[0].kernel_offset != 0) ? e->buf[0].metadata : src0_metadata,
                    src1_dt.value_type(),
                    (e->buf[1].kernel_offset != 0) ? e->buf[1].metadata : src1_metadata,
                    comptype, ectx);
}
size_t categorical_type::make_assignment_kernel(
    ckernel_builder *ckb, intptr_t ckb_offset, const ndt::type &dst_tp,
    const char *dst_arrmeta, const ndt::type &src_tp, const char *src_arrmeta,
    kernel_request_t kernreq, const eval::eval_context *ectx) const
{
  if (this == dst_tp.extended()) {
    if (this == src_tp.extended()) {
      // When assigning identical types, just use a POD copy
      return make_pod_typed_data_assignment_kernel(
          ckb, ckb_offset, get_data_size(), get_data_alignment(), kernreq);
    }
    // try to assign from another categorical type if it can be mapped
    else if (src_tp.get_type_id() == categorical_type_id) {
      // out_kernel.specializations =
      // assign_from_commensurate_category_specializations;
      // TODO auxdata
      throw std::runtime_error(
          "assignment between different categorical types isn't supported yet");
    }
    // assign from the same category value type
    else if (src_tp == m_category_tp) {
      ckb_offset =
          make_kernreq_to_single_kernel_adapter(ckb, ckb_offset, 1, kernreq);
      category_to_categorical_kernel_extra *e =
          ckb->alloc_ck_leaf<category_to_categorical_kernel_extra>(ckb_offset);
      switch (m_storage_type.get_type_id()) {
      case uint8_type_id:
        e->base.set_function<expr_single_t>(
            &category_to_categorical_kernel_extra::single_uint8);
        break;
      case uint16_type_id:
        e->base.set_function<expr_single_t>(
            &category_to_categorical_kernel_extra::single_uint16);
        break;
      case uint32_type_id:
        e->base.set_function<expr_single_t>(
            &category_to_categorical_kernel_extra::single_uint32);
        break;
      default:
        throw runtime_error(
            "internal error in categorical_type::make_assignment_kernel");
      }
      e->base.destructor = &category_to_categorical_kernel_extra::destruct;
      // The kernel type owns a reference to this type
      e->dst_cat_tp =
          static_cast<const categorical_type *>(ndt::type(dst_tp).release());
      e->src_arrmeta = src_arrmeta;
      return ckb_offset;
    } else if (src_tp.value_type() != m_category_tp &&
               src_tp.value_type().get_type_id() != categorical_type_id) {
      // Make a convert type to the category type, and have it do the chaining
      ndt::type src_cvt_tp = ndt::make_convert(m_category_tp, src_tp);
      return src_cvt_tp.extended()->make_assignment_kernel(
          ckb, ckb_offset, dst_tp, dst_arrmeta, src_cvt_tp, src_arrmeta,
          kernreq, ectx);
    } else {
      // Let the src_tp handle it
      return src_tp.extended()->make_assignment_kernel(
          ckb, ckb_offset, dst_tp, dst_arrmeta, src_tp, src_arrmeta, kernreq,
          ectx);
    }
  } else {
    if (dst_tp.value_type().get_type_id() != categorical_type_id) {
      ckb_offset =
          make_kernreq_to_single_kernel_adapter(ckb, ckb_offset, 1, kernreq);
      categorical_to_other_kernel_extra *e =
          ckb->alloc_ck<categorical_to_other_kernel_extra>(ckb_offset);
      switch (m_storage_type.get_type_id()) {
      case uint8_type_id:
        e->base.set_function<expr_single_t>(
            &categorical_to_other_kernel_extra::single_uint8);
        break;
      case uint16_type_id:
        e->base.set_function<expr_single_t>(
            &categorical_to_other_kernel_extra::single_uint16);
        break;
      case uint32_type_id:
        e->base.set_function<expr_single_t>(
            &categorical_to_other_kernel_extra::single_uint32);
        break;
      default:
        throw runtime_error(
            "internal error in categorical_type::make_assignment_kernel");
      }
      e->base.destructor = &categorical_to_other_kernel_extra::destruct;
      // The kernel type owns a reference to this type
      e->src_cat_tp =
          static_cast<const categorical_type *>(ndt::type(src_tp).release());
      return ::make_assignment_kernel(
          ckb, ckb_offset, dst_tp, dst_arrmeta, get_category_type(),
          get_category_arrmeta(), kernel_request_single, ectx);
    } else {
      stringstream ss;
      ss << "Cannot assign from " << src_tp << " to " << dst_tp;
      throw runtime_error(ss.str());
    }
  }
}
size_t dynd::make_expression_comparison_kernel(void *ckb, intptr_t ckb_offset,
                                               const ndt::type &src0_dt,
                                               const char *src0_arrmeta,
                                               const ndt::type &src1_dt,
                                               const char *src1_arrmeta,
                                               comparison_type_t comptype,
                                               const eval::eval_context *ectx)
{
  intptr_t root_ckb_offset = ckb_offset;
  buffered_kernel_extra *e =
      reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
          ->alloc_ck<buffered_kernel_extra>(ckb_offset);
  e->base.function = reinterpret_cast<void *>(&buffered_kernel_extra::kernel);
  e->base.destructor = &buffered_kernel_extra::destruct;
  // Initialize the information for buffering the operands
  if (src0_dt.get_kind() == expr_kind) {
    e->init_buffer(0, src0_dt.value_type());
    e->buf[0].kernel_offset = ckb_offset - root_ckb_offset;
    ckb_offset = make_assignment_kernel(
        ckb, ckb_offset, src0_dt.value_type(), e->buf[0].arrmeta, src0_dt,
        src0_arrmeta, kernel_request_single, ectx);
    // Have to re-retrieve 'e', because creating another kernel may invalidate
    // it
    e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
            ->get_at<buffered_kernel_extra>(root_ckb_offset);
  }
  if (src1_dt.get_kind() == expr_kind) {
    e->init_buffer(1, src1_dt.value_type());
    e->buf[1].kernel_offset = ckb_offset - root_ckb_offset;
    ckb_offset = make_assignment_kernel(
        ckb, ckb_offset, src1_dt.value_type(), e->buf[1].arrmeta, src1_dt,
        src1_arrmeta, kernel_request_single, ectx);
    // Have to re-retrieve 'e', because creating another kernel may invalidate
    // it
    e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
            ->get_at<buffered_kernel_extra>(root_ckb_offset);
  }
  // Allocate the data for the buffers
  if (e->buf[0].kernel_offset != 0) {
    ckb_offset = inc_to_alignment(ckb_offset, src0_dt.get_data_alignment());
    e->buf[0].data_offset = ckb_offset - root_ckb_offset;
    ckb_offset += e->buf[0].data_size;
  }
  if (e->buf[1].kernel_offset != 0) {
    ckb_offset = inc_to_alignment(ckb_offset, src1_dt.get_data_alignment());
    e->buf[1].data_offset = ckb_offset - root_ckb_offset;
    ckb_offset += e->buf[1].data_size;
  }
  reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
      ->reserve(ckb_offset + sizeof(ckernel_prefix));
  // Have to re-retrieve 'e', because allocating the buffer data may invalidate
  // it
  e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
          ->get_at<buffered_kernel_extra>(root_ckb_offset);
  e->cmp_kernel_offset = ckb_offset - root_ckb_offset;
  return make_comparison_kernel(
      ckb, ckb_offset, src0_dt.value_type(),
      (e->buf[0].kernel_offset != 0) ? e->buf[0].arrmeta : src0_arrmeta,
      src1_dt.value_type(),
      (e->buf[1].kernel_offset != 0) ? e->buf[1].arrmeta : src1_arrmeta,
      comptype, ectx);
}
Example #11
0
static void make_numpy_dtype_for_copy(pyobject_ownref *out_numpy_dtype, intptr_t ndim, const ndt::type &dt,
                                      const char *arrmeta)
{
  // DyND builtin types
  if (dt.is_builtin()) {
    out_numpy_dtype->reset((PyObject *)PyArray_DescrFromType(dynd_to_numpy_id(dt.get_id())));
    return;
  }

  switch (dt.get_id()) {
  case fixed_string_id: {
    const ndt::fixed_string_type *fsd = dt.extended<ndt::fixed_string_type>();
    PyArray_Descr *result;
    switch (fsd->get_encoding()) {
    case string_encoding_ascii:
      result = PyArray_DescrNewFromType(NPY_STRING);
      result->elsize = (int)fsd->get_data_size();
      out_numpy_dtype->reset((PyObject *)result);
      return;
    case string_encoding_utf_32:
      result = PyArray_DescrNewFromType(NPY_UNICODE);
      result->elsize = (int)fsd->get_data_size();
      out_numpy_dtype->reset((PyObject *)result);
      return;
    default:
      // If it's not one of the encodings NumPy supports,
      // use Unicode
      result = PyArray_DescrNewFromType(NPY_UNICODE);
      result->elsize = (int)fsd->get_data_size() * 4 / string_encoding_char_size_table[fsd->get_encoding()];
      out_numpy_dtype->reset((PyObject *)result);
      return;
    }
    break;
  }
  case string_id: {
    // Convert variable-length strings into NumPy object arrays
    PyArray_Descr *dtype = PyArray_DescrNewFromType(NPY_OBJECT);
    // Add metadata to the string type being created so that
    // it can round-trip. This metadata is compatible with h5py.
    out_numpy_dtype->reset((PyObject *)dtype);
    if (dtype->metadata == NULL) {
      dtype->metadata = PyDict_New();
    }
    PyDict_SetItemString(dtype->metadata, "vlen", (PyObject *)&PyUnicode_Type);
    return;
  }
  case fixed_dim_id: {
    if (ndim > 0) {
      const ndt::base_dim_type *bdt = dt.extended<ndt::base_dim_type>();
      make_numpy_dtype_for_copy(out_numpy_dtype, ndim - 1, bdt->get_element_type(),
                                arrmeta + sizeof(fixed_dim_type_arrmeta));
      return;
    }
    else {
      // If this isn't one of the array dimensions, it maps into
      // a numpy dtype with a shape
      // Build up the shape of the array for NumPy
      pyobject_ownref shape(PyList_New(0));
      ndt::type element_tp = dt;
      while (ndim > 0) {
        const fixed_dim_type_arrmeta *am = reinterpret_cast<const fixed_dim_type_arrmeta *>(arrmeta);
        intptr_t dim_size = am->dim_size;
        element_tp = dt.extended<ndt::base_dim_type>()->get_element_type();
        arrmeta += sizeof(fixed_dim_type_arrmeta);
        --ndim;
        if (PyList_Append(shape.get(), PyLong_FromSize_t(dim_size)) < 0) {
          throw runtime_error("propagating python error");
        }
      }
      // Get the numpy dtype of the element
      pyobject_ownref child_numpy_dtype;
      make_numpy_dtype_for_copy(&child_numpy_dtype, 0, element_tp, arrmeta);
      // Create the result numpy dtype
      pyobject_ownref tuple_obj(PyTuple_New(2));
      PyTuple_SET_ITEM(tuple_obj.get(), 0, child_numpy_dtype.release());
      PyTuple_SET_ITEM(tuple_obj.get(), 1, shape.release());

      PyArray_Descr *result = NULL;
      if (!PyArray_DescrConverter(tuple_obj, &result)) {
        throw dynd::type_error("failed to convert dynd type into numpy subarray dtype");
      }
      // Put the final numpy dtype reference in the output
      out_numpy_dtype->reset((PyObject *)result);
      return;
    }
    break;
  }
  case struct_id: {
    const ndt::struct_type *bs = dt.extended<ndt::struct_type>();
    size_t field_count = bs->get_field_count();

    pyobject_ownref names_obj(PyList_New(field_count));
    for (size_t i = 0; i < field_count; ++i) {
      const dynd::string &fn = bs->get_field_name(i);
#if PY_VERSION_HEX >= 0x03000000
      pyobject_ownref name_str(PyUnicode_FromStringAndSize(fn.begin(), fn.end() - fn.begin()));
#else
      pyobject_ownref name_str(PyString_FromStringAndSize(fn.begin(), fn.end() - fn.begin()));
#endif
      PyList_SET_ITEM(names_obj.get(), i, name_str.release());
    }

    pyobject_ownref formats_obj(PyList_New(field_count));
    pyobject_ownref offsets_obj(PyList_New(field_count));
    size_t standard_offset = 0, standard_alignment = 1;
    for (size_t i = 0; i < field_count; ++i) {
      // Get the numpy dtype of the element
      pyobject_ownref field_numpy_dtype;
      make_numpy_dtype_for_copy(&field_numpy_dtype, 0, bs->get_field_type(i), arrmeta);
      size_t field_alignment = ((PyArray_Descr *)field_numpy_dtype.get())->alignment;
      size_t field_size = ((PyArray_Descr *)field_numpy_dtype.get())->elsize;
      standard_offset = inc_to_alignment(standard_offset, field_alignment);
      standard_alignment = max(standard_alignment, field_alignment);
      PyList_SET_ITEM(formats_obj.get(), i, field_numpy_dtype.release());
      PyList_SET_ITEM((PyObject *)offsets_obj, i, PyLong_FromSize_t(standard_offset));
      standard_offset += field_size;
    }
    // Get the full element size
    standard_offset = inc_to_alignment(standard_offset, standard_alignment);
    pyobject_ownref itemsize_obj(PyLong_FromSize_t(standard_offset));

    pyobject_ownref dict_obj(PyDict_New());
    PyDict_SetItemString(dict_obj, "names", names_obj);
    PyDict_SetItemString(dict_obj, "formats", formats_obj);
    PyDict_SetItemString(dict_obj, "offsets", offsets_obj);
    PyDict_SetItemString(dict_obj, "itemsize", itemsize_obj);

    PyArray_Descr *result = NULL;
    if (!PyArray_DescrAlignConverter(dict_obj, &result)) {
      stringstream ss;
      ss << "failed to convert dynd type " << dt << " into numpy dtype via dict";
      throw dynd::type_error(ss.str());
    }
    out_numpy_dtype->reset((PyObject *)result);
    return;
  }
  default: {
    break;
  }
  }

  if (dt.get_base_id() == expr_kind_id) {
    // Convert the value type for the copy
    make_numpy_dtype_for_copy(out_numpy_dtype, ndim, dt.value_type(), NULL);
    return;
  }

  // Anything which fell through is an error
  stringstream ss;
  ss << "dynd as_numpy could not convert dynd type ";
  ss << dt;
  ss << " to a numpy dtype";
  throw dynd::type_error(ss.str());
}
Example #12
0
 const ndt::type &get_value_type() const { return m_value_tp.value_type(); }
ndt::type dynd::promote_types_arithmetic(const ndt::type &tp0, const ndt::type &tp1) {
  // Use the value types
  const ndt::type &tp0_val = tp0.value_type();
  const ndt::type &tp1_val = tp1.value_type();

  // cout << "Doing type promotion with value types " << tp0_val << " and " <<
  // tp1_val << endl;

  if (tp0_val.is_builtin() && tp1_val.is_builtin()) {
    const size_t int_size = sizeof(int);
    if (tp0_val.get_id() == void_id) {
      return tp1_val;
    }
    switch (tp0_val.get_base_id()) {
    case bool_kind_id:
      if (tp1_val.get_id() == void_id) {
        return tp0_val;
      }
      switch (tp1_val.get_base_id()) {
      case bool_kind_id:
        return ndt::make_type<int>();
      case int_kind_id:
      case uint_kind_id:
        return (tp1_val.get_data_size() >= int_size) ? tp1_val : ndt::make_type<int>();
      case float_kind_id:
        // The bool type doesn't affect float type sizes, except
        // require at least float32
        return tp1_val.unchecked_get_builtin_id() != float16_id ? tp1_val : ndt::make_type<float>();
      default:
        return tp1_val;
      }
    case int_kind_id:
      if (tp1_val.get_id() == void_id) {
        return tp0_val;
      }
      switch (tp1_val.get_base_id()) {
      case bool_kind_id:
        return (tp0_val.get_data_size() >= int_size) ? tp0_val : ndt::make_type<int>();
      case int_kind_id:
        if (tp0_val.get_data_size() < int_size && tp1_val.get_data_size() < int_size) {
          return ndt::make_type<int>();
        } else {
          return (tp0_val.get_data_size() >= tp1_val.get_data_size()) ? tp0_val : tp1_val;
        }
      case uint_kind_id:
        if (tp0_val.get_data_size() < int_size && tp1_val.get_data_size() < int_size) {
          return ndt::make_type<int>();
        } else {
          // When the element_sizes are equal, the uint kind wins
          return (tp0_val.get_data_size() > tp1_val.get_data_size()) ? tp0_val : tp1_val;
        }
      case float_kind_id:
        // Integer type sizes don't affect float type sizes, except
        // require at least float32
        return tp1_val.unchecked_get_builtin_id() != float16_id ? tp1_val : ndt::make_type<float>();
      case complex_kind_id:
        // Integer type sizes don't affect complex type sizes
        return tp1_val;
      default:
        break;
      }
      break;
    case uint_kind_id:
      if (tp1_val.get_id() == void_id) {
        return tp0_val;
      }
      switch (tp1_val.get_base_id()) {
      case bool_kind_id:
        return (tp0_val.get_data_size() >= int_size) ? tp0_val : ndt::make_type<int>();
      case int_kind_id:
        if (tp0_val.get_data_size() < int_size && tp1_val.get_data_size() < int_size) {
          return ndt::make_type<int>();
        } else {
          // When the element_sizes are equal, the uint kind wins
          return (tp0_val.get_data_size() >= tp1_val.get_data_size()) ? tp0_val : tp1_val;
        }
      case uint_kind_id:
        if (tp0_val.get_data_size() < int_size && tp1_val.get_data_size() < int_size) {
          return ndt::make_type<int>();
        } else {
          return (tp0_val.get_data_size() >= tp1_val.get_data_size()) ? tp0_val : tp1_val;
        }
      case float_kind_id:
        // Integer type sizes don't affect float type sizes, except
        // require at least float32
        return tp1_val.unchecked_get_builtin_id() != float16_id ? tp1_val : ndt::make_type<float>();
      case complex_kind_id:
        // Integer type sizes don't affect complex type sizes
        return tp1_val;
      default:
        break;
      }
      break;
    case float_kind_id:
      if (tp1_val.get_id() == void_id) {
        return tp0_val;
      }
      switch (tp1_val.get_base_id()) {
      // Integer type sizes don't affect float type sizes
      case bool_kind_id:
      case int_kind_id:
      case uint_kind_id:
        return tp0_val;
      case float_kind_id:
        return ndt::type(reinterpret_cast<ndt::base_type *>(max(
                             max(tp0_val.unchecked_get_builtin_id(), tp1_val.unchecked_get_builtin_id()), float32_id)),
                         false);
      case complex_kind_id:
        if (tp0_val.get_id() == float64_id && tp1_val.get_id() == complex_float32_id) {
          return ndt::make_type<complex<double>>();
        } else {
          return tp1_val;
        }
      default:
        break;
      }
      break;
    case complex_kind_id:
      if (tp1_val.get_id() == void_id) {
        return tp0_val;
      }
      switch (tp1_val.get_base_id()) {
      // Integer and float type sizes don't affect complex type sizes
      case bool_kind_id:
      case int_kind_id:
      case uint_kind_id:
      case float_kind_id:
        if (tp0_val.unchecked_get_builtin_id() == complex_float32_id &&
            tp1_val.unchecked_get_builtin_id() == float64_id) {
          return ndt::make_type<complex<double>>();
        } else {
          return tp0_val;
        }
      case complex_kind_id:
        return (tp0_val.get_data_size() >= tp1_val.get_data_size()) ? tp0_val : tp1_val;
      default:
        break;
      }
      break;
    default:
      break;
    }

    stringstream ss;
    ss << "internal error in built-in dynd type promotion of " << tp0_val << " and " << tp1_val;
    throw dynd::type_error(ss.str());
  }

  // HACK for getting simple string type promotions.
  // TODO: Do this properly in a pluggable manner.
  if ((tp0_val.get_id() == string_id || tp0_val.get_id() == fixed_string_id) &&
      (tp1_val.get_id() == string_id || tp1_val.get_id() == fixed_string_id)) {
    // Always promote to the default utf-8 string (for now, maybe return
    // encoding, etc later?)
    return ndt::make_type<ndt::string_type>();
  }

  // the value underneath the option type promotes
  if (tp0_val.get_id() == option_id) {
    if (tp1_val.get_id() == option_id) {
      return ndt::make_type<ndt::option_type>(
          promote_types_arithmetic(tp0_val.extended<ndt::option_type>()->get_value_type(),
                                   tp1_val.extended<ndt::option_type>()->get_value_type()));
    } else {
      return ndt::make_type<ndt::option_type>(
          promote_types_arithmetic(tp0_val.extended<ndt::option_type>()->get_value_type(), tp1_val));
    }
  } else if (tp1_val.get_id() == option_id) {
    return ndt::make_type<ndt::option_type>(
        promote_types_arithmetic(tp0_val, tp1_val.extended<ndt::option_type>()->get_value_type()));
  }

  // type, string -> type
  if (tp0_val.get_id() == type_id && tp1_val.get_base_id() == string_kind_id) {
    return tp0_val;
  }
  // string, type -> type
  if (tp0_val.get_base_id() == string_kind_id && tp1_val.get_id() == type_id) {
    return tp1_val;
  }

  // In general, if one type is void, just return the other type
  if (tp0_val.get_id() == void_id) {
    return tp1_val;
  } else if (tp1_val.get_id() == void_id) {
    return tp0_val;
  }

  // Promote some dimension types
  if ((tp0_val.get_id() == var_dim_id && tp1_val.get_base_id() == dim_kind_id) ||
      (tp1_val.get_id() == var_dim_id && tp0_val.get_base_id() == dim_kind_id)) {
    return ndt::make_type<ndt::var_dim_type>(
        promote_types_arithmetic(tp0_val.extended<ndt::base_dim_type>()->get_element_type(),
                                 tp1_val.extended<ndt::base_dim_type>()->get_element_type()));
  }

  stringstream ss;
  ss << "type promotion of " << tp0 << " and " << tp1 << " is not yet supported";
  throw dynd::type_error(ss.str());
}
static void as_numpy_analysis(pyobject_ownref *out_numpy_dtype,
                              bool *out_requires_copy, intptr_t ndim,
                              const ndt::type &dt, const char *arrmeta)
{
  if (dt.is_builtin()) {
    // DyND builtin types
    out_numpy_dtype->reset((PyObject *)PyArray_DescrFromType(
        dynd_to_numpy_type_id[dt.get_type_id()]));
    return;
  } else if (dt.get_type_id() == view_type_id &&
             dt.operand_type().get_type_id() == fixed_bytes_type_id) {
    // View operation for alignment
    as_numpy_analysis(out_numpy_dtype, out_requires_copy, ndim, dt.value_type(),
                      NULL);
    return;
  }

  switch (dt.get_type_id()) {
  case fixed_string_type_id: {
    const ndt::fixed_string_type *fsd = dt.extended<ndt::fixed_string_type>();
    PyArray_Descr *result;
    switch (fsd->get_encoding()) {
    case string_encoding_ascii:
      result = PyArray_DescrNewFromType(NPY_STRING);
      result->elsize = (int)fsd->get_data_size();
      out_numpy_dtype->reset((PyObject *)result);
      return;
    case string_encoding_utf_32:
      result = PyArray_DescrNewFromType(NPY_UNICODE);
      result->elsize = (int)fsd->get_data_size();
      out_numpy_dtype->reset((PyObject *)result);
      return;
    default:
      out_numpy_dtype->clear();
      *out_requires_copy = true;
      return;
    }
    break;
  }
  case string_type_id: {
    // Convert to numpy object type, requires copy
    out_numpy_dtype->clear();
    *out_requires_copy = true;
    return;
  }
  case date_type_id: {
#if NPY_API_VERSION >= 6 // At least NumPy 1.6
    out_numpy_dtype->clear();
    *out_requires_copy = true;
    return;
#else
    throw runtime_error("NumPy >= 1.6 is required for dynd date type interop");
#endif
  }
  case datetime_type_id: {
#if NPY_API_VERSION >= 6 // At least NumPy 1.6
    out_numpy_dtype->clear();
    *out_requires_copy = true;
    return;
#else
    throw runtime_error("NumPy >= 1.6 is required for dynd date type interop");
#endif
  }
  case property_type_id: {
    const ndt::property_type *pd = dt.extended<ndt::property_type>();
    // Special-case of 'int64 as date' property type, which is binary
    // compatible with NumPy's "M8[D]"
    if (pd->is_reversed_property() &&
        pd->get_value_type().get_type_id() == date_type_id &&
        pd->get_operand_type().get_type_id() == int64_type_id) {
      PyArray_Descr *datedt = NULL;
#if PY_VERSION_HEX >= 0x03000000
      pyobject_ownref M8str(PyUnicode_FromString("M8[D]"));
#else
      pyobject_ownref M8str(PyString_FromString("M8[D]"));
#endif
      if (!PyArray_DescrConverter(M8str.get(), &datedt)) {
        throw dynd::type_error("Failed to create NumPy datetime64[D] dtype");
      }
      out_numpy_dtype->reset((PyObject *)datedt);
      return;
    }
    break;
  }
  case byteswap_type_id: {
    const ndt::base_expr_type *bed = dt.extended<ndt::base_expr_type>();
    // Analyze the unswapped version
    as_numpy_analysis(out_numpy_dtype, out_requires_copy, ndim,
                      bed->get_value_type(), arrmeta);
    pyobject_ownref swapdt(out_numpy_dtype->release());
    // Byteswap the numpy dtype
    out_numpy_dtype->reset((PyObject *)PyArray_DescrNewByteorder(
        (PyArray_Descr *)swapdt.get(), NPY_SWAP));
    return;
  }
  case fixed_dim_type_id: {
    const ndt::base_dim_type *bdt = dt.extended<ndt::base_dim_type>();
    if (ndim > 0) {
      // If this is one of the array dimensions, it simply
      // becomes one of the numpy _array dimensions
      as_numpy_analysis(out_numpy_dtype, out_requires_copy, ndim - 1,
                        bdt->get_element_type(),
                        arrmeta + sizeof(fixed_dim_type_arrmeta));
      return;
    } else {
      // If this isn't one of the array dimensions, it maps into
      // a numpy dtype with a shape
      out_numpy_dtype->clear();
      *out_requires_copy = true;
      return;
    }
    break;
  }
  /*
    case cfixed_dim_type_id: {
      const cfixed_dim_type *fad = dt.extended<cfixed_dim_type>();
      if (ndim > 0) {
        // If this is one of the array dimensions, it simply
        // becomes one of the numpy _array dimensions
        as_numpy_analysis(out_numpy_dtype, out_requires_copy, ndim - 1,
                          fad->get_element_type(),
                          arrmeta + sizeof(cfixed_dim_type_arrmeta));
        return;
      } else {
        // If this isn't one of the array dimensions, it maps into
        // a numpy dtype with a shape
        // Build up the shape of the array for NumPy
        pyobject_ownref shape(PyList_New(0));
        ndt::type element_tp = dt;
        while (ndim > 0) {
          size_t dim_size = 0;
          if (dt.get_type_id() == cfixed_dim_type_id) {
            const cfixed_dim_type *cfd = element_tp.extended<cfixed_dim_type>();
            element_tp = cfd->get_element_type();
            if (cfd->get_data_size() != element_tp.get_data_size() * dim_size) {
              // If it's not C-order, a copy is required
              out_numpy_dtype->clear();
              *out_requires_copy = true;
              return;
            }
          } else {
            stringstream ss;
            ss << "dynd as_numpy could not convert dynd type ";
            ss << dt;
            ss << " to a numpy dtype";
            throw dynd::type_error(ss.str());
          }
          --ndim;
          if (PyList_Append(shape.get(), PyLong_FromSize_t(dim_size)) < 0) {
            throw runtime_error("propagating python error");
          }
        }
        // Get the numpy dtype of the element
        pyobject_ownref child_numpy_dtype;
        as_numpy_analysis(&child_numpy_dtype, out_requires_copy, 0, element_tp,
                          arrmeta);
        if (*out_requires_copy) {
          // If the child required a copy, stop right away
          out_numpy_dtype->clear();
          return;
        }
        // Create the result numpy dtype
        pyobject_ownref tuple_obj(PyTuple_New(2));
        PyTuple_SET_ITEM(tuple_obj.get(), 0, child_numpy_dtype.release());
        PyTuple_SET_ITEM(tuple_obj.get(), 1, shape.release());

        PyArray_Descr *result = NULL;
        if (!PyArray_DescrConverter(tuple_obj, &result)) {
          throw dynd::type_error(
              "failed to convert dynd type into numpy subarray dtype");
        }
        // Put the final numpy dtype reference in the output
        out_numpy_dtype->reset((PyObject *)result);
        return;
      }
      break;
    }
  */
  case struct_type_id: {
    if (dt.get_type_id() == struct_type_id && arrmeta == NULL) {
      // If it's a struct type with no arrmeta, a copy is required
      out_numpy_dtype->clear();
      *out_requires_copy = true;
      return;
    }
    const ndt::base_struct_type *bs = dt.extended<ndt::base_struct_type>();
    const uintptr_t *offsets = bs->get_data_offsets(arrmeta);
    size_t field_count = bs->get_field_count();

    pyobject_ownref names_obj(PyList_New(field_count));
    for (size_t i = 0; i < field_count; ++i) {
      const dynd::string &fn = bs->get_field_name_raw(i);
#if PY_VERSION_HEX >= 0x03000000
      pyobject_ownref name_str(
          PyUnicode_FromStringAndSize(fn.begin(), fn.end() - fn.begin()));
#else
      pyobject_ownref name_str(
          PyString_FromStringAndSize(fn.begin(), fn.end() - fn.begin()));
#endif
      PyList_SET_ITEM(names_obj.get(), i, name_str.release());
    }

    pyobject_ownref formats_obj(PyList_New(field_count));
    for (size_t i = 0; i < field_count; ++i) {
      // Get the numpy dtype of the element
      pyobject_ownref field_numpy_dtype;
      as_numpy_analysis(&field_numpy_dtype, out_requires_copy, 0,
                        bs->get_field_type(i), arrmeta);
      if (*out_requires_copy) {
        // If the field required a copy, stop right away
        out_numpy_dtype->clear();
        return;
      }
      PyList_SET_ITEM(formats_obj.get(), i, field_numpy_dtype.release());
    }

    pyobject_ownref offsets_obj(PyList_New(field_count));
    for (size_t i = 0; i < field_count; ++i) {
      PyList_SET_ITEM((PyObject *)offsets_obj, i,
                      PyLong_FromSize_t(offsets[i]));
    }

    pyobject_ownref dict_obj(PyDict_New());
    PyDict_SetItemString(dict_obj, "names", names_obj);
    PyDict_SetItemString(dict_obj, "formats", formats_obj);
    PyDict_SetItemString(dict_obj, "offsets", offsets_obj);
    if (dt.get_data_size() > 0) {
      pyobject_ownref itemsize_obj(PyLong_FromSize_t(dt.get_data_size()));
      PyDict_SetItemString(dict_obj, "itemsize", itemsize_obj);
    }

    PyArray_Descr *result = NULL;
    if (!PyArray_DescrConverter(dict_obj, &result)) {
      stringstream ss;
      ss << "failed to convert dynd type " << dt
         << " into numpy dtype via dict";
      throw dynd::type_error(ss.str());
    }
    out_numpy_dtype->reset((PyObject *)result);
    return;
  }
  default: {
    break;
  }
  }

  if (dt.get_kind() == expr_kind) {
    // If none of the prior checks caught this expression,
    // a copy is required.
    out_numpy_dtype->clear();
    *out_requires_copy = true;
    return;
  }

  // Anything which fell through is an error
  stringstream ss;
  ss << "dynd as_numpy could not convert dynd type ";
  ss << dt;
  ss << " to a numpy dtype";
  throw dynd::type_error(ss.str());
}
size_t dynd::make_expression_assignment_kernel(
    void *ckb, intptr_t ckb_offset, const ndt::type &dst_tp,
    const char *dst_arrmeta, const ndt::type &src_tp, const char *src_arrmeta,
    kernel_request_t kernreq, const eval::eval_context *ectx)
{
  intptr_t root_ckb_offset = ckb_offset;
  if (dst_tp.get_kind() == expr_kind) {
    const ndt::base_expr_type *dst_bed = dst_tp.extended<ndt::base_expr_type>();
    if (src_tp == dst_bed->get_value_type()) {
      // In this case, it's just a chain of value -> operand on the dst side
      const ndt::type &opdt = dst_bed->get_operand_type();
      if (opdt.get_kind() != expr_kind) {
        // Leaf case, just a single value -> operand kernel
        return dst_bed->make_value_to_operand_assignment_kernel(
            ckb, ckb_offset, dst_arrmeta, src_arrmeta, kernreq, ectx);
      } else {
        // Chain case, buffer one segment of the chain
        const ndt::type &buffer_tp = static_cast<const ndt::base_expr_type *>(
                                         opdt.extended())->get_value_type();
        buffered_kernel_extra *e =
            reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
                ->alloc_ck<buffered_kernel_extra>(ckb_offset);
        e->init(buffer_tp, kernreq);
        // Construct the first kernel (src -> buffer)
        e->first_kernel_offset = ckb_offset - root_ckb_offset;
        ckb_offset = dst_bed->make_value_to_operand_assignment_kernel(
            ckb, ckb_offset, e->buffer_arrmeta, src_arrmeta, kernreq, ectx);
        // Allocate the buffer data
        ckb_offset =
            inc_to_alignment(ckb_offset, buffer_tp.get_data_alignment());
        intptr_t buffer_data_offset = ckb_offset;
        inc_ckb_offset(ckb_offset, e->buffer_data_size);
        reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
            ->reserve(ckb_offset + sizeof(ckernel_prefix));
        // This may have invalidated the 'e' pointer, so get it again!
        e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
                ->get_at<buffered_kernel_extra>(root_ckb_offset);
        e->buffer_data_offset = buffer_data_offset - root_ckb_offset;
        // Construct the second kernel (buffer -> dst)
        e->second_kernel_offset = ckb_offset - root_ckb_offset;
        return ::make_assignment_kernel(ckb, ckb_offset, opdt, dst_arrmeta,
                                        buffer_tp, e->buffer_arrmeta, kernreq,
                                        ectx);
      }
    } else {
      ndt::type buffer_tp;
      if (src_tp.get_kind() != expr_kind) {
        // In this case, need a data converting assignment to
        // dst_tp.value_type(),
        // then the dst_tp expression chain
        buffer_tp = dst_bed->get_value_type();
      } else {
        // Both src and dst are expression types, use the src expression chain,
        // and
        // the src value type to dst type as the two segments to buffer together
        buffer_tp = src_tp.value_type();
      }
      buffered_kernel_extra *e =
          reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
              ->alloc_ck<buffered_kernel_extra>(ckb_offset);
      e->init(buffer_tp, kernreq);
      // Construct the first kernel (src -> buffer)
      e->first_kernel_offset = ckb_offset - root_ckb_offset;
      ckb_offset = ::make_assignment_kernel(ckb, ckb_offset, buffer_tp,
                                            e->buffer_arrmeta, src_tp,
                                            src_arrmeta, kernreq, ectx);
      ckb_offset = inc_to_alignment(ckb_offset, buffer_tp.get_data_alignment());
      // Allocate the buffer data
      intptr_t buffer_data_offset = ckb_offset;
      inc_ckb_offset(ckb_offset, e->buffer_data_size);
      reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
          ->reserve(ckb_offset + sizeof(ckernel_prefix));
      // This may have invalidated the 'e' pointer, so get it again!
      e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
              ->get_at<buffered_kernel_extra>(root_ckb_offset);
      e->buffer_data_offset = buffer_data_offset - root_ckb_offset;
      // Construct the second kernel (buffer -> dst)
      e->second_kernel_offset = ckb_offset - root_ckb_offset;
      return ::make_assignment_kernel(ckb, ckb_offset, dst_tp, dst_arrmeta,
                                      buffer_tp, e->buffer_arrmeta, kernreq,
                                      ectx);
    }
  } else {
    const ndt::base_expr_type *src_bed = src_tp.extended<ndt::base_expr_type>();
    if (dst_tp == src_bed->get_value_type()) {
      // In this case, it's just a chain of operand -> value on the src side
      const ndt::type &opdt = src_bed->get_operand_type();
      if (opdt.get_kind() != expr_kind) {
        // Leaf case, just a single value -> operand kernel
        return src_bed->make_operand_to_value_assignment_kernel(
            ckb, ckb_offset, dst_arrmeta, src_arrmeta, kernreq, ectx);
      } else {
        // Chain case, buffer one segment of the chain
        const ndt::type &buffer_tp = static_cast<const ndt::base_expr_type *>(
                                         opdt.extended())->get_value_type();
        buffered_kernel_extra *e =
            reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
                ->alloc_ck<buffered_kernel_extra>(ckb_offset);
        e->init(buffer_tp, kernreq);
        size_t buffer_data_size = e->buffer_data_size;
        // Construct the first kernel (src -> buffer)
        e->first_kernel_offset = ckb_offset - root_ckb_offset;
        ckb_offset = ::make_assignment_kernel(ckb, ckb_offset, buffer_tp,
                                              e->buffer_arrmeta, opdt,
                                              src_arrmeta, kernreq, ectx);
        // Allocate the buffer data
        ckb_offset =
            inc_to_alignment(ckb_offset, buffer_tp.get_data_alignment());
        size_t buffer_data_offset = ckb_offset;
        inc_ckb_offset(ckb_offset, buffer_data_size);
        reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
            ->reserve(ckb_offset + sizeof(ckernel_prefix));
        // This may have invalidated the 'e' pointer, so get it again!
        e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
                ->get_at<buffered_kernel_extra>(root_ckb_offset);
        e->buffer_data_offset = buffer_data_offset - root_ckb_offset;
        // Construct the second kernel (buffer -> dst)
        e->second_kernel_offset = ckb_offset - root_ckb_offset;
        return src_bed->make_operand_to_value_assignment_kernel(
            ckb, ckb_offset, dst_arrmeta, e->buffer_arrmeta, kernreq, ectx);
      }
    } else {
      // Put together the src expression chain and the src value type
      // to dst value type conversion
      const ndt::type &buffer_tp = src_tp.value_type();
      buffered_kernel_extra *e =
          reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
              ->alloc_ck<buffered_kernel_extra>(ckb_offset);
      e->init(buffer_tp, kernreq);
      size_t buffer_data_size = e->buffer_data_size;
      // Construct the first kernel (src -> buffer)
      e->first_kernel_offset = ckb_offset - root_ckb_offset;
      ckb_offset = ::make_assignment_kernel(ckb, ckb_offset, buffer_tp,
                                            e->buffer_arrmeta, src_tp,
                                            src_arrmeta, kernreq, ectx);
      // Allocate the buffer data
      ckb_offset = inc_to_alignment(ckb_offset, buffer_tp.get_data_alignment());
      size_t buffer_data_offset = ckb_offset;
      inc_ckb_offset(ckb_offset, buffer_data_size);
      reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
          ->reserve(ckb_offset + sizeof(ckernel_prefix));
      // This may have invalidated the 'e' pointer, so get it again!
      e = reinterpret_cast<ckernel_builder<kernel_request_host> *>(ckb)
              ->get_at<buffered_kernel_extra>(root_ckb_offset);
      e->buffer_data_offset = buffer_data_offset - root_ckb_offset;
      // Construct the second kernel (buffer -> dst)
      e->second_kernel_offset = ckb_offset - root_ckb_offset;
      return ::make_assignment_kernel(ckb, ckb_offset, dst_tp, dst_arrmeta,
                                      buffer_tp, e->buffer_arrmeta, kernreq,
                                      ectx);
    }
  }
}