/*
* Copyright © 2012 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see .
*
* Author: Benjamin Segovia
*/
/**
* \file lowering.cpp
* \author Benjamin Segovia
*/
#include "ir/context.hpp"
#include "ir/value.hpp"
#include "ir/liveness.hpp"
#include "sys/set.hpp"
namespace gbe {
namespace ir {
/*! Small helper class to lower return instructions */
class ContextReturn : public Context
{
public:
/*! Initialize a context dedicated to return instruction lowering */
ContextReturn(Unit &unit) : Context(unit) {
this->usedLabels = GBE_NEW_NO_ARG(vector);
}
/*! Lower the return instruction to gotos for the given function */
void lower(const std::string &functionName);
};
void ContextReturn::lower(const std::string &functionName) {
if ((this->fn = unit.getFunction(functionName)) == NULL)
return;
// Append a new block at the end of the function with a return instruction:
// the only one we are going to have
this->bb = &this->fn->getBottomBlock();
const LabelIndex index = this->label();
this->LABEL(index);
const BasicBlock *lastBlock = this->bb;
/* Append the STORE_PROFILING just before return. */
if (unit.getInProfilingMode() == true) {
this->STORE_PROFILING(this->getUnit().getProfilingInfo()->getBTI(),
this->getUnit().getProfilingInfo()->getProfilingType());
}
this->RET();
// Now traverse all instructions and replace all returns by GOTO index
fn->foreachInstruction([&](Instruction &insn) {
if (insn.getParent() == lastBlock) return; // This is the last block
if (insn.getOpcode() != OP_RET) return;
const Instruction bra = ir::BRA(index);
bra.replace(&insn);
});
}
void lowerReturn(Unit &unit, const std::string &functionName) {
ContextReturn ctx(unit);
ctx.lower(functionName);
}
/*! Characterizes how the argument is used (directly read, indirectly read,
* written)
*/
enum ArgUse {
ARG_DIRECT_READ = 0,
ARG_INDIRECT_READ = 1,
ARG_WRITTEN = 2
};
/*! Just to book keep the sequence of instructions that directly load an input
* argument
*/
struct LoadAddImm {
Instruction *load; //!< Load from the argument
Instruction *add; //!< Can be NULL if we only have load(arg)
Instruction *loadImm; //!< Can also be NULL
uint64_t offset; //!< Offset where to load in the structure
uint32_t argID; //!< Associated function argument
};
struct IndirectLoad {
Instruction *load; //!< Load from the argument
vector adds; //!< Can be NULL if we only have load(arg)
uint32_t argID; //!< Associated function argument
};
/*! List of direct loads */
typedef vector LoadAddImmSeq;
typedef vector IndirectLoadSeq;
/*! Helper class to lower function arguments if required */
class FunctionArgumentLowerer : public Context
{
public:
/*! Build the helper structure */
FunctionArgumentLowerer(Unit &unit);
/*! Free everything we needed */
virtual ~FunctionArgumentLowerer(void);
/*! Perform all function arguments substitution if needed */
void lower(const std::string &name);
/*! Lower the given function argument accesses */
ArgUse lower(uint32_t argID);
/*! Build the constant push for the function */
void buildConstantPush(void);
/* Lower indirect Read to indirct Mov */
void lowerIndirectRead(uint32_t argID);
/* Convert indirectLoad to indirect Mov */
void ReplaceIndirectLoad(void);
/*! Inspect the given function argument to see how it is used. If this is
* direct loads only, we also output the list of instructions used for each
* load
*/
ArgUse getArgUse(uint32_t argID);
/*! Recursively look if there is a store in the given use */
bool useStore(const ValueDef &def, set &visited);
/*! Look if the pointer use only load with immediate offsets */
bool matchLoadAddImm(uint32_t argID);
Liveness *liveness; //!< To compute the function graph
FunctionDAG *dag; //!< Contains complete dependency information
LoadAddImmSeq seq; //!< All the direct loads
IndirectLoadSeq indirectSeq; //!< All the indirect loads
};
INLINE uint64_t getOffsetFromImm(const Immediate &imm) {
switch (imm.getType()) {
// bit-cast these ones
case TYPE_DOUBLE:
case TYPE_FLOAT: NOT_SUPPORTED; return 0;
case TYPE_S64:
case TYPE_U64:
case TYPE_U32:
case TYPE_U16:
case TYPE_U8:
// sign extend these ones
case TYPE_S32:
case TYPE_S16:
case TYPE_S8: return imm.getIntegerValue();
case TYPE_BOOL:
case TYPE_HALF: NOT_SUPPORTED; return 0;
default:
GBE_ASSERT(0 && "Unsupported imm type.\n");
}
return 0;
}
bool matchLoad(Instruction *insn,
Instruction *add,
Instruction *loadImm,
uint64_t offset,
uint32_t argID,
LoadAddImm &loadAddImm)
{
const Opcode opcode = insn->getOpcode();
if (opcode == OP_LOAD) {
LoadInstruction *load = cast(insn);
if(!load)
return false;
if (load->getAddressSpace() != MEM_PRIVATE)
return false;
loadAddImm.load = insn;
loadAddImm.add = add;
loadAddImm.loadImm = loadImm;
loadAddImm.offset = offset;
loadAddImm.argID = argID;
return true;
} else
return false;
}
FunctionArgumentLowerer::FunctionArgumentLowerer(Unit &unit) :
Context(unit), liveness(NULL), dag(NULL) {}
FunctionArgumentLowerer::~FunctionArgumentLowerer(void) {
GBE_SAFE_DELETE(dag);
GBE_SAFE_DELETE(liveness);
}
void FunctionArgumentLowerer::lower(const std::string &functionName) {
if ((this->fn = unit.getFunction(functionName)) == NULL)
return;
GBE_SAFE_DELETE(dag);
GBE_SAFE_DELETE(liveness);
this->liveness = GBE_NEW(ir::Liveness, *fn);
this->dag = GBE_NEW(ir::FunctionDAG, *this->liveness);
// Process all structure arguments and find all the direct loads we can
// replace
const uint32_t argNum = fn->argNum();
vector indirctReadArgs;
for (uint32_t argID = 0; argID < argNum; ++argID) {
FunctionArgument &arg = fn->getArg(argID);
if (arg.type != FunctionArgument::STRUCTURE) continue;
if(this->lower(argID) == ARG_INDIRECT_READ)
indirctReadArgs.push_back(argID);
}
// Build the constant push description and remove the instruction that
// therefore become useless
this->buildConstantPush();
for (uint32_t i = 0; i < indirctReadArgs.size(); ++i){
lowerIndirectRead(indirctReadArgs[i]);
}
ReplaceIndirectLoad();
}
// Remove all the given instructions from the stream (if dead)
#define REMOVE_INSN(WHICH) \
for (const auto &loadAddImm : seq) { \
Instruction *WHICH = loadAddImm.WHICH; \
if (WHICH == NULL) continue; \
const UseSet &useSet = dag->getUse(WHICH, 0); \
bool isDead = true; \
for (auto use : useSet) { \
if (dead.contains(use->getInstruction()) == false) { \
isDead = false; \
break; \
} \
} \
if (isDead && !dead.contains(WHICH)) { \
dead.insert(WHICH); \
WHICH->remove(); \
} \
}
void FunctionArgumentLowerer::buildConstantPush(void)
{
if (seq.size() == 0)
return;
// Track instructions we remove to recursively kill them properly
set dead;
// The argument location we already pushed (since the same argument location
// can be used several times)
set inserted;
for (const auto &loadAddImm : seq) {
LoadInstruction *load = cast(loadAddImm.load);
if(!load) continue;
const uint32_t valueNum = load->getValueNum();
bool replaced = false;
Instruction *ins_after = load; // the instruction to insert after.
for (uint32_t valueID = 0; valueID < valueNum; ++valueID) {
const Type type = load->getValueType();
const RegisterFamily family = getFamily(type);
const uint32_t size = getFamilySize(family);
const uint32_t offset = loadAddImm.offset + valueID * size;
const PushLocation argLocation(*fn, loadAddImm.argID, offset);
Register pushed;
const Register reg = load->getValue(valueID);
if (offset != 0) {
if(inserted.contains(argLocation)) {
pushed = argLocation.getRegister();
} else {
// pushed register should be uniform register.
pushed = fn->newRegister(family, true);
this->appendPushedConstant(pushed, argLocation);
inserted.insert(argLocation);
}
} else {
pushed = fn->getArg(loadAddImm.argID).reg;
}
// TODO the MOV instruction can be most of the time avoided if the
// register is never written. We must however support the register
// replacement in the instruction interface to be able to patch all the
// instruction that uses "reg"
Instruction mov = ir::MOV(type, reg, pushed);
mov.insert(ins_after, &ins_after);
replaced = true;
}
if (replaced) {
dead.insert(load);
load->remove();
}
}
REMOVE_INSN(add)
REMOVE_INSN(loadImm)
}
#undef REMOVE_INSN
void FunctionArgumentLowerer::lowerIndirectRead(uint32_t argID)
{
FunctionArgument &arg = fn->getArg(argID);
vector derivedRegs;
map> addPtrInsns;
derivedRegs.push_back(arg.reg);
//Collect all load from this argument.
for(uint32_t i=0; igetRegUse(derivedRegs[i]);
for (const auto &use : *useSet) {
Instruction *insn = const_cast(use->getInstruction());
const Opcode opcode = insn->getOpcode();
const uint32_t dstNum = insn->getDstNum();
(void) dstNum;
GBE_ASSERT(dstNum == 1 || opcode == OP_LOAD);
const Register dst = insn->getDst();
auto it = addPtrInsns.find(derivedRegs[i]);
if((opcode == OP_ADD) && (derivedRegs[i] == arg.reg)) {
GBE_ASSERT(it == addPtrInsns.end());
vector addInsns;
addInsns.push_back(insn);
addPtrInsns.insert(std::make_pair(dst, addInsns));
derivedRegs.push_back(dst);
} else if(opcode == OP_LOAD) {
LoadInstruction *load = cast(insn);
if(!load)
continue;
if (load->getAddressSpace() != MEM_PRIVATE)
continue;
IndirectLoad indirectLoad;
Register addr = load->getAddressRegister();
indirectLoad.argID = argID;
indirectLoad.load = insn;
auto addrIt = addPtrInsns.find(addr);
GBE_ASSERT(addrIt != addPtrInsns.end());
indirectLoad.adds = addrIt->second;
indirectSeq.push_back(indirectLoad);
} else {
if(it == addPtrInsns.end()) continue; //use arg as phi or selection, no add, skip it.
auto dstIt = addPtrInsns.find(dst);
if(dstIt == addPtrInsns.end())
addPtrInsns.insert(std::make_pair(dst, it->second));
else {
//Muilt src from both argument, such as select, or phi, merge the vector
dstIt->second.insert(dstIt->second.end(), it->second.begin(), it->second.end());
}
derivedRegs.push_back(dst);
}
}
}
}
void FunctionArgumentLowerer::ReplaceIndirectLoad(void)
{
if (indirectSeq.size() == 0)
return;
// Track instructions we remove to recursively kill them properly
set dead;
set inserted;
for (const auto &indirectLoad : indirectSeq) {
const Register arg = fn->getArg(indirectLoad.argID).reg;
if(dead.contains(indirectLoad.load)) continue; //repetitive load in the indirectSeq, skip.
LoadInstruction *load = cast(indirectLoad.load);
const uint32_t valueNum = load ? load->getValueNum() : 0;
bool replaced = false;
Instruction *ins_after = load; // the instruction to insert after.
for (uint32_t valueID = 0; valueID < valueNum; ++valueID) {
const Type type = load->getValueType();
const RegisterFamily family = getFamily(type);
const uint32_t size = getFamilySize(family);
const uint32_t offset = valueID * size;
const Register reg = load->getValue(valueID);
Register addressReg = load->getAddressRegister();
if (fn->getPointerFamily() == FAMILY_QWORD) {
Register tmp = fn->newRegister(FAMILY_DWORD);
Instruction cvt = ir::CVT(ir::TYPE_U32, ir::TYPE_U64, tmp, load->getAddressRegister());
cvt.insert(ins_after, &ins_after);
addressReg = tmp;
}
Instruction mov = ir::INDIRECT_MOV(type, reg, arg, addressReg, offset);
mov.insert(ins_after, &ins_after);
replaced = true;
}
if (replaced && !dead.contains(load)) {
dead.insert(load);
load->remove();
}
vector adds = indirectLoad.adds;
for (uint32_t i=0; i(adds[i]);
if (add && !dead.contains(add)) {
Register dst = add->getDst();
const Register src0 = add->getSrc(0);
const Register src1 = add->getSrc(1);
GBE_ASSERT(src0 == arg || src1 == arg);
Register src = (src0 == arg) ? src1 : src0;
Instruction mov = ir::MOV(add->getType(), dst, src);
//MOV instruction could optimize if the dst don't write later
mov.replace(add);
dead.insert(add);
}
}
}
}
bool FunctionArgumentLowerer::useStore(const ValueDef &def, set &visited)
{
const UseSet &useSet = dag->getUse(def);
for (const auto &use : useSet) {
const Instruction *insn = use->getInstruction();
const uint32_t srcID = use->getSrcID();
const Opcode opcode = insn->getOpcode();
if (visited.contains(insn)) continue;
visited.insert(insn);
if (opcode == OP_STORE && srcID == StoreInstruction::addressIndex)
return true;
if (insn->isMemberOf() == false &&
insn->isMemberOf() == false)
continue;
else {
const uint32_t dstNum = insn->getDstNum();
for (uint32_t dstID = 0; dstID < dstNum; ++dstID)
if (this->useStore(ValueDef(insn, dstID), visited) == true)
return true;
}
}
return false;
}
bool FunctionArgumentLowerer::matchLoadAddImm(uint32_t argID)
{
const FunctionArgument &arg = fn->getArg(argID);
LoadAddImmSeq tmpSeq;
bool match = true;
// Inspect all uses of the function argument pointer
const UseSet &useSet = dag->getUse(&arg);
for (auto use : useSet) {
Instruction *insn = const_cast(use->getInstruction());
const Opcode opcode = insn->getOpcode();
// load dst arg
LoadAddImm loadAddImm;
if (matchLoad(insn, NULL, NULL, 0, argID, loadAddImm)) {
tmpSeq.push_back(loadAddImm);
continue;
}
// add.ptr_type dst ptr other
if (opcode != OP_ADD) return false;
BinaryInstruction *add = cast(insn);
if(!add) return false;
const Type addType = add->getType();
const RegisterFamily family = getFamily(addType);
if (family != unit.getPointerFamily()) return false;
if (addType == TYPE_FLOAT) return false;
// step 1 -> check that the other source comes from a load immediate
const uint32_t srcID = use->getSrcID();
const uint32_t otherID = srcID ^ 1;
const DefSet &defSet = dag->getDef(insn, otherID);
const uint32_t defNum = defSet.size();
if (defNum == 0 || defNum > 1) continue; // undefined or more than one def
const ValueDef *otherDef = *defSet.begin();
if (otherDef->getType() != ValueDef::DEF_INSN_DST) return false;
Instruction *otherInsn = const_cast(otherDef->getInstruction());
if (otherInsn->getOpcode() != OP_LOADI) return false;
LoadImmInstruction *loadImm = cast(otherInsn);
if(!loadImm) return false;
const Immediate imm = loadImm->getImmediate();
const uint64_t offset = getOffsetFromImm(imm);
// step 2 -> check that the results of the add are loads from private
// memory
const UseSet &addUseSet = dag->getUse(add, 0);
for (auto addUse : addUseSet) {
Instruction *insn = const_cast(addUse->getInstruction());
// We finally find something like load dst arg+imm
LoadAddImm loadAddImm;
if (matchLoad(insn, add, loadImm, offset, argID, loadAddImm)) {
tmpSeq.push_back(loadAddImm);
continue;
} else
match = false;
}
}
// OK, the argument only need direct loads. We can now append all the
// direct load definitions we found
for (const auto &loadImmSeq : tmpSeq)
seq.push_back(loadImmSeq);
return match;
}
ArgUse FunctionArgumentLowerer::getArgUse(uint32_t argID)
{
FunctionArgument &arg = fn->getArg(argID);
// case 1 - we may store something to the structure argument
set visited;
if (this->useStore(ValueDef(&arg), visited))
return ARG_WRITTEN;
// case 2 - we look for the patterns: LOAD(ptr) or LOAD(ptr+imm)
if (this->matchLoadAddImm(argID))
return ARG_DIRECT_READ;
// case 3 - LOAD(ptr+runtime_value)
return ARG_INDIRECT_READ;
}
ArgUse FunctionArgumentLowerer::lower(uint32_t argID) {
const ArgUse argUse = this->getArgUse(argID);
#if GBE_DEBUG
GBE_ASSERTM(argUse != ARG_WRITTEN,
"TODO A store to a structure argument "
"(i.e. not a char/short/int/float argument) has been found. "
"This is not supported yet");
//GBE_ASSERTM(argUse != ARG_INDIRECT_READ,
// "TODO Only direct loads of structure arguments are "
// "supported now");
#endif /* GBE_DEBUG */
return argUse;
}
void lowerFunctionArguments(Unit &unit, const std::string &functionName) {
FunctionArgumentLowerer lowerer(unit);
lowerer.lower(functionName);
}
} /* namespace ir */
} /* namespace gbe */