blob: c2c8abf890c7b551499f9f3446b198f5b440c1a4 [file]
// Copyright 2022 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/modules/vmvx/elementwise.h"
// TODO: We should only be including/using this in standalone builds. In others,
// we have to emulate or use other mechanisms. Since this file only contains
// fallback implementations, we don't care about the quality *that* much but
// would still like to avoid the libc dep for compatibility with the bitcode
// path.
#include <math.h>
//===----------------------------------------------------------------------===//
// Helpers for defining generic implementations of elementwise functions.
// Since it affords the best code size tradeoff options, the entrypoint
// is dispatched based on an opcode.
//===----------------------------------------------------------------------===//
// Opcodes for generic functions operating on 32-bit operands and result.
// Since the outer dispatcher only differentiates based on width, all other
// type specificity is carried by the opcode.
// Binary opcodes are named "X32B" and unary opcodes "X32U".
// The initial list was sorted, and it is encouraged to sort extensions, but
// each opcode must be numerically stable, so the list is not expected to
// be sorted over time.
typedef enum {
IREE_UK_X32B_ADDF = 0,
IREE_UK_X32B_ADDI = 1,
IREE_UK_X32B_ANDI = 2,
IREE_UK_X32B_DIVF = 3,
IREE_UK_X32B_DIVSI = 4,
IREE_UK_X32B_DIVUI = 5,
IREE_UK_X32B_MULF = 6,
IREE_UK_X32B_MULI = 7,
IREE_UK_X32B_ORI = 8,
IREE_UK_X32B_SHLI = 9,
IREE_UK_X32B_SHRSI = 10,
IREE_UK_X32B_SHRUI = 11,
IREE_UK_X32B_SUBF = 12,
IREE_UK_X32B_SUBI = 13,
IREE_UKENREL_X32B_XORI = 14,
} iree_uk_x32b_opcode_t;
typedef enum {
IREE_UK_X32U_ABSF,
IREE_UK_X32U_CEILF,
IREE_UK_X32U_CTLZ,
IREE_UK_X32U_EXPF,
IREE_UK_X32U_FLOORF,
IREE_UK_X32U_LOGF,
IREE_UK_X32U_NEGF,
IREE_UK_X32U_RSQRTF,
} iree_uk_x32u_opcode_t;
// Macros to access various typed, dereferenced pointers.
#define ASF32(ptr) *((float*)ptr)
#define ASUI32(ptr) *((iree_uk_uint32_t*)ptr)
#define ASSI32(ptr) *((iree_uk_int32_t*)ptr)
//===----------------------------------------------------------------------===//
// Implementation macros.
//===----------------------------------------------------------------------===//
// Defines a generic "dispatched" implementation via opcode_t by invoking
// the function iree_uk_generic_{category}_2d.
// Corresponds to the header macro DECLARE_UKERNEL_BINARY_2D.
#define DISPATCH_UKERNEL_BINARY_2D(opcode, opcode_t, dtype, category) \
IREE_UK_EXPORT int iree_uk_##category##_##opcode##_2d( \
const dtype* lhs, iree_uk_index_t lhs_offset, \
iree_uk_index_t lhs_stride0, iree_uk_index_t lhs_stride1, \
const dtype* rhs, iree_uk_index_t rhs_offset, \
iree_uk_index_t rhs_stride0, iree_uk_index_t rhs_stride1, \
dtype* IREE_UK_RESTRICT out, iree_uk_index_t out_offset, \
iree_uk_index_t out_stride0, iree_uk_index_t out_stride1, \
iree_uk_index_t size0, iree_uk_index_t size1) { \
return iree_uk_generic_##category##_2d( \
opcode_t, lhs, lhs_offset, lhs_stride0, lhs_stride1, rhs, rhs_offset, \
rhs_stride0, rhs_stride1, out, out_offset, out_stride0, out_stride1, \
size0, size1); \
}
// Defines a generic "dispatched" implementation via opcode_t by invoking
// the function iree_uk_generic_{category}_2d.
// Corresponds to the header macro DECLARE_UKERNEL_BINARY_2D.
#define DISPATCH_UKERNEL_UNARY_2D(opcode, opcode_t, dtype, category) \
IREE_UK_EXPORT int iree_uk_##category##_##opcode##_2d( \
const dtype* in, iree_uk_index_t in_offset, iree_uk_index_t in_stride0, \
iree_uk_index_t in_stride1, dtype* IREE_UK_RESTRICT out, \
iree_uk_index_t out_offset, iree_uk_index_t out_stride0, \
iree_uk_index_t out_stride1, iree_uk_index_t size0, \
iree_uk_index_t size1) { \
return iree_uk_generic_##category##_2d( \
opcode_t, in, in_offset, in_stride0, in_stride1, out, out_offset, \
out_stride0, out_stride1, size0, size1); \
}
//===----------------------------------------------------------------------===//
// Internal helpers.
//===----------------------------------------------------------------------===//
// Computes a single element of an x32b opcode. On error, should set
// |*result_code| to a non-zero value (but should not touch it otherwise).
static void iree_uk_generic_x32b_op(iree_uk_x32b_opcode_t opcode,
int* result_code,
const iree_uk_uint32_t* lhs,
const iree_uk_uint32_t* rhs,
iree_uk_uint32_t* out) {
switch (opcode) {
case IREE_UK_X32B_ADDF:
ASF32(out) = ASF32(lhs) + ASF32(rhs);
return;
case IREE_UK_X32B_ADDI:
ASUI32(out) = ASUI32(lhs) + ASUI32(rhs);
return;
case IREE_UK_X32B_ANDI:
ASUI32(out) = ASUI32(lhs) & ASUI32(rhs);
return;
case IREE_UK_X32B_DIVF:
ASF32(out) = ASF32(lhs) / ASF32(rhs);
return;
case IREE_UK_X32B_DIVSI:
ASSI32(out) = ASSI32(lhs) / ASSI32(rhs);
return;
case IREE_UK_X32B_DIVUI:
ASUI32(out) = ASUI32(lhs) / ASUI32(rhs);
return;
case IREE_UK_X32B_MULF:
ASF32(out) = ASF32(lhs) * ASF32(rhs);
return;
case IREE_UK_X32B_MULI:
ASUI32(out) = ASUI32(lhs) * ASUI32(rhs);
return;
case IREE_UK_X32B_ORI:
ASUI32(out) = ASUI32(lhs) | ASUI32(rhs);
return;
case IREE_UK_X32B_SHLI:
ASUI32(out) = ASUI32(lhs) << ASUI32(rhs);
return;
case IREE_UK_X32B_SHRSI:
ASSI32(out) = ASSI32(lhs) >> ASSI32(rhs);
return;
case IREE_UK_X32B_SHRUI:
ASUI32(out) = ASUI32(lhs) >> ASUI32(rhs);
return;
case IREE_UKENREL_X32B_XORI:
ASUI32(out) = ASUI32(lhs) ^ ASUI32(rhs);
return;
case IREE_UK_X32B_SUBF:
ASF32(out) = ASF32(lhs) - ASF32(rhs);
return;
case IREE_UK_X32B_SUBI:
ASSI32(out) = ASUI32(lhs) - ASUI32(rhs);
return;
default:
*result_code = 1;
}
}
// Computes a single element of an x32u opcode. On error, should set
// |*result_code| to a non-zero value (but should not touch it otherwise).
static void iree_uk_generic_x32u_op(iree_uk_x32u_opcode_t opcode,
int* result_code,
const iree_uk_uint32_t* in,
iree_uk_uint32_t* out) {
switch (opcode) {
case IREE_UK_X32U_ABSF:
ASF32(out) = fabsf(ASF32(in));
return;
case IREE_UK_X32U_CEILF:
ASF32(out) = ceilf(ASF32(in));
return;
case IREE_UK_X32U_CTLZ:
ASUI32(out) = iree_uk_count_leading_zeros_u32(ASUI32(in));
return;
case IREE_UK_X32U_EXPF:
ASF32(out) = expf(ASF32(in));
return;
case IREE_UK_X32U_FLOORF:
ASF32(out) = floorf(ASF32(in));
return;
case IREE_UK_X32U_LOGF:
ASF32(out) = logf(ASF32(in));
return;
case IREE_UK_X32U_NEGF:
ASF32(out) = -ASF32(in);
return;
case IREE_UK_X32U_RSQRTF:
ASF32(out) = 1.0f / sqrtf(ASF32(in));
return;
default:
*result_code = 1;
}
}
//===----------------------------------------------------------------------===//
// Opcode dispatch entry points.
//===----------------------------------------------------------------------===//
// Generic 32bit binary kernels.
IREE_UK_ATTRIBUTE_NOINLINE static int iree_uk_generic_x32b_2d(
iree_uk_x32b_opcode_t opcode,
// LHS.
const iree_uk_uint32_t* lhs, iree_uk_index_t lhs_offset,
iree_uk_index_t lhs_stride0, iree_uk_index_t lhs_stride1,
// RHS
const iree_uk_uint32_t* rhs, iree_uk_index_t rhs_offset,
iree_uk_index_t rhs_stride0, iree_uk_index_t rhs_stride1,
// OUT.
iree_uk_uint32_t* IREE_UK_RESTRICT out, iree_uk_index_t out_offset,
iree_uk_index_t out_stride0, iree_uk_index_t out_stride1,
// Sizes.
iree_uk_index_t size0, iree_uk_index_t size1) {
int result_code = 0;
// TODO: Manually unroll to x4 to trigger vectorization.
for (iree_uk_index_t i = 0; i < size0; ++i) {
for (iree_uk_index_t j = 0; j < size1; ++j) {
iree_uk_generic_x32b_op(opcode, &result_code,
&lhs[i * lhs_stride0 + j * lhs_stride1],
&rhs[i * rhs_stride0 + j * rhs_stride1],
&out[i * out_stride0 + j * out_stride1]);
}
}
return result_code;
}
// Generic 32bit unary kernels.
IREE_UK_ATTRIBUTE_NOINLINE static int iree_uk_generic_x32u_2d(
iree_uk_x32u_opcode_t opcode,
// IN.
const iree_uk_uint32_t* in, iree_uk_index_t in_offset,
iree_uk_index_t in_stride0, iree_uk_index_t in_stride1,
// OUT.
iree_uk_uint32_t* IREE_UK_RESTRICT out, iree_uk_index_t out_offset,
iree_uk_index_t out_stride0, iree_uk_index_t out_stride1,
// Sizes.
iree_uk_index_t size0, iree_uk_index_t size1) {
int result_code = 0;
// TODO: Manually unroll to x4 to trigger vectorization.
for (iree_uk_index_t i = 0; i < size0; ++i) {
for (iree_uk_index_t j = 0; j < size1; ++j) {
iree_uk_generic_x32u_op(opcode, &result_code,
&in[i * in_stride0 + j * in_stride1],
&out[i * out_stride0 + j * out_stride1]);
}
}
return result_code;
}
DISPATCH_UKERNEL_BINARY_2D(addf, IREE_UK_X32B_ADDF, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(addi, IREE_UK_X32B_ADDI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(andi, IREE_UK_X32B_ANDI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(divf, IREE_UK_X32B_DIVF, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(divsi, IREE_UK_X32B_DIVSI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(divui, IREE_UK_X32B_DIVUI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(mulf, IREE_UK_X32B_MULF, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(muli, IREE_UK_X32B_MULI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(ori, IREE_UK_X32B_ORI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(shli, IREE_UK_X32B_SHLI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(shrsi, IREE_UK_X32B_SHRSI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(shrui, IREE_UK_X32B_SHRUI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(subf, IREE_UK_X32B_SUBF, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(subi, IREE_UK_X32B_SUBI, iree_uk_uint32_t, x32b);
DISPATCH_UKERNEL_BINARY_2D(xori, IREE_UKENREL_X32B_XORI, iree_uk_uint32_t,
x32b);
DISPATCH_UKERNEL_UNARY_2D(absf, IREE_UK_X32U_ABSF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(ceilf, IREE_UK_X32U_CEILF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(ctlz, IREE_UK_X32U_CTLZ, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(expf, IREE_UK_X32U_EXPF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(floorf, IREE_UK_X32U_FLOORF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(logf, IREE_UK_X32U_LOGF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(negf, IREE_UK_X32U_NEGF, iree_uk_uint32_t, x32u);
DISPATCH_UKERNEL_UNARY_2D(rsqrtf, IREE_UK_X32U_RSQRTF, iree_uk_uint32_t, x32u);