Files
paddlepaddle--paddle/paddle/cinn/backends/llvm/codegen_llvm.cc
T
2026-07-13 12:40:42 +08:00

1871 lines
64 KiB
C++

// Copyright (c) 2021 CINN Authors. All Rights Reserved.
//
// Licensed 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.
#include "paddle/cinn/backends/llvm/codegen_llvm.h"
#include <glog/logging.h>
#include <glog/stl_logging.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/IR/Instruction.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/Intrinsics.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Metadata.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/raw_ostream.h>
#include <algorithm>
#include <functional>
#include <iostream>
#include <numeric>
#include <sstream>
#include <string>
#include <type_traits>
#include "paddle/common/enforce.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/Alignment.h"
#include "paddle/cinn/backends/extern_func_emitter.h"
#include "paddle/cinn/backends/extern_func_emitter_builtin.h"
#include "paddle/cinn/backends/llvm/llvm_util.h"
#include "paddle/cinn/common/type.h"
#include "paddle/cinn/ir/ir_printer.h"
#include "paddle/cinn/ir/op/ir_operators.h"
#include "paddle/cinn/ir/utils/ir_verify.h"
#include "paddle/cinn/optim/ir_simplify.h"
#include "paddle/cinn/runtime/cinn_runtime.h"
#include "paddle/cinn/runtime/intrinsic.h"
#include "paddle/cinn/utils/string.h"
#include "paddle/phi/backends/device_manager.h"
namespace cinn {
namespace backends {
using BinaryInstruction = llvm::Instruction::BinaryOps;
using cinn::common::bfloat16;
using cinn::common::float16;
using cinn::common::float8e4m3;
namespace {
template <typename T>
auto NodeToExpr(const T *node) {
std::ostringstream oss;
// oss << "\033[32m";
oss << ir::Expr(const_cast<T *>(node));
// oss << "\033[0m";
return oss.str();
}
bool is_integral_type(cinn::common::Type t) {
return t.is_int() || t.is_uint();
}
bool is_floating_type(cinn::common::Type t) { return t.is_float(); }
int GetFixedVectorNumElements(llvm::Type *type) {
return llvm::cast<llvm::FixedVectorType>(type)->getNumElements();
}
llvm::ElementCount GetFixedElementCount(int lanes) {
return llvm::ElementCount::getFixed(lanes);
}
llvm::Value *EmitComparison(llvm::CmpInst::Predicate predicate,
llvm::Value *lhs,
llvm::Value *rhs,
llvm::IRBuilder<> *b) {
llvm::Value *comparison_result{nullptr};
if (lhs->getType()->isIntegerTy()) {
comparison_result = b->CreateICmp(predicate, lhs, rhs);
} else {
comparison_result = b->CreateFCmp(predicate, lhs, rhs);
}
return comparison_result;
}
#define __IR_EMITTER_NOT_IMPLEMENTED(__op) CINN_NOT_IMPLEMENTED
int NextPowerOfTwo(int x) {
for (int p2 = 1;; p2 *= 2) {
if (p2 >= x) {
return p2;
}
}
return 0;
}
} // namespace
CodeGenLLVM::CodeGenLLVM(llvm::Module *m,
llvm::IRBuilder<> *b,
const std::shared_ptr<SymbolTable> &symbol_table,
const Target &target)
: m_(m), b_(b), symbol_table_(symbol_table), target_(target) {
if (!symbol_table.get()) {
symbol_table_ = std::make_shared<SymbolTable>();
}
symbol_table_->PushScope(); // Create a new scope by default.
md_builder_ = std::make_unique<llvm::MDBuilder>(b_->getContext());
md_tbaa_root_ = md_builder_->createTBAARoot("cinn-tbaa");
md_tbaa_alias_set_ = md_builder_->createTBAANode("cinn-alias", md_tbaa_root_);
InitTarget(target_);
RegisterCustomizedPODStructType();
}
CodeGenLLVM::~CodeGenLLVM() {}
llvm::Value *CodeGenLLVM::EmitVectorSlice(llvm::Value *vec,
int begin,
int extent) {
int numel = GetFixedVectorNumElements(vec->getType());
if (extent == numel && begin == 0) return vec;
CHECK(begin >= 0 && extent <= numel) << "Slicing out of bound!";
std::vector<llvm::Constant *> indices(extent);
for (int i = 0; i < extent; i++) {
llvm::Constant **v = &indices[i];
if (begin + i >= 0 && begin + i < numel) {
*v = llvm::ConstantInt::get(b_->getInt32Ty(), begin + i);
} else {
*v = llvm::UndefValue::get(b_->getInt32Ty());
}
}
return ShuffleVector(vec, vec, llvm::ConstantVector::get(std::move(indices)));
}
llvm::Value *CodeGenLLVM::EmitVectorPad(llvm::Value *vec, int lanes) {
llvm::Value *mask = llvm::UndefValue::get(
llvm::VectorType::get(b_->getInt32Ty(), GetFixedElementCount(lanes)));
int numel = GetFixedVectorNumElements(vec->getType());
CHECK(numel <= lanes);
if (numel == lanes) return vec;
for (int i = 0; i < numel; i++) {
mask = InsertElement(mask,
llvm::ConstantInt::get(b_->getInt32Ty(), i),
llvm::ConstantInt::get(b_->getInt32Ty(), i));
}
return ShuffleVector(vec, vec, mask);
}
llvm::Value *CodeGenLLVM::EmitVectorConcat(std::vector<llvm::Value *> vecs) {
int lanes = 0;
for (auto *v : vecs) {
lanes += GetFixedVectorNumElements(v->getType());
}
while (vecs.size() > 1) {
std::vector<llvm::Value *> new_vecs;
for (size_t i = 0; i < vecs.size() - 1; i += 2) {
auto *lhs = vecs[i];
auto *rhs = vecs[i + 1];
const auto lhs_lanes = GetFixedVectorNumElements(lhs->getType());
const auto rhs_lanes = GetFixedVectorNumElements(rhs->getType());
if (lhs_lanes < rhs_lanes) {
lhs = EmitVectorPad(lhs, rhs_lanes);
} else if (lhs_lanes > rhs_lanes) {
rhs = EmitVectorPad(rhs, lhs_lanes);
}
const auto shared_lanes = std::max(lhs_lanes, rhs_lanes);
std::vector<unsigned> mask(lhs_lanes + rhs_lanes);
std::iota(mask.begin(), std::next(mask.begin(), lhs_lanes), 0);
std::iota(std::next(mask.begin(), lhs_lanes), mask.end(), shared_lanes);
new_vecs.push_back(ShuffleVector(lhs, rhs, mask));
}
if (vecs.size() % 2) {
new_vecs.push_back(vecs.back());
}
vecs = std::move(new_vecs);
}
return EmitVectorSlice(vecs[0], 0, lanes);
}
llvm::Value *CodeGenLLVM::EmitBinaryOp(llvm::Value *lhs,
llvm::Value *rhs,
char opcode,
bool is_integral,
bool is_signed) {
llvm::Instruction::BinaryOps ops;
PADDLE_ENFORCE_EQ(
lhs->getType(),
rhs->getType(),
::common::errors::InvalidArgument(
"the types of operands of binary operation are mismatch"));
switch (opcode) {
case '+':
ops = is_integral ? llvm::Instruction::BinaryOps::Add
: llvm::Instruction::BinaryOps::FAdd;
break;
case '-':
ops = is_integral ? llvm::Instruction::BinaryOps::Sub
: llvm::Instruction::BinaryOps::FSub;
break;
case '*':
ops = is_integral ? llvm::Instruction::BinaryOps::Mul
: llvm::Instruction::BinaryOps::FMul;
break;
case '/':
ops = is_integral ? (is_signed ? llvm::Instruction::BinaryOps::SDiv
: llvm::Instruction::BinaryOps::UDiv)
: llvm::Instruction::BinaryOps::FDiv;
break;
case '%':
ops = is_integral ? (is_signed ? llvm::Instruction::BinaryOps::SRem
: llvm::Instruction::BinaryOps::URem)
: llvm::Instruction::BinaryOps::FRem;
break;
default:
return nullptr;
}
return BinOp(ops, lhs, rhs);
}
llvm::Value *CodeGenLLVM::Visit(const ir::IntImm *op) {
auto *type = b_->getIntNTy(op->type().bits());
return llvm::ConstantInt::get(type, op->value, true);
}
llvm::Value *CodeGenLLVM::Visit(const ir::UIntImm *op) {
if (op->type().is_bool()) {
auto *type = b_->getInt1Ty();
return llvm::ConstantInt::get(type, op->value, false);
}
auto *type = b_->getIntNTy(op->type().bits());
return llvm::ConstantInt::get(type, op->value, false);
}
llvm::Value *CodeGenLLVM::Visit(const ir::FloatImm *op) {
if (op->type().is_float(64)) {
return llvm::ConstantFP::get(b_->getDoubleTy(), op->value);
} else if (op->type().is_float(32)) {
return llvm::ConstantFP::get(b_->getFloatTy(), op->value);
} else if (op->type().is_bfloat16()) {
return llvm::ConstantFP::get(b_->getBFloatTy(), op->value);
} else if (op->type().is_float16()) {
return llvm::ConstantFP::get(b_->getHalfTy(), op->value);
} else if (op->type().is_float8e4m3()) {
PADDLE_THROW(::common::errors::InvalidArgument(
"llvm not support float8 yet.")); // TODO(YuhanXu)
} else {
PADDLE_THROW(::common::errors::InvalidArgument("illegal float type."));
}
return nullptr;
}
llvm::Value *CodeGenLLVM::LLVMGenGlobalStringVar(const std::string &data) {
return b_->CreateGlobalStringPtr(data);
}
llvm::Value *CodeGenLLVM::Visit(const ir::StringImm *op) {
return LLVMGenGlobalStringVar(op->value);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Add *op) {
auto promote_args = std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()}));
return EmitBinaryOp(Visit(&promote_args.at(0)),
Visit(&promote_args.at(1)),
'+',
is_integral_type(op->type()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Sub *op) {
auto promote_args = std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()}));
return EmitBinaryOp(Visit(&promote_args.at(0)),
Visit(&promote_args.at(1)),
'-',
is_integral_type(op->type()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Mul *op) {
auto promote_args = std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()}));
return EmitBinaryOp(Visit(&promote_args.at(0)),
Visit(&promote_args.at(1)),
'*',
is_integral_type(op->type()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Div *op) {
auto promote_args = std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()}));
return EmitBinaryOp(Visit(&promote_args.at(0)),
Visit(&promote_args.at(1)),
'/',
is_integral_type(op->type()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Mod *op) {
auto promote_args = std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()}));
return EmitBinaryOp(Visit(&promote_args.at(0)),
Visit(&promote_args.at(1)),
'%',
is_integral_type(op->type()));
}
#define __IR_EMITTER_DEFINE_CMP_VISITOR(__sop, __uop, __fop) \
auto promote_args = \
std::move(ir::TryElevateInt32ToInt64({op->a(), op->b()})); \
auto *lhs = Visit(&promote_args.at(0)); \
auto *rhs = Visit(&promote_args.at(1)); \
CHECK(promote_args.at(0).type() == promote_args.at(1).type()); \
llvm::CmpInst::Predicate predicate; \
if (promote_args.at(0).type().is_int()) { \
predicate = llvm::CmpInst::ICMP_##__sop; \
} else if (promote_args.at(0).type().is_uint()) { \
predicate = llvm::CmpInst::ICMP_##__uop; \
} else /*float*/ { \
predicate = llvm::CmpInst::FCMP_##__fop; \
} \
return EmitComparison(predicate, lhs, rhs, b_)
llvm::Value *CodeGenLLVM::Visit(const ir::EQ *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(EQ, EQ, OEQ);
}
llvm::Value *CodeGenLLVM::Visit(const ir::NE *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(NE, NE, ONE);
}
llvm::Value *CodeGenLLVM::Visit(const ir::LT *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(SLT, ULT, OLT);
}
llvm::Value *CodeGenLLVM::Visit(const ir::LE *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(SLE, ULE, OLE);
}
llvm::Value *CodeGenLLVM::Visit(const ir::GT *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(SGT, UGT, OGT);
}
llvm::Value *CodeGenLLVM::Visit(const ir::GE *op) {
__IR_EMITTER_DEFINE_CMP_VISITOR(SGE, UGE, OGE);
}
#undef __IR_EMITTER_DEFINE_CMP_VISITOR
llvm::Value *CodeGenLLVM::Visit(const ir::And *op) {
return And(Visit(&op->a()), Visit(&op->b()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Or *op) {
return Or(Visit(&op->a()), Visit(&op->b()));
}
llvm::Value *CodeGenLLVM::Visit(const ir::Min *op) {
auto *lhs = Visit(&op->a());
auto *rhs = Visit(&op->b());
llvm::Value *p{nullptr};
if (op->type().is_int()) {
p = ICmpSLT(lhs, rhs);
} else if (op->type().is_uint()) {
p = ICmpULT(lhs, rhs);
} else /*float*/ {
p = FCmpOLT(lhs, rhs);
}
return Select(p, lhs, rhs);
}
inline bool FuncHasStructSRet(const llvm::Function *callee) {
// StructSRet is constructed by hand, not the LLVM
// function attribute (StructSRet)
const auto *func_type = callee->getFunctionType();
llvm::Type *param_ty = func_type->getParamType(0);
llvm::Type *ret_type = func_type->getReturnType();
return param_ty->isPointerTy() && ret_type->isVoidTy();
}
std::vector<llvm::Value *> AdaptABIArguments(
llvm::IRBuilder<> *builder,
llvm::Function *callee,
std::vector<llvm::Value *> &&input_args) {
// this function decompose the input arguments to primitive elements
// and reconstruct the input arguments according to callee func type
// for example: welford_fp64 (welford_fp64, welford_fp32) ->
// (*struct.welford_fp64*, *struct.welford_fp64*, {<2x float>, float})
// struct_to_int64: if true, struct of 64bit size will be turned into i64
// by bit casting
std::vector<llvm::Value *> output_args;
auto input_it = input_args.begin();
const auto *func_type = callee->getFunctionType();
const int num_params = func_type->getNumParams();
output_args.reserve(num_params);
int param_idx = 0;
// Case 1: check whether we have a pointer for struct ret
if (FuncHasStructSRet(callee)) {
llvm::Type *param_ty = func_type->getParamType(0);
llvm::Value *sret_ptr = builder->CreateAlloca(
param_ty->getPointerElementType(), nullptr, "sret.ptr");
output_args.push_back(sret_ptr);
param_idx++;
}
// traverse all the param in the target func type
for (; param_idx < num_params; param_idx++) {
llvm::Type *param_ty = func_type->getParamType(param_idx);
if (input_it == input_args.end()) break;
llvm::Value *current_input = *input_it;
llvm::Type *input_type = current_input->getType();
if (input_type == param_ty) {
// Case 2: if type matches, just pass it directly
output_args.push_back(current_input);
input_it++;
} else if (param_ty->isPointerTy()) {
// Case 3: if the input type is pointer,
// we need to create a local buffer
PADDLE_ENFORCE_EQ(input_type,
param_ty->getPointerElementType(),
::common::errors::PreconditionNotMet(
"Pointer parameter type mismatch"));
auto *input_ptr = builder->CreateAlloca(input_type, nullptr, "input.ptr");
builder->CreateStore(current_input, input_ptr);
output_args.push_back(input_ptr);
input_it++;
} else if (param_ty->isIntegerTy() && input_type->isIntegerTy()) {
// Case 4: for argidx type, tensor index might be deterministically casted
// to int64_t, so we need to cast the integer type here
if (input_type->getPrimitiveSizeInBits() <
param_ty->getPrimitiveSizeInBits()) { // sign ext
output_args.push_back(
builder->CreateSExt(current_input, param_ty, "sext"));
} else {
output_args.push_back(
builder->CreateTrunc(current_input, param_ty, "trunc"));
}
input_it++;
} else {
PADDLE_THROW(
::common::errors::Fatal("Unhandled case for ABI param adaptation."));
}
}
PADDLE_ENFORCE_EQ(input_it,
input_args.end(),
::common::errors::PreconditionNotMet(
"Not all input args are consumed by the callee"));
return output_args;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Max *op) {
auto *lhs = Visit(&op->a());
auto *rhs = Visit(&op->b());
llvm::Value *p = nullptr;
if (op->type().is_int()) {
p = ICmpSGT(lhs, rhs);
} else if (op->type().is_uint()) {
p = ICmpUGT(lhs, rhs);
} else /*float*/ {
p = FCmpOGT(lhs, rhs);
}
return Select(p, lhs, rhs);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Minus *op) {
auto *v = Visit(&op->v());
return (op->type().is_int() || op->type().is_uint()) ? Neg(v) : FNeg(v);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Not *op) {
return Not(Visit(&op->v()));
}
llvm::Value *CodeGenLLVM::CastCompositeType(const ir::Expr &op_v) {
if (op_v.type().is_customized_type()) {
std::string from_type_name = op_v.type().to_string();
if (from_type_name.find("argidx_") != std::string::npos ||
from_type_name.find("welford_") != std::string::npos) {
auto callee = m_->getFunction("cast_" + from_type_name);
CHECK(callee) << "type casting function is null";
auto func_type = callee->getFunctionType();
auto value = Visit(&op_v);
auto params = AdaptABIArguments(b_, callee, {value});
llvm::Value *call_handle = Call(callee, params, "pod_value_cast");
if (FuncHasStructSRet(callee)) {
// currently, the functions are relatively simple, sret arg can only
// be found in the return type, which is the first argument
// sret created a stack temp value, we need to load from it
auto *sret_ptr = params[0];
auto *sret_ty = sret_ptr->getType()->getPointerElementType();
return b_->CreateLoad(sret_ty, sret_ptr);
} else {
return call_handle;
}
}
}
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Cast *op) {
auto from = op->v().type();
auto to = op->type();
if (auto cast_call = CastCompositeType(op->v())) {
return cast_call;
}
llvm::Type *source = CinnTypeToLLVMType(from, m_);
llvm::Type *target = CinnTypeToLLVMType(to, m_);
CHECK(source) << "source ir type is null";
CHECK(target) << "target ir type is null";
llvm::Value *value = Visit(&op->v());
CHECK(value) << "value is null";
// pod_value_t cast to a value.
if (op->v().type().is_customized_type() &&
op->v().type().customized_type() ==
cinn::common::customized_type::kpod_value_t) { // pod_value_t
// operator
llvm::Function *callee{};
if (op->type().is_bool()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_bool);
} else if (op->type().is_int(8)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int8);
} else if (op->type().is_int(16)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int16);
} else if (op->type().is_int(32)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int32);
} else if (op->type().is_int(64)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int64);
} else if (op->type().is_uint(8)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint8);
} else if (op->type().is_uint(16)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint16);
} else if (op->type().is_uint(32)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint32);
} else if (op->type().is_uint(64)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint64);
} else if (op->type().is_float(32)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float);
} else if (op->type().is_float(64)) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_double);
} else if (op->type().is_bfloat16()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_bfloat16);
} else if (op->type().is_float16()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float16);
} else if (op->type().is_float8e4m3()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float8e4m3);
} else if (op->type() == type_of<void *>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_void_p);
} else if (op->type() == type_of<cinn_buffer_t *>() ||
op->type() == type_of<const cinn_buffer_t *>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_buffer_p);
} else {
LOG(ERROR) << "can't cast cinn_pod_value_t to " << op->type();
CINN_NOT_IMPLEMENTED
}
CHECK(callee);
CHECK(op->v().as_var()) << "argument to the intrinsic function "
"cinn_pod_value_to_x should be a Var";
value = GetVar(op->v().as_var()->name);
return Call(callee, std::vector<llvm::Value *>({value}), "pod_value_cast");
}
do {
if (value->getType() == target) break;
if (to.is_cpp_handle() || to.is_cpp_handle2()) {
value = BitCast(value, target, "cast_to_cpp_handle");
break;
}
if (to.is_bool()) {
if (from.is_float()) {
llvm::Constant *zero = llvm::ConstantFP::get(source, 0.);
value = FCmpONE(value, zero);
} else {
llvm::Constant *zero = llvm::ConstantInt::get(source, 0);
value = ICmpNE(value, zero);
}
break;
}
if (from.is_float() == false && to.is_float() == false) {
value = IntCast(value, target, from.is_int());
break;
}
if (from.is_float() && to.is_int()) {
value = FPToSI(value, target);
break;
}
if (from.is_float() && to.is_uint()) {
value = FPToUI(value, target);
if (to.bits() < 8) {
value = IntCast(value, target, false);
}
break;
}
if (from.is_int() && to.is_float()) {
value = SIToFP(value, target);
break;
}
if (from.is_uint() && to.is_float()) {
value = UIToFP(value, target);
break;
}
CHECK(from.is_float() && to.is_float());
value = FPCast(value, target);
} while (false);
return value;
}
llvm::Value *CodeGenLLVM::CreateSerialFor(const ir::For *op, int stride) {
SymbolTableGuard symbol_table_guard(*symbol_table_);
llvm::BasicBlock *preheader_bb = b_->GetInsertBlock();
llvm::BasicBlock *exit_bb = nullptr;
llvm::BasicBlock::iterator insert_point = b_->GetInsertPoint();
if (insert_point == preheader_bb->end()) {
CHECK(!preheader_bb->getTerminator());
exit_bb = llvm::BasicBlock::Create(b_->getContext(),
"loop_exit",
b_->GetInsertBlock()->getParent(),
nullptr);
} else {
CHECK(preheader_bb->getTerminator());
exit_bb = preheader_bb->splitBasicBlock(insert_point, "loop_exit");
preheader_bb->getTerminator()->eraseFromParent();
}
llvm::BasicBlock *header_bb =
llvm::BasicBlock::Create(b_->getContext(),
"loop_header",
b_->GetInsertBlock()->getParent(),
nullptr);
llvm::BasicBlock *body_bb =
llvm::BasicBlock::Create(b_->getContext(),
"loop_body",
b_->GetInsertBlock()->getParent(),
nullptr);
llvm::Function *func = preheader_bb->getParent();
b_->SetInsertPoint(&func->getEntryBlock(),
func->getEntryBlock().getFirstInsertionPt());
llvm::Value *old_var = GetVar(op->loop_var->name);
// loop iterator
llvm::AllocaInst *loop_var = Alloca(
b_->getIntNTy(op->min->type().bits()), nullptr, op->loop_var->name);
loop_var->setAlignment(llvm::Align(4));
SetVar(op->loop_var->name, loop_var);
b_->SetInsertPoint(preheader_bb);
llvm::Value *start_index = Visit(&op->min);
llvm::Value *end_index = Visit(&op->extent);
Store(start_index, loop_var);
CHECK(!preheader_bb->getTerminator());
Br(header_bb);
// loop_header
b_->SetInsertPoint(header_bb);
llvm::Value *indvar = Load(loop_var, "indvar");
llvm::Value *exit_cond = ICmpSGE(indvar, end_index);
CondBr(/*Cond=*/exit_cond,
/*True=*/exit_bb,
/*False=*/body_bb);
// loop_body
b_->SetInsertPoint(body_bb);
llvm::Value *step =
llvm::ConstantInt::get(b_->getIntNTy(op->min->type().bits()), stride);
Visit(&op->body);
llvm::Value *indvar_inc = Add(indvar,
step,
"indvar.inc",
/*HasNUW=*/true,
/*HasNSW=*/true);
Store(indvar_inc, loop_var);
llvm::BranchInst *back_branch = Br(header_bb);
// Add loop metadata
decltype(auto) ctx = b_->getContext();
std::vector<llvm::Metadata *> loop_metadata;
auto temp_node = llvm::MDNode::getTemporary(ctx, llvm::None);
loop_metadata.push_back(temp_node.get());
// TODO(fc500110): Loop vectorize
// auto *vectorization = op->metadata.vectorization ? b_->getTrue() :
// b_->getFalse(); loop_metadata.push_back(llvm::MDNode::get(
// ctx, {llvm::MDString::get(ctx, "llvm.loop.vectorize.enable"),
// llvm::ConstantAsMetadata::get(b_->getFalse())}));
// Loop unroll
std::string llvm_unroll_metadata{"llvm.loop.unroll."};
switch (op->metadata.unroll_mode) {
case ir::LLVMForLoopMeta::FullyUnroll:
llvm_unroll_metadata += "full";
break;
case ir::LLVMForLoopMeta::NoUnroll:
llvm_unroll_metadata += "disable";
break;
default:
llvm_unroll_metadata += "enable";
}
/*
loop_metadata.push_back(llvm::MDNode::get(ctx, {llvm::MDString::get(ctx,
llvm_unroll_metadata)})); auto loop_id = llvm::MDNode::get(ctx,
loop_metadata); loop_id->replaceOperandWith(0, loop_id);
back_branch->setMetadata(llvm::LLVMContext::MD_loop, loop_id);
*/
if (old_var) {
SetVar(op->loop_var->name, old_var);
} else {
symbol_table_->Erase(op->loop_var->name);
}
b_->SetInsertPoint(exit_bb);
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::For *op) {
return CreateSerialFor(op);
}
llvm::Value *CodeGenLLVM::Visit(const ir::PolyFor *op) {
CINN_NOT_IMPLEMENTED
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Select *op) {
return Select(
Visit(&op->condition), Visit(&op->true_value), Visit(&op->false_value));
}
llvm::Value *CodeGenLLVM::Visit(const ir::IfThenElse *op) {
SymbolTableGuard symbol_table_guard(*symbol_table_);
bool emit_else = op->false_case.defined();
auto &ll_ctx = b_->getContext();
auto *ll_function = b_->GetInsertBlock()->getParent();
llvm::Value *cond = Visit(&op->condition);
llvm::BasicBlock *then_block =
llvm::BasicBlock::Create(ll_ctx, "if-then", ll_function);
llvm::BasicBlock *end_block =
llvm::BasicBlock::Create(ll_ctx, "if-end", ll_function);
if (op->false_case.defined()) {
llvm::BasicBlock *else_block =
llvm::BasicBlock::Create(ll_ctx, "if-else", ll_function);
CondBr(cond, then_block, else_block);
// true case
b_->SetInsertPoint(then_block);
Visit(&op->true_case);
Br(end_block);
// false case
b_->SetInsertPoint(else_block);
Visit(&op->false_case);
Br(end_block);
} else {
CondBr(cond, then_block, end_block);
b_->SetInsertPoint(then_block);
Visit(&op->true_case);
Br(end_block);
}
b_->SetInsertPoint(end_block);
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Block *op) {
// Create a new scope holding the temporary variables.
SymbolTableGuard symbol_table_guard(*symbol_table_);
llvm::Value *ret = nullptr;
llvm::BasicBlock *block = llvm::BasicBlock::Create(
b_->getContext(), "block", b_->GetInsertBlock()->getParent(), nullptr);
Br(block);
b_->SetInsertPoint(block);
for (const auto &expr : op->stmts) {
ret = Visit(&expr);
}
return ret;
}
llvm::Value *CodeGenLLVM::Visit(const ir::PrimitiveNode *) {
CINN_NOT_IMPLEMENTED return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::_BufferRange_ *) {
CINN_NOT_IMPLEMENTED return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::ScheduleBlock *) {
CINN_NOT_IMPLEMENTED return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::ScheduleBlockRealize *) {
CINN_NOT_IMPLEMENTED return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::_Dim_ *) {
CINN_NOT_IMPLEMENTED return nullptr;
}
llvm::Function *CallHostFallBack(const llvm::Module *m, const ir::Call *op) {
std::string fallback_func_name =
"cinn_host_" + op->name + "_" + common::Type2Str(op->type());
VLOG(6) << "Warn: host side has no func named '" << op->name
<< "', trying a fallback version '" << fallback_func_name << "'";
return m->getFunction(fallback_func_name);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Call *op) {
if (op->name == runtime::intrinsic::debug_log_repr) {
return EmitCall_debug_info(op);
} else if (op->is_extern_call()) {
auto emitter_id = ExternFuncID{backend_llvm_host, op->name.c_str()};
const auto &fn_name =
ExternFunctionEmitterRegistry::Global().Lookup(emitter_id);
if (!fn_name.empty()) {
ExternFunctionLLVMEmitter emitter(fn_name);
emitter.BindCodeGen(this);
emitter.Emit(op);
return extern_func_emit_res_;
}
}
llvm::Function *callee = m_->getFunction(op->name);
if (!callee) {
callee = CallHostFallBack(m_, op);
}
CHECK(callee) << "Unknown function referenced. [" << op->name << "]";
std::vector<llvm::Value *> args;
for (const auto &e : op->read_args) {
auto *arg = Visit(&e);
CHECK(arg) << "argument " << e << " is null";
args.push_back(arg);
}
for (const auto &e : op->write_args) {
auto *arg = Visit(&e);
CHECK(arg) << "argument " << e << " is null";
args.push_back(arg);
}
if (op->is_cinn_call()) {
auto arg = ir::intrinsics::GetAddr::Make(op->read_args[0]);
args[0] = Visit(&arg);
args[0] = BitCast(args[0], ll_void_p_ty(), "cast_to_void_p");
}
auto params = AdaptABIArguments(b_, callee, std::move(args));
llvm::Value *ret = Call(callee, params);
if (FuncHasStructSRet(callee)) {
// void return type and the first param is a pointer
// will be considered as sret callee
auto *sret_ptr = params[0];
auto *sret_ty = sret_ptr->getType()->getPointerElementType();
ret = b_->CreateLoad(sret_ty, params.front());
}
return ret;
}
llvm::Value *CodeGenLLVM::Visit(const ir::_Module_ *op) {
{ ir::ir_utils::IrVerify(op); }
for (auto &fn : op->functions) {
VLOG(3) << "JIT Linking function [" << fn.As<ir::_LoweredFunc_>()->name
<< "]";
auto fnll = Visit(fn.As<ir::_LoweredFunc_>());
VLOG(5) << "fn llvm:\n" << DumpToString(*fnll);
}
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::_Var_ *op) {
llvm::Value *value = GetVar(op->name, /* lazy= */ false);
// When visiting a Var that is allocated on the stack, we are actually
// reading its value instead of its address.
if (llvm::AllocaInst::classof(value)) {
return Load(value, op->name + "_load");
}
return value;
}
void CodeGenLLVM::Scalarize(
const Expr &e, std::function<void(int i, llvm::Value *v)> flambda) {
if (const ir::Ramp *ramp = e.As<ir::Ramp>()) {
for (int i = 0; i < ramp->type().lanes(); ++i) {
Expr offset = ramp->base + (ramp->stride * i);
VLOG(3) << "offset: " << offset;
flambda(i, Visit(&offset));
}
} else {
llvm::Value *value = Visit(&e);
for (int i = 0; i < e->type().lanes(); ++i) {
flambda(i, b_->CreateExtractElement(value, i));
}
}
}
llvm::Value *CodeGenLLVM::Visit(const ir::Load *op) {
llvm::Value *array{nullptr};
bool is_alias{false};
if (auto *tensor_op = op->tensor.As<ir::_Tensor_>()) {
array = GetVar(tensor_op->name);
} else if (auto *var_op = op->tensor.As<ir::_Var_>()) {
array = GetVar(var_op->name);
is_alias = alias_vars_.count(const_cast<ir::_Var_ *>(var_op));
} else {
array = Visit(&op->tensor);
}
CHECK(array) << "fail to Visit Load node: "
<< Expr(const_cast<ir::Load *>(op));
ir::Expr index = op->index();
if (index.type().lanes() <= 1) {
std::vector<llvm::Value *> indices;
indices.push_back(Visit(&index));
// auto load_inst = Load(InBoundsGEP(array, std::move(indices)));
auto *load_inst =
AlignedLoad(InBoundsGEP(array, std::move(indices)), llvm::MaybeAlign());
/*
if (is_alias) {
llvm::MDNode *meta = md_builder_->createTBAANode("cinn-alias",
md_tbaa_root_); load_inst->setMetadata("tbaa",
md_builder_->createTBAAStructTagNode(meta, meta, 0));
}
*/
if (auto *load_tensor = op->tensor.as_tensor()) {
AddTbaaMetadata(load_inst, load_tensor->name, op->index());
}
{
int alignment = op->type().bits();
alignment = 8;
PADDLE_ENFORCE_GT(alignment,
0,
::common::errors::InvalidArgument(
"alignment should be greater than 0"));
load_inst->setAlignment(llvm::Align(std::min(alignment, 8)));
}
// TODO(fc500110): tbaa AliasAnalysis
// auto md_tbaa_root = md_builder_->createTBAARoot("cinn-tbaa");
// auto md_tbaa_alias_set = md_builder_->createTBAANode("cinn-alias",
// md_tbaa_root); llvm::MDNode *meta = md_tbaa_alias_set;
// load_inst->setMetadata("tbaa", md_builder_->createTBAAStructTagNode(meta,
// meta, 0));
return load_inst;
} else { // vector load
Expr dense_strided_ramp = detail::StridedRampBase(op->index(), 1);
llvm::Value *buffer = Visit(&op->tensor);
if (dense_strided_ramp.defined()) {
CHECK(op->type().is_vector());
return DenseVectorLoad(op);
}
// scalarize load
Type type = op->type();
int alignment = type.bits() / 8;
llvm::Value *ret =
llvm::UndefValue::get(CinnTypeToLLVMType(type, m_, true));
auto flambda = [&](int i, llvm::Value *index) {
auto *ptr = CreateBufferPtr(type.ElementOf(), buffer, index);
llvm::LoadInst *load_inst =
b_->CreateAlignedLoad(ptr, llvm::Align(alignment), "load_vec");
ret = b_->CreateInsertElement(ret, load_inst, ll_const_int32(i));
if (auto *load_tensor = op->tensor.as_tensor()) {
AddTbaaMetadata(load_inst, load_tensor->name, op->index());
}
};
Scalarize(op->index(), flambda);
return ret;
}
}
llvm::Value *CodeGenLLVM::Visit(const ir::Store *op) {
llvm::Value *array{nullptr};
bool is_alias{false};
if (auto *tensor_op = op->tensor.As<ir::_Tensor_>()) {
array = GetVar(tensor_op->name);
} else if (auto *var_op = op->tensor.As<ir::_Var_>()) {
array = GetVar(var_op->name);
is_alias = alias_vars_.count(const_cast<ir::_Var_ *>(var_op));
}
CHECK(array) << "array is null";
ir::Expr index = op->index();
if (op->type().is_scalar()) {
std::vector<llvm::Value *> indices;
indices.push_back(Visit(&index));
// auto *store_inst = Store(Visit(&op->value), InBoundsGEP(array,
// std::move(indices)));
auto *store_inst = AlignedStore(Visit(&op->value),
InBoundsGEP(array, std::move(indices)),
llvm::MaybeAlign());
/*
if (is_alias) {
llvm::MDNode *meta = md_builder_->createTBAANode("cinn-alias",
md_tbaa_root_); store_inst->setMetadata("tbaa",
md_builder_->createTBAAStructTagNode(meta, meta, 0));
}
*/
{
int alignment = op->type().bits();
alignment = 8;
PADDLE_ENFORCE_GT(alignment,
0,
::common::errors::InvalidArgument(
"alignment should be greater than 0"));
store_inst->setAlignment(llvm::Align(std::min(alignment, 8)));
}
// TODO(fc500110): tbaa AliasAnalysis
// auto md_tbaa_root = md_builder_->createTBAARoot("cinn-tbaa");
// auto md_tbaa_alias_set = md_builder_->createTBAANode("cinn-alias",
// md_tbaa_root); llvm::MDNode *meta = md_tbaa_alias_set;
// store_inst->setMetadata("tbaa",
// md_builder_->createTBAAStructTagNode(meta, meta, 0));
AddTbaaMetadata(store_inst, op->tensor.as_tensor()->name, op->index());
return store_inst;
} else { // vector store
Expr dense_strided_ramp = detail::StridedRampBase(op->index(), 1);
auto ramp_expr = op->index();
auto *ramp = index.As<ir::Ramp>();
auto *buffer = Visit(&op->tensor);
auto *value = Visit(&op->value);
if (dense_strided_ramp.defined()) { // stride 1
int total_lanes = op->type().lanes();
int step = naive_vec_alignment_ / op->type().ElementOf().bits();
// fit the total_lanes in native_lanes(split into multiple native steps)
for (int offset = 0; offset < total_lanes; offset += total_lanes) {
int lanes = total_lanes;
Expr base = optim::ArithSimplify(ramp->base + offset);
auto *ptr =
CreateBufferPtr(op->type().ElementOf(), buffer, Visit(&base));
auto *vtype = llvm::VectorType::get(
CinnTypeToLLVMType(op->type().ElementOf(), m_, true),
GetFixedElementCount(lanes))
->getPointerTo();
int alignment = std::max(op->type().ElementOf().bits() / 8, 1);
llvm::StoreInst *inst =
b_->CreateAlignedStore(CreateVecSlice(value, offset, lanes),
b_->CreatePointerCast(ptr, vtype),
llvm::Align(alignment));
AddTbaaMetadata(inst, op->tensor.as_tensor()->name, base);
return inst;
}
}
// scalarize store
Type type = op->type();
int alignment = type.bits() / 8;
llvm::Value *ret =
llvm::UndefValue::get(CinnTypeToLLVMType(type, m_, true));
auto flambda = [&](int i, llvm::Value *index) {
auto *ptr = CreateBufferPtr(type.ElementOf(), buffer, index);
llvm::StoreInst *store_inst =
b_->CreateAlignedStore(b_->CreateExtractElement(value, i),
ptr,
llvm::Align(alignment),
"store_vec");
ret = b_->CreateInsertElement(ret, store_inst, ll_const_int32(i));
if (auto *store_tensor = op->tensor.as_tensor()) {
AddTbaaMetadata(store_inst, store_tensor->name, op->index());
}
};
Scalarize(op->index(), flambda);
return ret;
}
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Alloc *op) {
auto *buffer_op = op->destination.As<ir::_Buffer_>();
auto *buffer = GetVar(buffer_op->name);
CHECK(buffer);
return buffer;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Free *op) {
auto *buffer_op = op->destination.As<ir::_Buffer_>();
CHECK(symbol_table_->Lookup(buffer_op->name));
symbol_table_->Erase(buffer_op->name);
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::_Buffer_ *op) {
return GetVar(op->name);
}
llvm::Value *CodeGenLLVM::Visit(const ir::_Tensor_ *op) {
return GetVar(op->name);
}
template <typename T,
std::enable_if_t<std::is_same<const ir::Expr &, T>::value, int> = 0>
void appendBody(std::vector<Expr> &new_body, T &&v) { // NOLINT
new_body.push_back(v);
}
template <typename T,
std::enable_if_t<!std::is_same<const ir::Expr &, T>::value, int> = 1>
void appendBody(std::vector<Expr> &new_body, T &&v) { // NOLINT
new_body.insert(new_body.end(), v.begin(), v.end());
}
llvm::Value *CodeGenLLVM::Visit(const ir::_LoweredFunc_ *op) {
auto init_function_state = [this]() { alias_vars_.clear(); };
init_function_state();
PADDLE_ENFORCE_EQ(
op->alloc_output_buffer_exprs.size(),
op->dealloc_output_buffer_exprs.size(),
::common::errors::InvalidArgument(
"the count of allocation and deallocation expressions is not "
"match"));
std::vector<Expr> new_body;
auto create_temp_buffers = op->PrepareCreateTempBufferExprs();
auto alloc_temp_buffers = op->PrepareAllocTempBufferExprs();
auto dealloc_temp_buffers = op->PrepareDeallocTempBufferExprs();
appendBody(new_body, op->argument_prepare_exprs);
appendBody(new_body, create_temp_buffers);
appendBody(new_body, alloc_temp_buffers);
appendBody(new_body, op->alloc_output_buffer_exprs);
appendBody(new_body, op->buffer_data_cast_exprs);
appendBody(new_body, op->body);
appendBody(new_body, dealloc_temp_buffers);
appendBody(new_body, op->dealloc_output_buffer_exprs);
ir::Expr function_body = ir::Block::Make(new_body);
// Emit Function
std::vector<llvm::Type *> arg_types = {b_->getInt8PtrTy(), b_->getInt32Ty()};
llvm::FunctionType *function_type = llvm::FunctionType::get(
/*Result=*/b_->getVoidTy(),
/*Params=*/std::move(arg_types),
/*isVarArg=*/false);
CHECK(m_->getFunction(op->name) == nullptr)
<< "function[" << op->name << "] exists";
f_ = llvm::Function::Create(
/*FunctionType=*/function_type,
/*LinkageTypes=*/llvm::Function::ExternalLinkage,
/*Name=*/op->name,
/*Module=*/m_);
f_->setCallingConv(llvm::CallingConv::C);
f_->setHasUWTable(); // GDB
std::vector<llvm::Value *> args;
args.reserve(f_->arg_size());
std::transform(
f_->arg_begin(), f_->arg_end(), std::back_inserter(args), [](auto &arg) {
return std::addressof(arg);
});
llvm::BasicBlock *entry = llvm::BasicBlock::Create(
/*Context=*/b_->getContext(),
/*Name=*/"entry",
/*Parent=*/f_,
/*InsertBefore=*/nullptr);
SetVar("_args", args[0]);
b_->SetInsertPoint(entry);
Visit(&function_body);
symbol_table_->Erase("_args");
RetVoid();
return f_;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Let *op) {
CHECK(op->type().valid());
auto name = op->symbol.As<ir::_Var_>()->name;
if (op->symbol.As<ir::_Var_>()->type().is_cpp_handle()) {
alias_vars_.insert(const_cast<ir::_Var_ *>(op->symbol.As<ir::_Var_>()));
}
if (op->body.defined()) {
SetVar(name, Visit(&op->body));
} else {
llvm::AllocaInst *inst =
Alloca(CinnTypeToLLVMType(op->type(), m_), nullptr, name);
auto get_align = [](int n) {
int i{0}, r{1};
while (n > r) {
r *= 2;
++i;
}
return r / 8;
};
int align_bits = std::max<int>(op->type().bits(), 8);
int align = get_align(align_bits);
inst->setAlignment(llvm::Align(align));
SetVar(name, inst);
}
return GetVar(name);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Reduce *op) {
__IR_EMITTER_NOT_IMPLEMENTED(op);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Ramp *op) {
__IR_EMITTER_NOT_IMPLEMENTED(op);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Broadcast *op) {
const llvm::ElementCount elem_count = GetFixedElementCount(op->lanes);
llvm::Value *value = Visit(&op->value);
llvm::Constant *undef = llvm::UndefValue::get(
llvm::VectorType::get(value->getType(), elem_count));
llvm::Constant *zero = llvm::ConstantInt::get(ll_int32_ty(), 0);
value = b_->CreateInsertElement(undef, value, zero, "broadcast");
llvm::Constant *zeros = llvm::ConstantVector::getSplat(elem_count, zero);
return b_->CreateShuffleVector(value, undef, zeros, "broadcast_shuffle");
}
llvm::Value *CodeGenLLVM::Visit(const ir::FracOp *op) {
__IR_EMITTER_NOT_IMPLEMENTED(op);
}
llvm::Value *CodeGenLLVM::Visit(const ir::Product *op) {
auto size = op->operands().size();
if (size == 0) return nullptr;
llvm::Value *ret = Visit(&op->operand(0));
for (int i = 1; i < size; i++) {
llvm::Value *v = Visit(&op->operand(i));
if (is_integral_type(op->type())) {
ret = Mul(ret, v);
} else {
ret = FMul(ret, v);
}
}
return ret;
}
llvm::Value *CodeGenLLVM::Visit(const ir::Sum *op) {
auto size = op->operands().size();
if (size == 0) return nullptr;
llvm::Value *ret = Visit(&op->operand(0));
for (int i = 1; i < size; i++) {
llvm::Value *v = Visit(&op->operand(i));
if (is_integral_type(op->type())) {
ret = Add(ret, v);
} else { // float
ret = FAdd(ret, v);
}
}
return ret;
}
#undef __IR_EMITTER_CINN_NOT_IMPLEMENTED
void CodeGenLLVM::Compile(const ir::Module &module) { Visit(module.self()); }
llvm::Value *CodeGenLLVM::EmitCall_buffer_malloc(const ir::Call *op) {
return nullptr;
}
llvm::Value *CodeGenLLVM::EmitCall_get_address(const ir::Call *op) {
if (auto *read_var = op->read_args.front().as_var()) {
return GetVar(read_var->name);
}
if (auto *read_buf = op->read_args.front().as_buffer()) {
return GetVar(read_buf->name);
}
return nullptr;
}
llvm::Value *CodeGenLLVM::EmitCall_debug_info(const ir::Call *op) {
auto callee = m_->getFunction(runtime::intrinsic::debug_log_repr);
PADDLE_ENFORCE_GE(op->read_args.size(),
1UL,
::common::errors::InvalidArgument(
"The arguments of debug_log_repr should be greater "
"than 1"));
std::vector<llvm::Value *> args;
for (auto &arg : op->read_args) {
args.push_back(Visit(&arg));
}
return Call(callee, args, "call debug_info");
}
llvm::Value *CodeGenLLVM::GetVar(const std::string &name, bool lazy) {
auto symbol = symbol_table_->Lookup(name);
if (!lazy) {
CHECK(symbol) << "No var [" << name << "] found";
}
return symbol;
}
llvm::Value *CodeGenLLVM::SetVar(const std::string &name, llvm::Value *val) {
symbol_table_->Insert(name, val);
CHECK(GetVar(name));
return val;
}
llvm::FunctionType *CodeGenLLVM::GenFunctionTypeFromCinnFunction(
const ir::_LoweredFunc_ *func, bool with_buffer_type) {
auto func_ret_type = CinnTypeToLLVMType(Void(), m_);
std::vector<llvm::Type *> arg_types;
for (auto &arg : func->args) {
if (arg.is_buffer() && arg.is_var()) {
alias_vars_.insert(arg.var_arg().get());
}
if (arg.is_var()) {
arg_types.push_back(CinnTypeToLLVMType(arg.var_arg()->type(), m_));
} else if (arg.is_buffer()) {
if (with_buffer_type) {
arg_types.push_back(ll_cinn_buffer_p_ty());
} else {
arg_types.push_back(CinnTypeToLLVMType(arg.buffer_arg()->type(), m_));
}
}
}
return llvm::FunctionType::get(func_ret_type, arg_types, false);
}
llvm::Value *CodeGenLLVM::DenseVectorLoad(const ir::Load *op) {
auto index = op->index();
auto *ramp = index.As<ir::Ramp>();
CHECK(ramp);
int load_lanes = op->type().lanes();
int native_lanes = naive_vec_alignment_ / op->type().bits();
std::vector<llvm::Value *> slices;
llvm::Value *buffer = Visit(&op->tensor);
buffer->setName("buffer");
for (int i = 0; i < load_lanes; i += load_lanes) {
int slice_lanes = load_lanes;
auto slice_base = optim::ArithSimplify(ramp->base + i);
const llvm::ElementCount elem_count = GetFixedElementCount(slice_lanes);
llvm::Type *slice_type = llvm::VectorType::get(
CinnTypeToLLVMType(op->type().ElementOf(), m_, true), elem_count);
llvm::Value *elt_ptr =
CreateBufferPtr(op->type().ElementOf(), buffer, Visit(&slice_base));
llvm::Value *vec_ptr = b_->CreatePointerCast(
elt_ptr, slice_type->getPointerTo(), "get_vec_ptr");
int alignment = std::max(op->type().ElementOf().bits() / 8, 1);
llvm::Instruction *load_inst =
b_->CreateAlignedLoad(vec_ptr, llvm::Align(alignment), "load_vec");
AddTbaaMetadata(load_inst, op->tensor.as_tensor()->name, op->index());
slices.push_back(load_inst);
}
PADDLE_ENFORCE_EQ(
slices.size(),
1UL,
::common::errors::InvalidArgument("slices size should be 1."));
return slices[0];
}
llvm::Value *CodeGenLLVM::CreateBufferVecPtr(Type t,
llvm::Value *buffer,
llvm::Value *index) {
PADDLE_ENFORCE_GT(
t.lanes(),
1,
::common::errors::InvalidArgument("type lanes should be greater "
"than 1, but received %d",
t.lanes()));
llvm::PointerType *btype =
llvm::dyn_cast<llvm::PointerType>(buffer->getType());
CHECK(btype);
llvm::PointerType *ptype =
CinnTypeToLLVMType(t, m_)->getPointerTo(btype->getAddressSpace());
if (btype != ptype) {
buffer = b_->CreatePointerCast(buffer, ptype);
}
return b_->CreateInBoundsGEP(buffer, index);
}
llvm::Value *CodeGenLLVM::CreateBufferPtr(Type t,
llvm::Value *buffer,
llvm::Value *index) {
PADDLE_ENFORCE_EQ(
t.lanes(),
1,
::common::errors::InvalidArgument("type lanes should be 1, but "
"received %d",
t.lanes()));
auto *btype = llvm::dyn_cast<llvm::PointerType>(buffer->getType());
CHECK(btype);
auto *ptype =
CinnTypeToLLVMType(t, m_)->getPointerTo(btype->getAddressSpace());
CHECK(ptype);
if (btype != ptype) {
buffer = b_->CreatePointerCast(buffer, ptype, "pointer_cast");
}
return b_->CreateInBoundsGEP(buffer, index, "buffer_ptr");
}
llvm::Value *CodeGenLLVM::CreateVecSlice(llvm::Value *vec,
int begin,
int lanes) {
int total_lanes = GetFixedVectorNumElements(vec->getType());
PADDLE_ENFORCE_LE(begin + lanes,
total_lanes,
::common::errors::InvalidArgument(
"begin + lanes should be less than total_lanes"));
if (lanes == total_lanes && begin == 0) return vec; // full slice
std::vector<llvm::Constant *> indices;
for (int i = 0; i < lanes; ++i) {
indices.push_back(ll_const_int32(begin + i));
}
llvm::Constant *undef = llvm::UndefValue::get(vec->getType());
return b_->CreateShuffleVector(
vec, undef, llvm::ConstantVector::get(indices));
}
int GetNaiveVecAlignmentImpl(common::UnknownArch, const Target &target) {
PADDLE_THROW(::common::errors::InvalidArgument("unknown Arch found"));
}
int GetNaiveVecAlignmentImpl(common::X86Arch, const Target &target) {
if (target.bits == Target::Bit::k32) {
return 256;
} else if (target.bits == Target::Bit::k64) {
return 512;
}
PADDLE_THROW(::common::errors::InvalidArgument("get unknown bits"));
}
int GetNaiveVecAlignmentImpl(common::ARMArch, const Target &target) {
return 128;
}
int GetNaiveVecAlignmentImpl(common::NVGPUArch, const Target &target) {
return 128;
}
int GetNaiveVecAlignmentImpl(common::HygonDCUArchHIP, const Target &target) {
return 128;
}
int GetNaiveVecAlignmentImpl(common::HygonDCUArchSYCL, const Target &target) {
return 128;
}
int GetNaiveVecAlignmentImpl(common::CustomDeviceArch arch,
const Target &target) {
#ifdef CINN_WITH_CUSTOM_DEVICE
auto place = phi::CustomPlace(arch.device_type, arch.device_id);
return phi::DeviceManager::GetPreferredVectorWidth(place);
#else
return 128;
#endif
}
int GetNaiveVecAlignment(const Target &target) {
return std::visit(
[&](const auto &impl) { return GetNaiveVecAlignmentImpl(impl, target); },
target.arch.variant());
}
void CodeGenLLVM::InitTarget(const Target &target) {
// Keep this aligned with cmake/cinn/llvm.cmake: CINN JIT links only the
// native target. InitializeAll* references every configured target in LLVM's
// TargetSelect.h and needs the matching all-target libraries.
// https://github.com/llvm/llvm-project/blob/llvmorg-13.0.1/llvm/include/llvm/Support/TargetSelect.h
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
naive_vec_alignment_ = GetNaiveVecAlignment(target);
}
void CodeGenLLVM::AddTbaaMetadata(llvm::Instruction *inst,
std::string_view buffer,
Expr index) {
// If the index is constant, generate some TBAA info that helps LLVM
// understand our loads/stores aren't aliased.
bool constant_index = false;
int base = 0;
int width = 1;
if (index.defined()) {
if (const ir::Ramp *ramp = index.As<ir::Ramp>()) {
auto *pstride_int = ramp->stride.As<ir::IntImm>();
auto *pbase_int = ramp->base.As<ir::IntImm>();
if (pstride_int && pbase_int) {
int stride = pstride_int->value;
base = pbase_int->value;
PADDLE_ENFORCE_GE(
base,
0,
::common::errors::InvalidArgument("base should be greater than 0"));
width = NextPowerOfTwo(ramp->lanes * stride);
while (base % width) {
base -= base % width;
width *= 2;
}
constant_index = true;
}
} else {
auto *pbase_int = index.As<ir::IntImm>();
if (pbase_int) {
int pbase = pbase_int->value;
base = pbase;
constant_index = true;
}
}
}
llvm::MDBuilder builder(b_->getContext());
// Add type-based-alias-analysis metadata to the pointer, so that loads and
// stores to different buffers can get reordered.
llvm::MDNode *tbaa = builder.createTBAARoot("cinn buffer");
tbaa = builder.createTBAAScalarTypeNode(std::string(buffer), tbaa);
// Add metadata for constant indices to allow loads and stores to the same
// buffer to get reordered.
if (constant_index) {
for (int w = 1024; w >= width; w /= 2) {
int b = (base / w) * w;
tbaa = builder.createTBAAScalarTypeNode(
utils::StringFormat("%s.width%d.base%d", buffer.data(), w, b), tbaa);
}
}
tbaa = builder.createTBAAStructTagNode(tbaa, tbaa, 0);
inst->setMetadata("tbaa", tbaa);
}
llvm::Value *CodeGenLLVM::Visit(const ir::IntrinsicOp *op) {
switch (op->getKind()) {
#define __(op__) \
case ir::IntrinsicKind::k##op__: \
return Visit(llvm::dyn_cast<ir::intrinsics::op__>(op));
INTRINSIC_KIND_FOR_EACH(__)
#undef __
}
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::BufferGetDataHandle *op) {
std::vector<llvm::Value *> args({Visit(&op->buffer)});
auto *callee = m_->getFunction("cinn_buffer_get_data_handle");
return Call(callee, std::move(args));
}
llvm::Value *CodeGenLLVM::Visit(
const ir::intrinsics::BufferGetDataConstHandle *op) {
std::vector<llvm::Value *> args({Visit(&op->buffer)});
auto *callee = m_->getFunction("cinn_buffer_get_data_const_handle");
return Call(callee, std::move(args));
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::BufferCreate *op) {
auto *callee = m_->getFunction(runtime::intrinsic::buffer_create_default);
auto buffer_node = op->buffer.as_buffer();
CHECK(buffer_node);
std::vector<llvm::Value *> args(
{ll_const_int32(buffer_node->target.runtime_arch())});
int64_t memory_size = (buffer_node->dtype.ElementOf().bits() + 7) / 8;
// Calculate buffer size and determine if it contains a symbolic constant
Expr buffer_size(static_cast<int64_t>(1));
buffer_size = buffer_size * ir::Expr(memory_size);
for (int i = 0; i < buffer_node->shape.size(); i++) {
buffer_size = buffer_size * buffer_node->shape[i];
}
auto promote_args = std::move(ir::TryElevateInt32ToInt64({buffer_size}));
args.push_back(Visit(&promote_args.at(0)));
args.push_back(ll_const_int32(32));
return Call(callee, args);
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::GetAddr *op) {
if (auto *n = op->data.as_var()) {
return GetVar(n->name);
} else if (auto *n = op->data.as_buffer()) {
return GetVar(n->name);
}
if (auto *n =
op->data
.As<ir::Load>()) { // get the address to an element in a buffer
auto *e = Visit(&op->data);
if (auto *e_load = llvm::dyn_cast<llvm::LoadInst>(e)) {
return e_load->getPointerOperand();
}
return e;
}
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::ArgsConstruct *op) {
llvm::SmallVector<llvm::Value *, 7> args;
Expr var(op->var);
var->set_type(type_of<cinn_pod_value_t>());
var = ir::intrinsics::GetAddr::Make(var);
llvm::Value *ll_var = Visit(&var);
var = ir::Cast::Make(type_of<cinn_pod_value_t *>(), var);
Expr num_args(static_cast<int>(op->args.size()));
args.push_back(
BitCast(ll_var, ll_cinn_pod_p_ty(), "cast_to_pod_value_t_ptr"));
args.push_back(Visit(&num_args));
for (auto &arg : op->args) {
args.push_back(Visit(&arg));
}
auto *callee = m_->getFunction(runtime::intrinsic::args_construct_repr);
return Call(callee, std::move(args));
}
llvm::Function *CodeGenLLVM::GetIntrinsicDecl(
llvm::Intrinsic::ID id,
llvm::Type *ret_type,
llvm::ArrayRef<llvm::Type *> arg_types) {
llvm::Module *module = m_;
if (!llvm::Intrinsic::isOverloaded(id)) {
return llvm::Intrinsic::getDeclaration(module, id, {});
}
llvm::SmallVector<llvm::Intrinsic::IITDescriptor, 4> infos;
llvm::Intrinsic::getIntrinsicInfoTableEntries(id, infos);
llvm::SmallVector<llvm::Type *, 4> overload_types;
auto try_match = [&](llvm::FunctionType *f_ty, bool var_arg) {
overload_types.clear();
llvm::ArrayRef<llvm::Intrinsic::IITDescriptor> ref(infos);
auto match =
llvm::Intrinsic::matchIntrinsicSignature(f_ty, ref, overload_types);
if (match == llvm::Intrinsic::MatchIntrinsicTypes_Match) {
if (llvm::Intrinsic::matchIntrinsicVarArg(var_arg, ref)) {
return llvm::Intrinsic::MatchIntrinsicTypes_NoMatchArg;
}
}
return match;
};
auto *fn_ty = llvm::FunctionType::get(ret_type, arg_types, false);
switch (try_match(fn_ty, false)) {
case llvm::Intrinsic::MatchIntrinsicTypes_Match:
return llvm::Intrinsic::getDeclaration(module, id, overload_types);
case llvm::Intrinsic::MatchIntrinsicTypes_NoMatchRet:
return nullptr;
case llvm::Intrinsic::MatchIntrinsicTypes_NoMatchArg:
break;
}
// try matching the var arg signature.
llvm::SmallVector<llvm::Type *, 4> var_types;
for (int i = 0; i <= arg_types.size(); ++i) {
if (i > 0) {
var_types.push_back(arg_types[i - 1]);
}
auto *ft = llvm::FunctionType::get(ret_type, var_types, true);
if (try_match(ft, true) == llvm::Intrinsic::MatchIntrinsicTypes_Match) {
return llvm::Intrinsic::getDeclaration(module, id, overload_types);
}
}
return nullptr;
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::BuiltinIntrin *op) {
std::string func_name = op->name;
if (op->id == -1) {
if (func_name == "bitwise_and") {
PADDLE_ENFORCE_GE(op->args.size(),
2U,
::common::errors::InvalidArgument(
"bitwise_and should have at least 2 arguments"));
return b_->CreateAnd(Visit(&op->args[0]), Visit(&op->args[1]));
} else if (func_name == "bitwise_or") {
PADDLE_ENFORCE_GE(op->args.size(),
2U,
::common::errors::InvalidArgument(
"bitwise_or should have at least 2 arguments"));
return b_->CreateOr(Visit(&op->args[0]), Visit(&op->args[1]));
} else if (func_name == "bitwise_xor") {
PADDLE_ENFORCE_GE(op->args.size(),
2U,
::common::errors::InvalidArgument(
"bitwise_xor should have at least 2 arguments"));
return b_->CreateXor(Visit(&op->args[0]), Visit(&op->args[1]));
} else if (func_name == "bitwise_not") {
PADDLE_ENFORCE_GE(op->args.size(),
1U,
::common::errors::InvalidArgument(
"bitwise_not should have at least 1 argument"));
return b_->CreateNot(Visit(&op->args[0]));
} else if (func_name == "left_shift") {
PADDLE_ENFORCE_GE(op->args.size(),
2U,
::common::errors::InvalidArgument(
"left_shift should have at least 2 arguments"));
return b_->CreateShl(Visit(&op->args[0]), Visit(&op->args[1]));
} else if (func_name == "right_shift") {
PADDLE_ENFORCE_GE(op->args.size(),
2U,
::common::errors::InvalidArgument(
"right_shift should have at least 2 arguments"));
if (op->args[0]->type().is_int()) {
return b_->CreateAShr(Visit(&op->args[0]), Visit(&op->args[1]));
} else {
return b_->CreateLShr(Visit(&op->args[0]), Visit(&op->args[1]));
}
} else if (func_name == "isnan") {
PADDLE_ENFORCE_GE(op->args.size(),
1U,
::common::errors::InvalidArgument(
"isnan should have at least 1 argument"));
llvm::Value *v = Visit(&op->args[0]);
return b_->CreateFCmpUNO(v, v);
}
}
llvm::Intrinsic::ID id = op->id;
int64_t num_signature = op->arg_nums;
std::vector<llvm::Value *> arg_value;
std::vector<llvm::Type *> arg_type;
for (size_t i = 0; i < op->args.size(); ++i) {
arg_value.push_back(Visit(&op->args[i]));
if (i < static_cast<size_t>(num_signature)) {
arg_type.push_back(arg_value.back()->getType());
}
}
CHECK(!op->args.empty());
llvm::Type *return_type = CinnTypeToLLVMType(op->type(), m_, true);
llvm::Function *fn = GetIntrinsicDecl(id, return_type, arg_type);
CHECK(fn) << "Cannot find intrinsic declaration, possible type mismatch: "
<< llvm::Intrinsic::getName(id).str();
return b_->CreateCall(fn, arg_value);
}
llvm::Value *CodeGenLLVM::Visit(const ir::intrinsics::PodValueToX *op) {
auto to_type = op->GetOutputType(0);
llvm::Function *callee{};
if (to_type == type_of<float>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float);
} else if (to_type == type_of<double>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_double);
} else if (to_type == type_of<bfloat16>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_bfloat16);
} else if (to_type == type_of<float8e4m3>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float8e4m3);
} else if (to_type == type_of<float16>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_float16);
} else if (to_type == type_of<bool>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_bool);
} else if (to_type == type_of<int8_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int8);
} else if (to_type == type_of<int16_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int16);
} else if (to_type == type_of<int32_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int32);
} else if (to_type == type_of<int64_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_int64);
} else if (to_type == type_of<uint8_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint8);
} else if (to_type == type_of<uint16_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint16);
} else if (to_type == type_of<uint32_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint32);
} else if (to_type == type_of<uint64_t>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_uint64);
} else if (to_type == type_of<void *>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_void_p);
} else if (to_type == type_of<cinn_buffer_t *>()) {
callee = m_->getFunction(runtime::intrinsic::pod_value_to_buffer_p);
} else {
std::stringstream ss;
ss << "Not supported type: " << to_type;
PADDLE_THROW(::common::errors::InvalidArgument(ss.str()));
}
CHECK(callee);
auto *value = Visit(&op->pod_value_ptr);
CHECK(value);
return Call(callee, std::vector<llvm::Value *>({value}), "pod_value_cast");
}
void CodeGenLLVM::RegisterCustomizedPODStructType() {
// some of the POD struct type defined in cinn_runtime.h
// might be missing when loaded by LLVM, for unknown reasons
// To make sure they can be found, we explicitly register them here
#define REGISTER_STRUCT_TYPE(name, ...) \
llvm::StructType::create({__VA_ARGS__}, "struct." #name, /*isPacked=*/false);
#define REGISTER_ARGIDX_TYPE(dname, dtype) \
REGISTER_STRUCT_TYPE(argidx_##dname##_i32, dtype, ll_int32_ty()) \
REGISTER_STRUCT_TYPE(argidx_##dname##_i64, dtype, ll_int64_ty())
REGISTER_STRUCT_TYPE(welford_fp32, ll_fp32_ty(), ll_fp32_ty(), ll_fp32_ty())
REGISTER_STRUCT_TYPE(welford_fp64, ll_fp64_ty(), ll_fp64_ty(), ll_fp64_ty())
REGISTER_ARGIDX_TYPE(fp32, ll_fp32_ty())
REGISTER_ARGIDX_TYPE(fp64, ll_fp64_ty())
REGISTER_ARGIDX_TYPE(i16, ll_int16_ty())
REGISTER_ARGIDX_TYPE(i32, ll_int32_ty())
REGISTER_ARGIDX_TYPE(i64, ll_int64_ty())
REGISTER_ARGIDX_TYPE(u8, ll_uint8_ty())
#undef REGISTER_ARGIDX_TYPE
#undef REGISTER_STRUCT_TYPE
}
} // namespace backends
} // namespace cinn