zeam-vm / pelemay

Pelemay is a native compiler for Elixir, which generates SIMD instructions. It has a plan to generate for GPU code.
Apache License 2.0
186 stars 14 forks source link

Type checking and inference with investigation of range of a value of each element #76

Open zacky1972 opened 4 years ago

zacky1972 commented 4 years ago

Is your feature request related to a problem? Please describe. In SIMD calculation, optimized code is different according to which type of each element is required, 8, 16, 32, or 64 bit.

Describe the solution you'd like Type checking and inference system should investigate range of a value of each element and generate code using suitable type of array.

Describe alternatives you've considered None.

Additional context This issue is related to #75

zacky1972 commented 4 years ago

I wrote sample generated code:

static ERL_NIF_TERM
func(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
  if (__builtin_expect((argc != 1), false)) {
    return enif_make_badarg(env);
  }
  if (__builtin_expect(enif_is_empty_list(env, argv[0]), false)) {
    // Write code here in the case that argv[0] is an empty list
    ...
    return true;
  }

  ERL_NIF_TERM from, to;
  if (__builtin_expect(enif_get_range(env, argv[0], &from, &to), false)) {
    // Write code here in the case that argv[0] is an range
    ...
    return true;
  }

  size_t n0 = INIT_SIZE_ERL_NIF;
  size_t nn0 = cache_line_size;
  ERL_NIF_TERM *t0 = (ERL_NIF_TERM *)enif_alloc(nn0);
  if (__builtin_expect((t0 == NULL), false)) {
    // In the case of an error of memory allocation
    return false;
  }
  size_t l0 = 0;

 // try to read argv[0] as a list
  bool is_list0 = false;
  ERL_NIF_TERM tail;
  while (enif_get_list_cell(env, argv[0], &t0[l0++], &tail)) {
    is_list0 = true;
    if(__builtin_expect(i > n0, false)) {
      // expand vector t0
      ...
    }
  }
  if (__builtin_expect(is_list0, true)) {
    // t0 is set to an vector of ERL_NIF_TERM, l0 is set to the length of t0

    if (__builtin_expect(enif_is_number(env, t0[0]), true)) {
      double double_0_1st;
      if (__builtin_expect(enif_get_double(env, t0[0], &double_0_1st)), true) {
        // In the case that the 1st element of t0 is a double number
        double *double_0 = (double *)enif_alloc(l0 * sizeof(double))
        if (__builtin_expect((double_0 == NULL), false)) {
          // In the case of an error of memory allocation
          return false;
        }
        double_0[0] = double_0_1st;
        for(size_t i = 1; i < l0; i++) {
          if(__builtin_expect(!enif_get_double(env, t0[i], &double_0[i]), false)) {
            // In the case of poly-typed list
            unsigned int b0[l0 >> PRECISION(UINT_MAX)] = {}; // bit vector for t0
            size_t c0[T_NUM] = {}; // set to 0
            c0[T_DOUBLE] += i;
            do {
              if(__builtin_expect(enif_is_number(env, t0[i]), true)) {
                // In the case that t0[i] is a number
                ErlNifSInt64 i64_i0;
                ErlNIfUInt64 u64_i0;
                if(__builtin_expect(enif_get_int64(env, t0[i], &i64_i0), true)) {
                  // In the case of t0[i] is int64
                  update_type_counters(c0, i64_i0);
                  BITSET(b0, i, 1);
                } else if(__builtin_expect(enif_get_uint64(env, t0[i], &u64_i0), true)) {
                  // In the case of t0[i] is not int64 but uint64
                  c0[T_UINT64]++;
                  BITSET(b0, i, 1);
                } else if(__builtin_expect(enif_get_double(env, t0[i], double_0[i]), true)) {
                  // In the case of t0[i] is double
                  c0[T_DOUBLE]++;
                  BITSET(b0, i, 0);
                } else {
                  // In the case of out of range of INT64 and UINT64
                  return out_of_range;
                }
              } else {
                // In the case that t0[i] is not a number
                ...
              }
            } while(i < l0);
            // double_0 is set to a double array read from argv[0]
            // t0 is set to an array of ERL_NIF_TERM
            // b0 is set to an bit vector that a bit is set to 0 if the corresponding element is a double number
            // c0 is set to counters for each type

            // Write code here in the case that argv[0] is a poly-typed list of double.
            return true;
          }
        }
        // double_0 is set to a double array read from argv[0]
        // l0 is set to the length of double_0

        // Write code here in the case that argv[0] is a mono-typed list of double
        return true;
      } 

      ErlNifSInt64 int64_0_1st;
      if (__builtin_expect(enif_get_int64(env, t0[0], &int64_0_1st), false)) {
        // In the case that argv[0] is a list of an int64
        ...
        return true;
      }
      ErlNIfUInt64 uint64_0_1st;
      if (__builtin_expect(enif_get_uint64(env, t0[0], &uint64_0_1st), false)) {
        // In the case that argv[0] is a list of an int64
        ...
        return true;
      }
      // In the case of out of range of INT64 and UINT64
      return out_of_range;
    } else if ...
  }

  enif_free(t0);

  if(__builtin_expect(enif_is_number(env, argv[0]), true)) {
    // in the case that argv[0] is a number
    ...
    return true;
  }
  ...
  if(__builtin_expect(enif_get_map_size(env, argv[0], &l0), true)) {
    // in the case that argv[0] is a map
    ...
    return true;
  }
  if(__builtin_expect(enif_get_tuple(env, argv[0], &l0, &t0), true)) {
    // in the case that argv[0] is a tuple
    ...
    return true;
  }
}