summaryrefslogtreecommitdiff
path: root/source/slang/ir.h
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/ir.h')
-rw-r--r--source/slang/ir.h463
1 files changed, 284 insertions, 179 deletions
diff --git a/source/slang/ir.h b/source/slang/ir.h
index 8f350827a..95a7a97d1 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -25,8 +25,6 @@ struct IRFunc;
struct IRGlobalValueWithCode;
struct IRInst;
struct IRModule;
-struct IRUser;
-struct IRValue;
typedef unsigned int IROpFlags;
enum : IROpFlags
@@ -57,6 +55,13 @@ enum IROp : int16_t
#include "ir-inst-defs.h"
+#define INST(ID, MNEMONIC, ARG_COUNT, FLAGS) /* empty */
+#define INST_RANGE(BASE, FIRST, LAST) \
+ kIROp_First##BASE = kIROp_##FIRST, \
+ kIROp_Last##BASE = kIROp_##LAST,
+
+#include "ir-inst-defs.h"
+
kIROp_Invalid = -1,
};
@@ -83,18 +88,18 @@ IROpInfo getIROpInfo(IROp op);
// A use of another value/inst within an IR operation
struct IRUse
{
- IRValue* get() { return usedValue; }
- IRUser* getUser() { return user; }
+ IRInst* get() { return usedValue; }
+ IRInst* getUser() { return user; }
- void init(IRUser* user, IRValue* usedValue);
- void set(IRValue* usedValue);
+ void init(IRInst* user, IRInst* usedValue);
+ void set(IRInst* usedValue);
void clear();
- // The value that is being used
- IRValue* usedValue = nullptr;
+ // The instruction that is being used
+ IRInst* usedValue = nullptr;
- // The value that is doing the using.
- IRUser* user = nullptr;
+ // The instruction that is doing the using.
+ IRInst* user = nullptr;
// The next use of the same value
IRUse* nextUse = nullptr;
@@ -145,22 +150,30 @@ struct IRDecoration : public IRObject
typedef Type IRType;
struct IRBlock;
+struct IRParentInst;
-// Base class for values in the IR
-struct IRValue : public IRObject
+// Every value in the IR is an instruction (even things
+// like literal values).
+//
+struct IRInst : public IRObject
{
// The operation that this value represents
IROp op;
- // The type of the result value of this instruction,
- // or `null` to indicate that the instruction has
- // no value.
- RefPtr<Type> type;
+ // The total number of operands of this instruction.
+ //
+ // TODO: We shouldn't need to allocate this on
+ // all instructions. Instead we should have
+ // instructions that need "vararg" support to
+ // allocate this field ahead of the `this`
+ // pointer.
+ uint32_t operandCount = 0;
- Type* getFullType() { return type; }
+ UInt getOperandCount()
+ {
+ return operandCount;
+ }
- Type* getRate();
- Type* getDataType();
// Source location information for this value, if any
SourceLoc sourceLoc;
@@ -180,154 +193,196 @@ struct IRValue : public IRObject
IRUse* firstUse = nullptr;
+ // The parent of this instruction.
+ IRParentInst* parent;
+
+ IRParentInst* getParent() { return parent; }
+
+ // The next and previous instructions with the same parent
+ IRInst* next;
+ IRInst* prev;
+
+ IRInst* getNextInst() { return next; }
+ IRInst* getPrevInst() { return prev; }
+
+ // The type of the result value of this instruction,
+ // or `null` to indicate that the instruction has
+ // no value.
+ RefPtr<Type> type;
+
+ Type* getFullType() { return type; }
+
+ Type* getRate();
+ Type* getDataType();
+
+ // After the type, we have data that is specific to
+ // the subtype of `IRInst`. In most cases, this is
+ // just a series of `IRUse` values representing
+ // operands of the instruction.
+
+ IRUse* getOperands();
+
+ IRInst* getOperand(UInt index)
+ {
+ return getOperands()[index].get();
+ }
+
+ void setOperand(UInt index, IRInst* value)
+ {
+ getOperands()[index].set(value);
+ }
+
+
+ //
+
// Replace all uses of this value with `other`, so
// that this value will now have no uses.
- void replaceUsesWith(IRValue* other);
+ void replaceUsesWith(IRInst* other);
// Free a value (which needs to have been removed
// from its parent, had its uses eliminated, etc.)
void deallocate();
+ // Clean up any non-pool resources used by this instruction
virtual void dispose() override;
-};
-// Values that are contained in a doubly-linked
-// list inside of some parent.
-//
-// TODO: consider merging this into `IRValue` so
-// that *all* values have a parent.
-struct IRChildValue : IRValue
-{
- // The parent of this value.
- IRValue* parent;
+ // Insert this instruction into the same basic block
+ // as `other`, right before/after it.
+ void insertBefore(IRInst* other);
+ void insertAfter(IRInst* other);
+
+ // Insert as first/last child of given parent
+ void insertAtStart(IRParentInst* parent);
+ void insertAtEnd(IRParentInst* parent);
+
+ // Move to the start/end of current parent
+ void moveToStart();
+ void moveToEnd();
+
+ // Insert before/after the given instruction, in a specific block
+ void insertBefore(IRInst* other, IRParentInst* parent);
+ void insertAfter(IRInst* other, IRParentInst* parent);
+
+ // Remove this instruction from its parent block,
+ // but don't delete it, or replace uses.
+ void removeFromParent();
+
+ // Remove this instruction from its parent block,
+ // and then destroy it (it had better have no uses!)
+ void removeAndDeallocate();
+
+ // Clear out the arguments of this instruction,
+ // so that we don't appear on the list of uses
+ // for those values.
+ void removeArguments();
- // The next and previous values in the same
- // list on teh same parent.
- IRChildValue* next;
- IRChildValue* prev;
};
-// Helper for storing linked lists of child values.
-struct IRValueListBase
+// `dynamic_cast` equivalent
+template<typename T>
+T* as(IRInst* inst, T* /* */ = nullptr)
{
- IRChildValue* first = 0;
- IRChildValue* last = 0;
+ if (inst && T::isaImpl(inst->op))
+ return (T*) inst;
+ return nullptr;
+}
-protected:
- void addImpl(IRValue* parent, IRChildValue* val);
-};
+// `static_cast` equivalent, with debug validation
template<typename T>
-struct IRValueList : IRValueListBase
+T* cast(IRInst* inst, T* /* */ = nullptr)
{
- T* getFirst() { return (T*)first; }
- T* getLast() { return (T*)last; }
+ SLANG_ASSERT(!inst || as<T>(inst));
+ return (T*)inst;
+}
+
+
+// A double-linked list of instruction
+struct IRInstListBase
+{
+ IRInstListBase()
+ {}
+
+ IRInstListBase(IRInst* first, IRInst* last)
+ : first(first)
+ , last(last)
+ {}
+
- void add(IRValue* parent, T* val)
- {
- addImpl(parent, val);
- }
+
+ IRInst* first = 0;
+ IRInst* last = 0;
+
+ IRInst* getFirst() { return first; }
+ IRInst* getLast() { return last; }
struct Iterator
{
- T* val;
+ IRInst* inst;
- Iterator() : val(0) {}
- Iterator(T* val) : val(val) {}
+ Iterator() : inst(nullptr) {}
+ Iterator(IRInst* inst) : inst(inst) {}
void operator++()
{
- if (val)
+ if (inst)
{
- val = (T*)val->next;
+ inst = inst->next;
}
}
- T* operator*()
+ IRInst* operator*()
{
- return val;
+ return inst;
}
bool operator!=(Iterator const& i)
{
- return val != i.val;
+ return inst != i.inst;
}
};
- Iterator begin() { return Iterator(getFirst()); }
- Iterator end() { return Iterator(nullptr); }
+ Iterator begin() { return Iterator(first); }
+ Iterator end() { return Iterator(last ? last->next : nullptr); }
};
-// Values that can use other values. These always
-// have their operands "tail allocated" after
-// the fields of this type, so derived types must
-// either:
-//
-// - Add no new fields, or
-// - Add only fields that represent the `IRUse` operands
-// - Add a fixed number of `IRUse` operand fields and
-// then any additional data after them.
-//
-struct IRUser : IRChildValue
+// Specialization of `IRInstListBase` for the case where
+// we know (or at least expect) all of the instructions
+// to be of type `T`
+template<typename T>
+struct IRInstList : IRInstListBase
{
- // The total number of arguments of this instruction.
- //
- // TODO: We shouldn't need to allocate this on
- // all instructions. Instead we should have
- // instructions that need "vararg" support to
- // allocate this field ahead of the `this`
- // pointer.
- uint32_t argCount = 0;
+ IRInstList() {}
- UInt getArgCount()
- {
- return argCount;
- }
+ IRInstList(T* first, T* last)
+ : IRInstListBase(first, last)
+ {}
- IRUse* getArgs();
+ explicit IRInstList(IRInstListBase const& list)
+ : IRInstListBase(list)
+ {}
- IRValue* getArg(UInt index)
- {
- return getArgs()[index].get();
- }
+ T* getFirst() { return (T*) first; }
+ T* getLast() { return (T*) last; }
- void setArg(UInt index, IRValue* value)
+ struct Iterator : public IRInstListBase::Iterator
{
- getArgs()[index].set(value);
- }
-};
-
-// Instructions are values that are children of a basic block,
-// and can actually be executed.
-struct IRInst : IRUser
-{
- IRBlock* getParentBlock() { return (IRBlock*)parent; }
+ Iterator() {}
+ Iterator(IRInst* inst) : IRInstListBase::Iterator(inst) {}
- IRInst* getPrevInst() { return (IRInst*)prev; }
- IRInst* getNextInst() { return (IRInst*)next; }
-
-
- // Insert this instruction into the same basic block
- // as `other`, right before it.
- void insertBefore(IRInst* other);
-
- // Remove this instruction from its parent block,
- // but don't delete it, or replace uses.
- void removeFromParent();
-
- // Remove this instruction from its parent block,
- // and then destroy it (it had better have no uses!)
- void removeAndDeallocate();
+ T* operator*()
+ {
+ return (T*) inst;
+ }
+ };
- // Clear out the arguments of this instruction,
- // so that we don't appear on the list of uses
- // for those values.
- void removeArguments();
+ Iterator begin() { return Iterator(first); }
+ Iterator end() { return Iterator(last ? last->next : nullptr); }
};
typedef int64_t IRIntegerValue;
typedef double IRFloatingPointValue;
-struct IRConstant : IRValue
+struct IRConstant : IRInst
{
union
{
@@ -341,10 +396,12 @@ struct IRConstant : IRValue
// A instruction that ends a basic block (usually because of control flow)
struct IRTerminatorInst : IRInst
-{};
-
-bool isTerminatorInst(IROp op);
-bool isTerminatorInst(IRInst* inst);
+{
+ static bool isaImpl(IROp op)
+ {
+ return (op >= kIROp_FirstTerminatorInst) && (op <= kIROp_LastTerminatorInst);
+ }
+};
// A function parameter is owned by a basic block, and represents
// either an incoming function parameter (in the entry block), or
@@ -354,15 +411,26 @@ bool isTerminatorInst(IRInst* inst);
// In each case, the basic idea is that a block is a "label with
// arguments."
//
-// Note: an `IRParam` is an `IRUser` *just* so that we can use
-// it as the user of other values during SSA generation.
-struct IRParam : IRUser
+struct IRParam : IRInst
+{
+ IRParam* getNextParam();
+ IRParam* getPrevParam();
+
+ static bool isaImpl(IROp op) { return op == kIROp_Param; }
+};
+
+// A "parent" instruction is one that contains other instructions
+// as its children. The most common case of a parent instruction
+// is a basic block, but there are other cases (e.g., a function
+// is in turn a parent for basic blocks).
+struct IRParentInst : IRInst
{
- IRParam* nextParam;
- IRParam* prevParam;
+ // The instructions stored under this parent
+ IRInstListBase children;
- IRParam* getNextParam() { return nextParam; }
- IRParam* getPrevParam() { return prevParam; }
+ IRInst* getFirstChild() { return children.first; }
+ IRInst* getLastChild() { return children.last; }
+ IRInstListBase getChildren() { return children; }
};
// A basic block is a parent instruction that adds the constraint
@@ -370,42 +438,63 @@ struct IRParam : IRUser
// no function declarations, or nested blocks). We also expect
// that the previous/next instruction are always a basic block.
//
-struct IRBlock : IRValue
+struct IRBlock : IRParentInst
{
// Linked list of the instructions contained in this block
//
- // Note that in a valid program, every block must end with
- // a "terminator" instruction, so these should be non-NULL,
- // and `lastInst` should actually be an `IRTerminatorInst`.
- IRInst* firstInst;
- IRInst* lastInst;
+ IRInstListBase getChildren() { return children; }
+ IRInst* getFirstInst() { return children.first; }
+ IRInst* getLastInst() { return children.last; }
- IRInst* getFirstInst() { return firstInst; }
- IRInst* getLastInst() { return lastInst; }
-
- // Links for the list of basic blocks in the parent function
- IRBlock* prevBlock;
- IRBlock* nextBlock;
-
- IRBlock* getPrevBlock() { return prevBlock; }
- IRBlock* getNextBlock() { return nextBlock; }
-
- // Linked list of parameters of this block
- IRParam* firstParam;
- IRParam* lastParam;
+ // In a valid program, every basic block should end with
+ // a "terminator" instruction.
+ //
+ // This function will return the terminator, if it exists,
+ // or `null` if there is none.
+ IRTerminatorInst* getTerminator() { return as<IRTerminatorInst>(getLastInst()); }
+
+ // We expect that the siblings of a basic block will
+ // always be other basic blocks (we don't allow
+ // mixing of blocks and other instructions in the
+ // same parent).
+ IRBlock* getPrevBlock() { return cast<IRBlock>(getPrevInst()); }
+ IRBlock* getNextBlock() { return cast<IRBlock>(getNextInst()); }
+
+ // The parameters of a block are represented by `IRParam`
+ // instructions at the start of the block. These play
+ // the role of function parameters for the entry block
+ // of a function, and of phi nodes in other blocks.
+ IRParam* getFirstParam() { return as<IRParam>(getFirstInst()); }
+ IRParam* getLastParam();
+ IRInstList<IRParam> getParams()
+ {
+ return IRInstList<IRParam>(
+ getFirstParam(),
+ getLastParam());
+ }
- IRParam* getFirstParam() { return firstParam; }
- IRParam* getLastParam() { return lastParam; }
void addParam(IRParam* param);
- // The parent function that contains this block
- IRGlobalValueWithCode* parentFunc;
-
- IRGlobalValueWithCode* getParent() { return parentFunc; }
-
- void insertAfter(IRBlock* other);
- void insertAfter(IRBlock* other, IRGlobalValueWithCode* func);
-
+ // The parent of a basic block is assumed to be a
+ // value with code (e.g., a function, global variable
+ // with initializer, etc.).
+ IRGlobalValueWithCode* getParent() { return cast<IRGlobalValueWithCode>(IRInst::getParent()); }
+
+ // The predecessor and successor lists of a block are needed
+ // when we want to work with the control flow graph (CFG) of
+ // a function. Rather than store these explicitly (and thus
+ // need to update them when transformations might change the
+ // CFG), we compute predecessors and successors in an
+ // implicit fashion using the use-def information for a
+ // block itself.
+ //
+ // To a first approximation, the predecessors of a block
+ // are the blocks where the IR value of the block is used.
+ // Similarly, the successors of a block are all values used
+ // by the terminator instruction of the block.
+ // The `getPredecessors()` and `getSuccessors()` functions
+ // make this more precise.
+ //
struct PredecessorList
{
PredecessorList(IRUse* begin) : b(begin) {}
@@ -465,6 +554,10 @@ struct IRBlock : IRValue
PredecessorList getPredecessors();
SuccessorList getSuccessors();
+
+ //
+
+ static bool isaImpl(IROp op) { return op == kIROp_Block; }
};
// For right now, we will represent the type of
@@ -474,21 +567,17 @@ struct IRBlock : IRValue
// TODO: need to do this better.
typedef FuncType IRFuncType;
-struct IRGlobalValue : IRValue
+// A "global value" is an instruction that might have
+// linkage, so that it can be declared in one module
+// and then resolved to a definition in another module.
+struct IRGlobalValue : IRParentInst
{
- IRModule* parentModule;
-
// The mangled name, for a symbol that should have linkage,
// or which might have multiple declarations.
Name* mangledName = nullptr;
-
- IRGlobalValue* nextGlobalValue;
- IRGlobalValue* prevGlobalValue;
-
- IRGlobalValue* getNextValue() { return nextGlobalValue; }
- IRGlobalValue* getPrevValue() { return prevGlobalValue; }
-
+#if 0
+ // TODO: these all belong on `IRInst`
void insertBefore(IRGlobalValue* other);
void insertBefore(IRGlobalValue* other, IRModule* module);
void insertAtStart(IRModule* module);
@@ -500,18 +589,26 @@ struct IRGlobalValue : IRValue
void removeFromParent();
void moveToEnd();
+#endif
+
+ static bool isaImpl(IROp op)
+ {
+ return (op >= kIROp_FirstGlobalValue) && (op <= kIROp_LastGlobalValue);
+ }
};
/// @brief A global value that potentially holds executable code.
///
struct IRGlobalValueWithCode : IRGlobalValue
{
- // The list of basic blocks in this function
- IRBlock* firstBlock = nullptr;
- IRBlock* lastBlock = nullptr;
-
- IRBlock* getFirstBlock() { return firstBlock; }
- IRBlock* getLastBlock() { return lastBlock; }
+ // The children of a value with code will be the basic
+ // blocks of its definition.
+ IRBlock* getFirstBlock() { return cast<IRBlock>(getFirstChild()); }
+ IRBlock* getLastBlock() { return cast<IRBlock>(getLastChild()); }
+ IRInstList<IRBlock> getBlocks()
+ {
+ return IRInstList<IRBlock>(getChildren());
+ }
// Add a block to the end of this function.
void addBlock(IRBlock* block);
@@ -557,7 +654,18 @@ struct IRFunc : IRGlobalValueWithCode
}
};
-// A module is a parent to functions, global variables, types, etc.
+// The IR module itself is represented as an instruction, which
+// serves at the root of the tree of all instructions in the module.
+struct IRModuleInst : IRParentInst
+{
+ // Pointer back to the non-instruction object that represents
+ // the module, so that we can get back to it in algorithms
+ // that need it.
+ IRModule* module;
+
+ IRInstListBase getGlobalInsts() { return getChildren(); }
+};
+
struct IRModule : RefObject
{
// The compilation session in use.
@@ -565,13 +673,10 @@ struct IRModule : RefObject
MemoryPool memoryPool;
List<IRObject*> irObjectsToFree; // list of ir objects to run destructor upon destruction
- // A list of all the functions and other
- // global values declared in this module.
- IRGlobalValue* firstGlobalValue = nullptr;
- IRGlobalValue* lastGlobalValue = nullptr;
+ IRModuleInst* moduleInst;
+ IRModuleInst* getModuleInst() { return moduleInst; }
- IRGlobalValue* getFirstGlobalValue() { return firstGlobalValue; }
- IRGlobalValue* getlastGlobalValue() { return lastGlobalValue; }
+ IRInstListBase getGlobalInsts() { return getModuleInst()->getChildren(); }
~IRModule()
{