blob: 3ce33df893eb305e2e9adf00fbd7c8e67cf15eb0 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file codegen_opencl.cc
*/
#include "codegen_opencl.h"
#include <tvm/ffi/cast.h>
#include <tvm/ffi/reflection/registry.h>
#include <cmath>
#include <string>
#include <vector>
#include "../../../runtime/thread_storage_scope.h"
#include "../../../target/build_common.h"
#include "../runtime/texture.h"
#include "opencl_fallback_module.h"
namespace tvm {
namespace codegen {
namespace {
const VarNode* TryUnwrapTextureVar(const Expr& texture) {
if (const auto* var = texture.as<VarNode>()) {
return var;
}
if (const auto* call = texture.as<CallNode>(); call && call->op.same_as(builtin::buffer_data())) {
TVM_FFI_ICHECK_EQ(call->args.size(), 1U);
const auto* buffer = call->args[0].as<VarNode>();
TVM_FFI_ICHECK(buffer && buffer->ty.as<BufferTypeNode>())
<< "buffer_data expects a Var with BufferType";
return buffer;
}
return nullptr;
}
struct TextureArgument {
const VarNode* var;
const PointerTypeNode* pointer_type;
};
TextureArgument UnwrapTextureArgument(const Expr& texture) {
const auto* var = TryUnwrapTextureVar(texture);
TVM_FFI_ICHECK(var)
<< "Texture arguments must be a pointer Var or a buffer_data(BufferVar) projection";
const auto* pointer_type = texture->ty.as<PointerTypeNode>();
TVM_FFI_ICHECK(pointer_type) << "Texture arguments must have PointerType";
TVM_FFI_ICHECK(runtime::IsTextureStorage(std::string(pointer_type->storage_scope)))
<< "Texture intrinsics only support texture buffers";
return {var, pointer_type};
}
} // namespace
class InferTextureAccess : public StmtExprVisitor {
public:
static constexpr const uint8_t kReadAccess = 1;
static constexpr const uint8_t kWriteAccess = 2;
InferTextureAccess() {}
using StmtExprVisitor::VisitExpr_;
using StmtExprVisitor::VisitStmt_;
std::unordered_map<const VarNode*, std::string> Infer(const Stmt& n) {
StmtExprVisitor::VisitStmt(n);
std::unordered_map<const VarNode*, std::string> storage_scope_qualifiers;
for (auto& texture : var_access_map_) {
if (texture.second == kReadAccess) {
storage_scope_qualifiers.insert({texture.first, "texture_read"});
} else if (texture.second == kWriteAccess) {
storage_scope_qualifiers.insert({texture.first, "texture_write"});
} else if (texture.second == (kReadAccess | kWriteAccess)) {
storage_scope_qualifiers.insert({texture.first, ""});
}
}
return storage_scope_qualifiers;
}
void VisitStmt_(const DeclBufferNode* op) final {
if (const VarNode* source = TryUnwrapTextureVar(op->data)) {
auto it = buffer_data_map_.find(source);
buffer_data_map_[op->buffer.get()] = it == buffer_data_map_.end() ? source : it->second;
}
StmtExprVisitor::VisitStmt_(op);
}
void VisitExpr_(const CallNode* op) final {
if (op->op.same_as(builtin::texture2d_load())) {
const VarNode* texture = UnwrapTextureArgument(op->args[0]).var;
auto it = buffer_data_map_.find(texture);
var_access_map_[it == buffer_data_map_.end() ? texture : it->second] |= kReadAccess;
} else if (op->op.same_as(builtin::texture2d_store())) {
const VarNode* texture = UnwrapTextureArgument(op->args[0]).var;
auto it = buffer_data_map_.find(texture);
var_access_map_[it == buffer_data_map_.end() ? texture : it->second] |= kWriteAccess;
}
StmtExprVisitor::VisitExpr_(op);
}
private:
std::unordered_map<const VarNode*, uint8_t> var_access_map_;
std::unordered_map<const VarNode*, const VarNode*> buffer_data_map_;
};
CodeGenOpenCL::CodeGenOpenCL() {
// Set OpenCL specific restrict keyword
restrict_keyword_ = "restrict";
}
void CodeGenOpenCL::InitFuncState(const PrimFunc& f) {
CodeGenC::InitFuncState(f);
this->SetTextureScope(InferTextureAccess().Infer(f->body));
for (Var arg : f->params) {
auto ptr_type = arg->ty.as<PointerTypeNode>();
if (ptr_type && runtime::IsTextureStorage(std::string(ptr_type->storage_scope))) {
// Storage scope qualifiers for textures are inferred
// and set prior to function codegen.
continue;
}
if (ptr_type) {
alloc_storage_scope_[arg.get()] = "global";
}
}
}
void CodeGenOpenCL::PrintFuncPrefix(std::ostream& os) { os << "__kernel "; }
void CodeGenOpenCL::PreFunctionBody(const PrimFunc& f) {
for (Var arg : f->params) {
auto ptr_type = arg->ty.as<PointerTypeNode>();
if (ptr_type && runtime::IsTextureStorage(std::string(ptr_type->storage_scope))) {
this->stream << " const sampler_t image_sampler = "
"CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;\n";
return;
}
}
}
std::string CodeGenOpenCL::Finish() {
// inject extension enable pragma for fp16 and fp64
if (enable_fp16_) {
decl_stream << "#ifdef cl_khr_fp16\n"
"#pragma OPENCL EXTENSION cl_khr_fp16 : enable\n"
"#elif defined(cl_amd_fp16)\n"
"#pragma OPENCL EXTENSION cl_amd_fp16 : enable\n"
"#else\n"
"#error \"Half precision floating point not supported"
" by OpenCL implementation on your device.\" \n"
"#endif\n\n";
}
if (enable_fp64_) {
decl_stream << "#ifdef cl_khr_fp64\n"
"#pragma OPENCL EXTENSION cl_khr_fp64 : enable\n"
"#elif defined(cl_amd_fp64)\n"
"#pragma OPENCL EXTENSION cl_amd_fp64 : enable\n"
"#else\n"
"#error \"Double precision floating point not supported"
" by OpenCL implementation on your device.\" \n"
"#endif\n\n";
}
// Enable atomic_add used by get_valid_counts. Only needed for OpenCL < 1.1.
if (enable_atomics_) {
decl_stream << "#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable\n"
"#pragma OPENCL EXTENSION cl_khr_global_int32_extended_atomics : enable\n\n";
decl_stream << "__inline float atomic_add_float_emu(volatile __global float* sum, const float "
"toAdd) {\n"
"float next_value = 0;"
"float prev_value = 0;"
"do {\n"
"prev_value =*(sum);\n"
"next_value =prev_value + toAdd;\n"
"} while(atomic_cmpxchg((volatile global int *)(sum), *((int*)&prev_value), "
"*((int*)&next_value)) != *((int*)&prev_value));\n"
"return next_value;\n}\n";
}
// Enable OpenCL 1.2 sampler-less texture reads, but utilize
// provided sampler in OpenCL 2.0.
if (enable_compliant_texture_reads_) {
// TODO(csullivan, lunderberg): Extend device attribute querying to support remote devices
// generically through the device API such that a target can be created from a specific device's
// attributes and utilized during codegen. Potential generlization of #8127 (c02cafb) for remote
// devices.
//
// E.g. Only provide an image sampler when the local or remote device supports OpenCL 2.0,
// see below for context.
//
// For backwards compatibility with OpenCL 1.2, sampler-less read_image calls are used.
// By default in sampler-less read_image calls OpenCL defaults to
// sampler_ = "CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST";
// See section 6.12.14.3 Built-in Image Sampler-less Read Functions in the OpenCL 1.2
// specification. For OpenCL 2.0 it can be preferable to use,
// sampler_ = "CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST";
// For now we rely on OpenCL preprocessor directives to utilize the correct behavior
// depending on the OpenCL version detected at OpenCL compile time.
decl_stream << "#ifdef __OPENCL_VERSION__\n"
<< "#if __OPENCL_VERSION__ == CL_VERSION_2_0"
<< " || __OPENCL_VERSION__ == CL_VERSION_3_0 \n"
<< "#define READ_IMAGEH(image, sampler, coord) "
<< "read_imageh(image, sampler, coord)\n"
<< "#define READ_IMAGEF(image, sampler, coord) "
<< "read_imagef(image, sampler, coord)\n"
<< "#else\n"
<< "#define READ_IMAGEH(image, sampler, coord) "
<< "read_imageh(image, coord)\n"
<< "#define READ_IMAGEF(image, sampler, coord) "
<< "read_imagef(image, coord)\n"
<< "#endif\n"
<< "#endif\n\n";
}
return CodeGenC::Finish();
}
void CodeGenOpenCL::BindThreadIndex(const IterVar& iv) {
TVM_FFI_ICHECK(!var_idmap_.count(iv->var.get()));
runtime::ThreadScope ts = runtime::ThreadScope::Create(iv->thread_tag);
std::ostringstream os;
if (ts.rank == 1) {
os << "get_local_id(" << ts.dim_index << ")";
} else {
os << "get_group_id(" << ts.dim_index << ")";
}
var_idmap_[iv->var.get()] = CastFromTo(os.str(), PrimType::UInt(64), iv->var.ty());
}
void CodeGenOpenCL::PrintType(const PrimType& t, std::ostream& os) { // NOLINT(*)
int lanes = t.lanes();
if (t.IsVoid()) {
os << "void";
return;
}
if (t == PrimType::Bool()) {
os << "bool";
return;
}
bool fail = false;
if (t.code() == DLDataTypeCode::kDLFloat) {
switch (t.bits()) {
case 16:
os << "half";
enable_fp16_ = true;
break;
case 32:
os << "float";
break;
case 64:
os << "double";
enable_fp64_ = true;
break;
default:
fail = true;
break;
}
if (!fail && lanes == 1) return;
if (!fail && ((lanes >= 2 && lanes <= 4) || lanes == 8 || lanes == 16)) {
os << lanes;
return;
}
} else if (t.MatchesCode(DLDataTypeCode::kDLBool)) {
os << "uint";
if (!fail && ((lanes >= 2 && lanes <= 4) || lanes == 8 || lanes == 16)) {
os << lanes;
return;
}
} else if (t.MatchesCode(DLDataTypeCode::kDLUInt, DLDataTypeCode::kDLInt)) {
if (t.MatchesCode(DLDataTypeCode::kDLUInt)) {
os << 'u';
}
switch (t.bits()) {
case 8:
os << "char";
break;
case 16:
os << "short";
break;
case 32:
os << "int";
break;
case 64:
os << "long";
break;
case 1:
os << "int";
break;
default:
fail = true;
break;
}
if (!fail && lanes == 1) return;
if (!fail && ((lanes >= 2 && lanes <= 4) || lanes == 8 || lanes == 16)) {
os << lanes;
return;
}
}
TVM_FFI_THROW(InternalError) << "Cannot convert type " << ffi::DLDataTypeToString(t->dtype)
<< " to OpenCL type";
}
void CodeGenOpenCL::PrintType(const Type& type, std::ostream& os) { // NOLINT(*)
if (auto* ptr = type.as<PrimTypeNode>()) {
return PrintType(ffi::GetRef<PrimType>(ptr), os);
} else if (auto* ptr = type.as<PointerTypeNode>()) {
if (runtime::IsTextureStorage(std::string(ptr->storage_scope))) {
os << "image2d_array_t";
} else {
PrintType(ptr->element_type, os);
os << '*';
}
} else if (IsVoidType(type)) {
os << "void";
} else {
TVM_FFI_THROW(InternalError) << "Type " << type << " does not have a corresponding C Type";
}
}
void CodeGenOpenCL::PrintVecAddr(const VarNode* buffer, const PrimType& t, PrimExpr base,
std::ostream& os) { // NOLINT(*)
const VarNode* buffer_var = buffer;
PrimType elem_type = t.WithLanes(1);
if (!HandleTypeMatch(buffer_var, elem_type)) {
os << '(';
auto it = alloc_storage_scope_.find(buffer_var);
if (it != alloc_storage_scope_.end()) {
PrintStorageScope(it->second, os);
}
PrintType(elem_type, os);
os << "*)";
}
os << GetVarID(buffer_var) << " + ";
PrintExpr(base, os);
}
std::string CodeGenOpenCL::GetVecLoad(const PrimType& t, const VarNode* buffer, PrimExpr base) {
std::ostringstream os;
os << "vload" << t.lanes() << "(0, ";
PrintVecAddr(buffer, t, base, os);
os << ")";
return os.str();
}
void CodeGenOpenCL::PrintVecStore(const VarNode* buffer, const PrimType& t, PrimExpr base,
const std::string& value) {
this->PrintIndent();
stream << "vstore" << t.lanes() << "(" << value << ", 0, ";
PrintVecAddr(buffer, t, base, stream);
stream << ");\n";
}
void CodeGenOpenCL::PrintVecElemLoadExpr(const PrimType& t, int i, const std::string& value,
std::ostream& os) { // NOLINT(*)
int lanes = t.lanes();
TVM_FFI_ICHECK_GT(lanes, 1);
if (t.bits() == 8 && (t.MatchesCode(DLDataTypeCode::kDLInt, DLDataTypeCode::kDLUInt))) {
if (i != 0) {
os << "|";
}
os << "((0x000000ff << " << i * 8 << ") & (" << value << " << " << i * 8 << "))";
return;
}
if (i == 0) {
// NOTE: opencl print things as (float2)(v0, v1)
os << "((";
PrintType(t, os);
os << ")(";
}
os << value;
if (i != lanes - 1) {
os << ",";
} else {
os << "))";
}
return;
}
void CodeGenOpenCL::PrintStorageSync(const CallNode* op) {
const std::string& sync = op->args[0].as<StringImmNode>()->value;
if (sync == "warp") {
this->PrintIndent();
this->stream << "barrier(CLK_LOCAL_MEM_FENCE);\n";
} else if (sync == "shared") {
this->PrintIndent();
this->stream << "barrier(CLK_LOCAL_MEM_FENCE);\n";
} else if (sync == "global") {
TVM_FFI_THROW(InternalError) << "not supported";
}
}
void CodeGenOpenCL::PrintStorageScope(const std::string& scope, std::ostream& os) { // NOLINT(*)
if (scope == "global") {
os << "__global ";
} else if (scope == "shared") {
os << "__local ";
} else if (scope == "texture_read") {
os << "__read_only ";
} else if (scope == "texture_write") {
os << "__write_only ";
}
}
void CodeGenOpenCL::PrintRestrict(const Var& v, std::ostream& os) {
// Apply restrict qualifer for non-texture types only
if (auto* ptr = v->ty.as<PointerTypeNode>()) {
if (!runtime::IsTextureStorage(std::string(ptr->storage_scope))) {
os << ' ' << restrict_keyword_;
}
}
}
std::string CodeGenOpenCL::CastFromTo(std::string value, const PrimType& from,
const PrimType& target) {
if (from == target) return value;
return CastTo(value, target);
}
std::string CodeGenOpenCL::CastTo(std::string value, const PrimType& target) {
std::ostringstream os;
if (target == PrimType::Bool()) {
os << "(";
os << "(";
this->PrintType(target, os);
os << ")" << value << ")";
return os.str();
} else {
os << "(";
os << "convert_";
this->PrintType(target, os);
os << "(" << value << "))";
return os.str();
}
}
void CodeGenOpenCL::VisitStmt_(const AllocBufferNode* op) {
// Compute constant_size from buffer shape
size_t constant_size = 1;
for (const auto& dim : op->buffer->shape) {
const IntImmNode* dim_imm = dim.as<IntImmNode>();
TVM_FFI_ICHECK(dim_imm) << "Can only handle constant size stack allocation for now";
constant_size *= dim_imm->value;
}
allocation_size_.insert({op->buffer.get(), constant_size * op->buffer->dtype.lanes()});
CodeGenC::VisitStmt_(op);
}
void CodeGenOpenCL::VisitExpr_(const CallNode* op, std::ostream& os) {
if (op->op.same_as(builtin::address_of())) {
// Overload tvm_address_of to add storage scope (e.g. __global).
const BufferLoadNode* load = op->args[0].as<BufferLoadNode>();
TVM_FFI_ICHECK(op->args.size() == 1 && load);
TVM_FFI_ICHECK_EQ(load->indices.size(), 1)
<< "CodeGenOpenCL only supports flat memory allocations.";
os << "((";
auto it = alloc_storage_scope_.find(load->buffer.get());
if (it != alloc_storage_scope_.end()) {
PrintStorageScope(it->second, os);
}
this->PrintType(load->ty.as_or_throw<PrimType>().WithLanes(1), os);
os << " *)" << this->GetVarID(load->buffer.get()) << " + ";
this->PrintExpr(load->indices[0], os);
os << ')';
} else if (op->op.same_as(builtin::texture2d_store())) {
TextureArgument texture = UnwrapTextureArgument(op->args[0]);
const int channel_size = op->args[4].as_or_throw<IntImm>()->value;
TVM_FFI_ICHECK(channel_size == 64 || channel_size == 128)
<< "Unsupported Channel Size: " << channel_size;
PrimType channel_type(runtime::GetChannelType(channel_size));
PrimType buffer_type = texture.pointer_type->element_type.as_or_throw<PrimType>();
std::stringstream ss;
this->PrintExpr(op->args[5].as_or_throw<PrimExpr>(), ss);
std::string value;
value = this->SSAGetID(ss.str(), buffer_type.WithLanes(channel_size / buffer_type.bits()));
if (channel_size == 64) {
os << "write_imageh(";
} else if (channel_size == 128) {
os << "write_imagef(";
} else {
TVM_FFI_THROW(InternalError) << "Unsupported Channel Size: " << channel_size;
}
os << this->GetVarID(texture.var);
os << ", ";
os << "(int4)(";
this->PrintExpr(op->args[1].as_or_throw<PrimExpr>(), os);
os << ", ";
this->PrintExpr(op->args[2].as_or_throw<PrimExpr>(), os);
os << ", ";
this->PrintExpr(op->args[3].as_or_throw<PrimExpr>(), os);
os << ", ";
this->PrintExpr(IntImm::Int32(0), os);
os << "), ";
os << "as_";
this->PrintType(channel_type, os);
os << "(" << value << ")";
os << ")";
} else if (op->op.same_as(builtin::texture2d_load())) {
TextureArgument texture = UnwrapTextureArgument(op->args[0]);
enable_compliant_texture_reads_ = true;
std::stringstream ss;
const int channel_size = op->args[4].as_or_throw<IntImm>()->value;
PrimType op_ty = op->ty.as_or_throw<PrimType>();
const int data_lanes = channel_size / op_ty.bits();
TVM_FFI_ICHECK(channel_size == 64 || channel_size == 128)
<< "Unsupported Channel Size: " << channel_size;
ss << "as_";
this->PrintType(op_ty.WithLanes(data_lanes), ss);
ss << "(";
if (channel_size == 64) {
ss << "READ_IMAGEH(";
} else if (channel_size == 128) {
ss << "READ_IMAGEF(";
} else {
TVM_FFI_THROW(InternalError) << "Unsupported Channel Size: " << channel_size;
}
ss << this->GetVarID(texture.var);
ss << ", ";
ss << "image_sampler, ";
ss << "((int4)(";
this->PrintExpr(op->args[1].as_or_throw<PrimExpr>(), ss);
ss << ", ";
this->PrintExpr(op->args[2].as_or_throw<PrimExpr>(), ss);
ss << ", ";
this->PrintExpr(op->args[3].as_or_throw<PrimExpr>(), ss);
ss << ", ";
this->PrintExpr(IntImm::Int32(0), ss);
ss << "))))";
std::string rhs = SSAGetID(ss.str(), op_ty.WithLanes(data_lanes));
if (auto ramp = op->args.back().as<RampNode>()) {
if (ramp->base.as<IntImmNode>() && *tirx::as_const_int(ramp->base) == 0 &&
*tirx::as_const_int(ramp->lanes) == data_lanes &&
*tirx::as_const_int(ramp->stride) == 1) {
os << rhs;
} else if (*tirx::as_const_int(ramp->stride) == 1) {
os << "(*(";
this->PrintType(op_ty.WithLanes(*tirx::as_const_int(ramp->lanes)), os);
os << "*)";
os << "((";
this->PrintType(op_ty.WithLanes(1), os);
os << "*)&" << rhs << " + ";
this->PrintExpr(ramp->base, os);
os << "))";
} else {
TVM_FFI_THROW(InternalError) << "Unsupported Texture Load Args";
}
} else {
os << "((";
this->PrintType(op_ty.WithLanes(1), os);
os << "*)&" << rhs << ")[";
this->PrintExpr(op->args.back().as_or_throw<PrimExpr>(), os);
os << "]";
}
} else if (op->op.same_as(builtin_call_extern_) || op->op.same_as(builtin_call_pure_extern_)) {
auto func = op->args[0].as_or_throw<StringImm>();
// Enable atomics extension if used.
if (func->value == "atomic_add" &&
op->ty.as_or_throw<PrimType>().code() == DLDataTypeCode::kDLFloat) {
enable_atomics_ = true;
ffi::Array<Expr> args = op->args;
this->PrintCallExtern(op->ty, "atomic_add_float_emu", args, true, os);
} else if (func->value == "nearbyint") {
ffi::Array<Expr> args = op->args;
this->PrintCallExtern(op->ty, "rint", args, true, os);
} else {
if (func->value == "atomic_add") {
enable_atomics_ = true;
}
CodeGenC::VisitExpr_(op, os);
}
} else {
CodeGenC::VisitExpr_(op, os);
}
}
void CodeGenOpenCL::VisitExpr_(const BroadcastNode* op, std::ostream& os) { // NOLINT(*)
std::string v = PrintExpr(op->value);
int lanes = op->ty.as_or_throw<PrimType>().lanes();
os << "((";
PrintType(op->ty.as_or_throw<PrimType>(), os);
os << ")(";
for (int i = 0; i < lanes; ++i) {
if (i != 0) os << ", ";
os << v;
}
os << "))";
}
void CodeGenOpenCL::VisitExpr_(const RampNode* op, std::ostream& os) { // NOLINT(*)
os << "((";
PrintType(op->ty.as_or_throw<PrimType>(), os);
os << ")(";
int lanes = op->ty.as_or_throw<PrimType>().lanes();
for (int i = 0; i < lanes; i++) {
os << "(" << PrintExpr(op->base) << ")"
<< "+(" << PrintExpr(op->stride) << "*" << i << ")";
if (i != lanes - 1) os << ", ";
}
os << "))";
}
void CodeGenOpenCL::VisitExpr_(const FloatImmNode* op, std::ostream& os) { // NOLINT(*)
if (std::isinf(op->value)) {
if (op->value < 0) {
os << "-";
}
os << "INFINITY";
} else if (std::isnan(op->value)) {
os << "NAN";
} else {
CodeGenC::VisitExpr_(op, os);
}
}
template <typename T>
inline void PrintBinaryExpr(const T* op, const char* opstr, std::ostream& os, CodeGenOpenCL* p) {
if (op->ty.template as_or_throw<PrimType>().lanes() == 1) {
os << opstr << "((";
p->PrintType(op->a.ty(), os);
os << ")";
p->PrintExpr(op->a, os);
os << ", (";
p->PrintType(op->b.ty(), os);
os << ")";
p->PrintExpr(op->b, os);
os << ')';
} else {
p->PrintVecBinaryOp(opstr, op->ty.template as_or_throw<PrimType>(), op->a, op->b, os);
}
}
void CodeGenOpenCL::VisitExpr_(const MinNode* op, std::ostream& os) {
PrintBinaryExpr(op, "min", os, this);
}
void CodeGenOpenCL::VisitExpr_(const MaxNode* op, std::ostream& os) {
PrintBinaryExpr(op, "max", os, this);
}
void CodeGenOpenCL::VisitExpr_(const ModNode* op, std::ostream& os) { // NOLINT(*)
std::string opstr;
PrimType op_ty = op->ty.as_or_throw<PrimType>();
if (op_ty.MatchesCode(DLDataTypeCode::kDLInt, DLDataTypeCode::kDLUInt)) {
opstr = "%";
} else {
TVM_FFI_ICHECK(op_ty.code() == DLDataTypeCode::kDLFloat)
<< "Expected floating point or integer dtype in Mod, but got "
<< ffi::DLDataTypeToString(op->ty.as_or_throw<PrimType>()->dtype);
opstr = "fmod";
}
if (op_ty.lanes() == 1) {
if (isalpha(opstr.c_str()[0])) {
os << opstr.c_str() << '(';
this->PrintExpr(op->a, os);
os << ", ";
this->PrintExpr(op->b, os);
os << ')';
} else {
os << '(';
this->PrintExpr(op->a, os);
os << ' ' << opstr.c_str() << ' ';
this->PrintExpr(op->b, os);
os << ')';
}
} else {
this->PrintVecBinaryOp(opstr.c_str(), op->ty.as_or_throw<PrimType>(), op->a, op->b, os);
}
}
void CodeGenOpenCL::VisitExpr_(const AndNode* op, std::ostream& os) {
std::ostringstream oss;
os << "(";
this->PrintExpr(op->a, oss);
os << CastTo(oss.str(), op->ty.as_or_throw<PrimType>());
oss.str("");
os << " && ";
this->PrintExpr(op->b, oss);
os << CastTo(oss.str(), op->ty.as_or_throw<PrimType>());
os << ")";
}
void CodeGenOpenCL::VisitExpr_(const OrNode* op, std::ostream& os) {
std::ostringstream oss;
os << "(";
this->PrintExpr(op->a, oss);
os << CastTo(oss.str(), op->ty.as_or_throw<PrimType>());
oss.str("");
os << " || ";
this->PrintExpr(op->b, oss);
os << CastTo(oss.str(), op->ty.as_or_throw<PrimType>());
os << ")";
}
void CodeGenOpenCL::VisitExpr_(const SelectNode* op, std::ostream& os) {
PrimType op_ty = op->ty.as_or_throw<PrimType>();
std::ostringstream oss;
os << "select(";
PrintExpr(op->false_value, oss);
os << CastFromTo(oss.str(), op->false_value.ty(), op_ty);
oss.str("");
os << ", ";
PrintExpr(op->true_value, oss);
os << CastFromTo(oss.str(), op->true_value.ty(), op_ty);
oss.str("");
os << ", ";
PrintExpr(op->condition, oss);
if (op_ty.code() == DLDataTypeCode::kDLFloat) {
os << CastTo(oss.str(), PrimType::Int(op_ty.bits(), op_ty.lanes()));
} else {
os << CastFromTo(oss.str(), op->condition.ty(), op_ty);
}
os << ")";
}
void CodeGenOpenCL::SetTextureScope(
const std::unordered_map<const VarNode*, std::string>& scope) { // NOLINT(*)
for (auto& texture : scope) {
alloc_storage_scope_.insert(texture);
}
}
ffi::Module BuildOpenCL(IRModule mod, Target target) {
bool output_ssa = false;
ffi::Map<GlobalVar, PrimFunc> functions;
for (auto [gvar, base_func] : mod->functions) {
TVM_FFI_ICHECK(base_func->IsInstance<PrimFuncNode>())
<< "CodeGenOpenCL: Can only take PrimFunc";
auto prim_func = base_func.as_or_throw<PrimFunc>();
auto calling_conv = prim_func->GetAttr<CallingConv>(tvm::attr::kCallingConv);
TVM_FFI_ICHECK(calling_conv.has_value())
<< "CodeGenOpenCL: expected kCallingConv attribute to be set.";
TVM_FFI_ICHECK(calling_conv.value() == CallingConv::kDeviceKernelLaunch)
<< "CodeGenOpenCL: expect calling_conv equals CallingConv::kDeviceKernelLaunch, but got "
<< static_cast<int>(calling_conv.value());
functions.Set(gvar, prim_func);
}
std::stringstream code;
const auto fpostproc = tvm::ffi::Function::GetGlobal("tvm_callback_opencl_postproc");
for (auto [gvar, prim_func] : functions) {
code << "// Function: " << gvar->name_hint << std::endl;
CodeGenOpenCL cg;
cg.Init(output_ssa);
for (auto [other_gvar, other_prim_func] : functions) {
cg.DeclareFunction(other_gvar, other_prim_func);
}
cg.AddFunction(gvar, prim_func);
std::string fsource = cg.Finish();
if (fpostproc) {
fsource = (*fpostproc)(fsource, target).cast<std::string>();
}
code << fsource;
}
// The aggregated OpenCL C source dump is preserved in the in-memory
// source map keyed by "cl" — only used by InspectSource and never
// serialized.
std::string code_str = code.str();
ffi::Map<ffi::String, ffi::String> source;
source.Set("cl", code_str);
return target::OpenCLModuleCreateWithFallback(ffi::Bytes(code_str), ffi::String("cl"),
ExtractFuncInfo(mod), std::move(source));
}
void RegisterOpenCLCodegen() {
static bool registered = false;
if (registered) return;
registered = true;
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("target.build.opencl", BuildOpenCL);
}
ffi::String DeviceScopeCompatibilityFromTarget(Target target, ffi::String memory_scope) {
bool is_adreno = target->HasKey("adreno");
if (is_adreno) {
return ffi::String("global");
}
return memory_scope;
}
void RegisterOpenCLDeviceScopeCompatibility() {
static bool registered = false;
if (registered) return;
registered = true;
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("DeviceScopeCompatibility.opencl", DeviceScopeCompatibilityFromTarget);
}
} // namespace codegen
} // namespace tvm