From 78c6d3c02f327ba930195f4dd7504f5dcf951c1a Mon Sep 17 00:00:00 2001 From: Matt Pharr Date: Thu, 5 Jan 2012 12:20:44 -0800 Subject: [PATCH] Add initial support for 'goto' statements. ispc now supports goto, but only under uniform control flow--i.e. it must be possible for the compiler to statically determine that all program instances will follow the goto. An error is issued at compile time if a goto is used when this is not the case. --- ast.cpp | 9 ++- cbackend.cpp | 2 +- ctx.cpp | 45 +++++++++-- ctx.h | 17 +++++ docs/ispc.txt | 35 ++++++++- examples/intrinsics/generic-16.h | 6 ++ examples/intrinsics/sse4.h | 4 + func.cpp | 13 +++- ispc.h | 3 + parse.yy | 16 +++- stmt.cpp | 127 +++++++++++++++++++++++++++++++ stmt.h | 32 ++++++++ tests/goto-1.ispc | 17 +++++ tests/goto-2.ispc | 18 +++++ tests/goto-3.ispc | 18 +++++ tests/goto-4.ispc | 19 +++++ tests_errors/goto-1.ispc | 10 +++ tests_errors/goto-2.ispc | 11 +++ tests_errors/goto-3.ispc | 11 +++ tests_errors/goto-4.ispc | 10 +++ 20 files changed, 408 insertions(+), 15 deletions(-) create mode 100644 tests/goto-1.ispc create mode 100644 tests/goto-2.ispc create mode 100644 tests/goto-3.ispc create mode 100644 tests/goto-4.ispc create mode 100644 tests_errors/goto-1.ispc create mode 100644 tests_errors/goto-2.ispc create mode 100644 tests_errors/goto-3.ispc create mode 100644 tests_errors/goto-4.ispc diff --git a/ast.cpp b/ast.cpp index 023e4ba9..40503219 100644 --- a/ast.cpp +++ b/ast.cpp @@ -91,6 +91,7 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, ForStmt *fs; ForeachStmt *fes; ReturnStmt *rs; + LabeledStmt *ls; StmtList *sl; PrintStmt *ps; AssertStmt *as; @@ -131,9 +132,12 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, fes->stmts = (Stmt *)WalkAST(fes->stmts, preFunc, postFunc, data); } else if (dynamic_cast(node) != NULL || - dynamic_cast(node) != NULL) { - // nothing + dynamic_cast(node) != NULL || + dynamic_cast(node) != NULL) { + // nothing } + else if ((ls = dynamic_cast(node)) != NULL) + ls->stmt = (Stmt *)WalkAST(ls->stmt, preFunc, postFunc, data); else if ((rs = dynamic_cast(node)) != NULL) rs->val = (Expr *)WalkAST(rs->val, preFunc, postFunc, data); else if ((sl = dynamic_cast(node)) != NULL) { @@ -289,3 +293,4 @@ EstimateCost(ASTNode *root) { WalkAST(root, lCostCallback, NULL, &cost); return cost; } + diff --git a/cbackend.cpp b/cbackend.cpp index cf606672..2eb853e3 100644 --- a/cbackend.cpp +++ b/cbackend.cpp @@ -376,7 +376,7 @@ namespace { if (I.getType() == Type::getVoidTy(I.getContext()) || !I.hasOneUse() || isa(I) || isa(I) || isa(I) || isa(I) || isa(I) || isa(I) || - isa(I) || isa(I)) + isa(I) || isa(I) || isa(I)) // Don't inline a load across a store or other bad things! return false; diff --git a/ctx.cpp b/ctx.cpp index 1ad5d086..b0afca64 100644 --- a/ctx.cpp +++ b/ctx.cpp @@ -77,7 +77,7 @@ struct CFInfo { bool IsIf() { return type == If; } bool IsLoop() { return type == Loop; } bool IsForeach() { return type == Foreach; } - bool IsVaryingType() { return !isUniform; } + bool IsVarying() { return !isUniform; } bool IsUniform() { return isUniform; } enum CFType { If, Loop, Foreach }; @@ -157,9 +157,10 @@ CFInfo::GetForeach(llvm::BasicBlock *breakTarget, /////////////////////////////////////////////////////////////////////////// FunctionEmitContext::FunctionEmitContext(Function *func, Symbol *funSym, - llvm::Function *llvmFunction, + llvm::Function *lf, SourcePos firstStmtPos) { function = func; + llvmFunction = lf; /* Create a new basic block to store all of the allocas */ allocaBlock = llvm::BasicBlock::Create(*g->ctx, "allocas", llvmFunction, 0); @@ -762,7 +763,7 @@ int FunctionEmitContext::VaryingCFDepth() const { int sum = 0; for (unsigned int i = 0; i < controlFlowInfo.size(); ++i) - if (controlFlowInfo[i]->IsVaryingType()) + if (controlFlowInfo[i]->IsVarying()) ++sum; return sum; } @@ -777,6 +778,41 @@ FunctionEmitContext::InForeachLoop() const { } +bool +FunctionEmitContext::initLabelBBlocks(ASTNode *node, void *data) { + LabeledStmt *ls = dynamic_cast(node); + if (ls == NULL) + return true; + + FunctionEmitContext *ctx = (FunctionEmitContext *)data; + + if (ctx->labelMap.find(ls->name) != ctx->labelMap.end()) + Error(ls->pos, "Multiple labels named \"%s\" in function.", + ls->name.c_str()); + else { + llvm::BasicBlock *bb = ctx->CreateBasicBlock(ls->name.c_str()); + ctx->labelMap[ls->name] = bb; + } + return true; +} + + +void +FunctionEmitContext::InitializeLabelMap(Stmt *code) { + labelMap.erase(labelMap.begin(), labelMap.end()); + WalkAST(code, initLabelBBlocks, NULL, this); +} + + +llvm::BasicBlock * +FunctionEmitContext::GetLabeledBasicBlock(const std::string &label) { + if (labelMap.find(label) != labelMap.end()) + return labelMap[label]; + else + return NULL; +} + + void FunctionEmitContext::CurrentLanesReturned(Expr *expr, bool doCoherenceCheck) { const Type *returnType = function->GetReturnType(); @@ -920,8 +956,7 @@ FunctionEmitContext::GetStringPtr(const std::string &str) { llvm::BasicBlock * FunctionEmitContext::CreateBasicBlock(const char *name) { - llvm::Function *function = bblock->getParent(); - return llvm::BasicBlock::Create(*g->ctx, name, function); + return llvm::BasicBlock::Create(*g->ctx, name, llvmFunction); } diff --git a/ctx.h b/ctx.h index e573a8ca..743dbb67 100644 --- a/ctx.h +++ b/ctx.h @@ -39,6 +39,7 @@ #define ISPC_CTX_H 1 #include "ispc.h" +#include #include #include #include @@ -192,6 +193,15 @@ public: bool InForeachLoop() const; + /** Step through the code and find label statements; create a basic + block for each one, so that subsequent calls to + GetLabeledBasicBlock() return the corresponding basic block. */ + void InitializeLabelMap(Stmt *code); + + /** If there is a label in the function with the given name, return the + new basic block that it starts. */ + llvm::BasicBlock *GetLabeledBasicBlock(const std::string &label); + /** Called to generate code for 'return' statement; value is the expression in the return statement (if non-NULL), and doCoherenceCheck indicates whether instructions should be generated @@ -446,6 +456,9 @@ private: /** Pointer to the Function for which we're currently generating code. */ Function *function; + /** LLVM function representation for the current function. */ + llvm::Function *llvmFunction; + /** The basic block into which we add any alloca instructions that need to go at the very start of the function. */ llvm::BasicBlock *allocaBlock; @@ -537,6 +550,10 @@ private: tasks launched from the current function. */ llvm::Value *launchGroupHandlePtr; + std::map labelMap; + + static bool initLabelBBlocks(ASTNode *node, void *data); + llvm::Value *pointerVectorToVoidPointers(llvm::Value *value); static void addGSMetadata(llvm::Value *inst, SourcePos pos); bool ifsInLoopAllUniform() const; diff --git a/docs/ispc.txt b/docs/ispc.txt index 011ec208..5d9ea2b0 100644 --- a/docs/ispc.txt +++ b/docs/ispc.txt @@ -100,6 +100,7 @@ Contents: * `Conditional Statements: "if"`_ * `Basic Iteration Statements: "for", "while", and "do"`_ + * `Unstructured Control Flow: "goto"`_ * `"Coherent" Control Flow Statements: "cif" and Friends`_ * `Parallel Iteration Statements: "foreach" and "foreach_tiled"`_ * `Parallel Iteration with "programIndex" and "programCount"`_ @@ -1184,7 +1185,8 @@ but are likely to be supported in future releases: ``int64`` types * Character constants * String constants and arrays of characters as strings -* ``switch`` and ``goto`` statements +* ``switch`` statements +* ``goto`` statements are partially supported (see `Unstructured Control Flow: "goto"`_) * ``union`` types * Bitfield members of ``struct`` types * Variable numbers of arguments to functions @@ -2005,6 +2007,37 @@ one of them executes a ``continue`` statement, other program instances executing code in the loop body that didn't execute the ``continue`` will be unaffected by it. +Unstructured Control Flow: "goto" +--------------------------------- + +``goto`` statements are allowed in ``ispc`` programs under limited +circumstances; specifically, only when the compiler can determine that if +any program instance executes a ``goto`` statement, then all of the program +instances will be running at that statement, such that all will follow the +``goto``. + +Put another way: it's illegal for there to be "varying" control flow +statements in scopes that enclose a ``goto`` statement. An error is issued +if a ``goto`` is used in this situation. + +The syntax for adding labels to ``ispc`` programs and jumping to them with +``goto`` is the same as in C. The following code shows a ``goto`` based +equivalent of a ``for`` loop where the induction variable ``i`` goes from +zero to ten. + +:: + + uniform int i = 0; + check: + if (i > 10) + goto done; + // loop body + ++i; + goto check; + done: + // ... + + "Coherent" Control Flow Statements: "cif" and Friends ----------------------------------------------------- diff --git a/examples/intrinsics/generic-16.h b/examples/intrinsics/generic-16.h index ea120abb..e2426f7f 100644 --- a/examples/intrinsics/generic-16.h +++ b/examples/intrinsics/generic-16.h @@ -307,6 +307,12 @@ static FORCEINLINE uint32_t __movmsk(__vec16_i1 mask) { return mask.v; } +static FORCEINLINE __vec16_i1 __equal(__vec16_i1 a, __vec16_i1 b) { + __vec16_i1 r; + r.v = (a.v & b.v) | (~a.v & ~b.v); + return r; +} + static FORCEINLINE __vec16_i1 __and(__vec16_i1 a, __vec16_i1 b) { __vec16_i1 r; r.v = a.v & b.v; diff --git a/examples/intrinsics/sse4.h b/examples/intrinsics/sse4.h index c9556924..0662a4c0 100644 --- a/examples/intrinsics/sse4.h +++ b/examples/intrinsics/sse4.h @@ -228,6 +228,10 @@ static FORCEINLINE uint32_t __movmsk(__vec4_i1 mask) { return _mm_movemask_ps(mask.v); } +static FORCEINLINE __vec4_i1 __equal(__vec4_i1 a, __vec4_i1 b) { + return _mm_cmpeq_epi32(_mm_castps_si128(a.v), _mm_castps_si128(b.v)); +} + static FORCEINLINE __vec4_i1 __and(__vec4_i1 a, __vec4_i1 b) { return _mm_and_ps(a.v, b.v); } diff --git a/func.cpp b/func.cpp index 288f5e44..6f5e03db 100644 --- a/func.cpp +++ b/func.cpp @@ -290,8 +290,10 @@ Function::emitCode(FunctionEmitContext *ctx, llvm::Function *function, llvm::BasicBlock *bbAllOn = ctx->CreateBasicBlock("all_on"); llvm::BasicBlock *bbNotAll = ctx->CreateBasicBlock("not_all_on"); - ctx->BranchInst(bbAllOn, bbNotAll, allOn); + // Set up basic blocks for goto targets + ctx->InitializeLabelMap(code); + ctx->BranchInst(bbAllOn, bbNotAll, allOn); // all on: we've determined dynamically that the mask is all // on. Set the current mask to "all on" explicitly so that // codegen for this path can be improved with this knowledge in @@ -322,12 +324,19 @@ Function::emitCode(FunctionEmitContext *ctx, llvm::Function *function, // above ctx->SetCurrentBasicBlock(bbSomeOn); ctx->SetFunctionMask(mask); + + // Set up basic blocks for goto targets again; we want to have + // one set of them for gotos in the 'all on' case, and a + // distinct set for the 'mixed mask' case. + ctx->InitializeLabelMap(code); + code->EmitCode(ctx); if (ctx->GetCurrentBasicBlock()) ctx->ReturnInst(); - } else + // Set up basic blocks for goto targets + ctx->InitializeLabelMap(code); // No check, just emit the code code->EmitCode(ctx); } diff --git a/ispc.h b/ispc.h index 507d3328..e058cc3b 100644 --- a/ispc.h +++ b/ispc.h @@ -98,6 +98,8 @@ namespace llvm { #endif class ArrayType; +class AST; +class ASTNode; class AtomicType; class FunctionEmitContext; class Expr; @@ -421,6 +423,7 @@ enum { COST_FUNPTR_UNIFORM = 12, COST_FUNPTR_VARYING = 24, COST_GATHER = 8, + COST_GOTO = 4, COST_LOAD = 2, COST_REGULAR_BREAK_CONTINUE = 2, COST_RETURN = 4, diff --git a/parse.yy b/parse.yy index 70cb2b3f..e5091e33 100644 --- a/parse.yy +++ b/parse.yy @@ -224,7 +224,7 @@ struct ForeachDimension { %type declaration_specifiers %type string_constant -%type struct_or_union_name enum_identifier +%type struct_or_union_name enum_identifier goto_identifier %type int_constant soa_width_specifier %type foreach_dimension_specifier @@ -1262,7 +1262,11 @@ statement ; labeled_statement - : TOKEN_CASE constant_expression ':' statement + : goto_identifier ':' statement + { + $$ = new LabeledStmt($1, $3, @1); + } + | TOKEN_CASE constant_expression ':' statement { UNIMPLEMENTED; } | TOKEN_DEFAULT ':' statement { UNIMPLEMENTED; } @@ -1433,9 +1437,13 @@ iteration_statement } ; +goto_identifier + : TOKEN_IDENTIFIER { $$ = yylval.stringVal->c_str(); } + ; + jump_statement - : TOKEN_GOTO TOKEN_IDENTIFIER ';' - { UNIMPLEMENTED; } + : TOKEN_GOTO goto_identifier ';' + { $$ = new GotoStmt($2, @1, @2); } | TOKEN_CONTINUE ';' { $$ = new ContinueStmt(false, @1); } | TOKEN_BREAK ';' diff --git a/stmt.cpp b/stmt.cpp index 95142abe..8d46c012 100644 --- a/stmt.cpp +++ b/stmt.cpp @@ -494,6 +494,7 @@ lEmitIfStatements(FunctionEmitContext *ctx, Stmt *stmts, const char *trueOrFalse ctx->EndScope(); } + void IfStmt::EmitCode(FunctionEmitContext *ctx) const { // First check all of the things that might happen due to errors @@ -1915,6 +1916,132 @@ ReturnStmt::Print(int indent) const { } +/////////////////////////////////////////////////////////////////////////// +// GotoStmt + +GotoStmt::GotoStmt(const char *l, SourcePos gotoPos, SourcePos ip) + : Stmt(gotoPos) { + label = l; + identifierPos = ip; +} + + +void +GotoStmt::EmitCode(FunctionEmitContext *ctx) const { + if (ctx->VaryingCFDepth() > 0) { + Error(pos, "\"goto\" statements are only legal under \"uniform\" " + "control flow."); + return; + } + if (ctx->InForeachLoop()) { + Error(pos, "\"goto\" statements are currently illegal inside " + "\"foreach\" loops."); + return; + } + + llvm::BasicBlock *bb = ctx->GetLabeledBasicBlock(label); + if (bb == NULL) { + // TODO: use the string distance stuff to suggest alternatives if + // there are some with names close to the label name we have here.. + Error(identifierPos, "No label named \"%s\" found in current function.", + label.c_str()); + return; + } + + ctx->BranchInst(bb); + ctx->SetCurrentBasicBlock(NULL); +} + + +void +GotoStmt::Print(int indent) const { + printf("%*cGoto label \"%s\"\n", indent, ' ', label.c_str()); +} + + +Stmt * +GotoStmt::Optimize() { + return this; +} + + +Stmt * +GotoStmt::TypeCheck() { + return this; +} + + +int +GotoStmt::EstimateCost() const { + return COST_GOTO; +} + + +/////////////////////////////////////////////////////////////////////////// +// LabeledStmt + +LabeledStmt::LabeledStmt(const char *n, Stmt *s, SourcePos p) + : Stmt(p) { + name = n; + stmt = s; +} + + +void +LabeledStmt::EmitCode(FunctionEmitContext *ctx) const { + llvm::BasicBlock *bblock = ctx->GetLabeledBasicBlock(name); + assert(bblock != NULL); + + // End the current basic block with a jump to our basic block and then + // set things up for emission to continue there. Note that the current + // basic block may validly be NULL going into this statement due to an + // earlier goto that NULLed it out; that doesn't stop us from + // re-establishing a current basic block starting at the label.. + if (ctx->GetCurrentBasicBlock() != NULL) + ctx->BranchInst(bblock); + ctx->SetCurrentBasicBlock(bblock); + + if (stmt != NULL) + stmt->EmitCode(ctx); +} + + +void +LabeledStmt::Print(int indent) const { + printf("%*cLabel \"%s\"\n", indent, ' ', name.c_str()); + if (stmt != NULL) + stmt->Print(indent); +} + + +Stmt * +LabeledStmt::Optimize() { + return this; +} + + +Stmt * +LabeledStmt::TypeCheck() { + if (!isalpha(name[0]) || name[0] == '_') { + Error(pos, "Label must start with either alphabetic character or '_'."); + return NULL; + } + for (unsigned int i = 1; i < name.size(); ++i) { + if (!isalnum(name[i]) && name[i] != '_') { + Error(pos, "Character \"%c\" is illegal in labels.", name[i]); + return NULL; + } + } + return this; +} + + +int +LabeledStmt::EstimateCost() const { + return 0; +} + + /////////////////////////////////////////////////////////////////////////// // StmtList diff --git a/stmt.h b/stmt.h index 73142197..eb5af2f9 100644 --- a/stmt.h +++ b/stmt.h @@ -282,6 +282,38 @@ public: }; +class GotoStmt : public Stmt { +public: + GotoStmt(const char *label, SourcePos gotoPos, SourcePos idPos); + + void EmitCode(FunctionEmitContext *ctx) const; + void Print(int indent) const; + + Stmt *Optimize(); + Stmt *TypeCheck(); + int EstimateCost() const; + + std::string label; + SourcePos identifierPos; +}; + + +class LabeledStmt : public Stmt { +public: + LabeledStmt(const char *label, Stmt *stmt, SourcePos p); + + void EmitCode(FunctionEmitContext *ctx) const; + void Print(int indent) const; + + Stmt *Optimize(); + Stmt *TypeCheck(); + int EstimateCost() const; + + std::string name; + Stmt *stmt; +}; + + /** @brief Representation of a list of statements in the program. */ class StmtList : public Stmt { diff --git a/tests/goto-1.ispc b/tests/goto-1.ispc new file mode 100644 index 00000000..dd7ab119 --- /dev/null +++ b/tests/goto-1.ispc @@ -0,0 +1,17 @@ + +export uniform int width() { return programCount; } + + +export void f_f(uniform float RET[], uniform float aFOO[]) { + float a = aFOO[programIndex]; + float b = 0.; b = a; + RET[programIndex] = a+b; + goto skip; + RET[programIndex] = 0; + skip: + ; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 2 + 2*programIndex; +} diff --git a/tests/goto-2.ispc b/tests/goto-2.ispc new file mode 100644 index 00000000..d2b1f8b1 --- /dev/null +++ b/tests/goto-2.ispc @@ -0,0 +1,18 @@ + +export uniform int width() { return programCount; } + + +export void f_f(uniform float RET[], uniform float aFOO[]) { + float a = aFOO[programIndex]; + float b = 0.; b = a; + RET[programIndex] = a+b; + if (all(a != 0)) + goto skip; + RET[programIndex] = 0; + skip: + ; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 2 + 2*programIndex; +} diff --git a/tests/goto-3.ispc b/tests/goto-3.ispc new file mode 100644 index 00000000..bcad69ad --- /dev/null +++ b/tests/goto-3.ispc @@ -0,0 +1,18 @@ + +export uniform int width() { return programCount; } + + +export void f_f(uniform float RET[], uniform float aFOO[]) { + float a = aFOO[programIndex]; + float b = 0.; b = a; + RET[programIndex] = a+b; + if (all(a == 0)) + goto skip; + RET[programIndex] = 0; + skip: + ; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 0; +} diff --git a/tests/goto-4.ispc b/tests/goto-4.ispc new file mode 100644 index 00000000..97c373ef --- /dev/null +++ b/tests/goto-4.ispc @@ -0,0 +1,19 @@ + +export uniform int width() { return programCount; } + + +export void f_f(uniform float RET[], uniform float aFOO[]) { + float a = aFOO[programIndex]; + float b = 0.; b = a; + RET[programIndex] = 0; + encore: + ++RET[programIndex]; + if (any(a != 0)) { + a = max(a-1, 0); + goto encore; + } +} + +export void result(uniform float RET[]) { + RET[programIndex] = programCount+1; +} diff --git a/tests_errors/goto-1.ispc b/tests_errors/goto-1.ispc new file mode 100644 index 00000000..dbe14491 --- /dev/null +++ b/tests_errors/goto-1.ispc @@ -0,0 +1,10 @@ +// Multiple labels named "label" in function + +void func(int x) { + label: + ; + label: + ; +} + + diff --git a/tests_errors/goto-2.ispc b/tests_errors/goto-2.ispc new file mode 100644 index 00000000..fc4a00c6 --- /dev/null +++ b/tests_errors/goto-2.ispc @@ -0,0 +1,11 @@ +// "goto" statements are only legal under "uniform" control flow + +void func(int x) { + if (x < 0) + goto label; + + label: + ; +} + + diff --git a/tests_errors/goto-3.ispc b/tests_errors/goto-3.ispc new file mode 100644 index 00000000..2a1dc48c --- /dev/null +++ b/tests_errors/goto-3.ispc @@ -0,0 +1,11 @@ +// "goto" statements are only legal under "uniform" control flow + +void func(int x) { + cif (x < 0) + goto label; + + label: + ; +} + + diff --git a/tests_errors/goto-4.ispc b/tests_errors/goto-4.ispc new file mode 100644 index 00000000..08a84b4d --- /dev/null +++ b/tests_errors/goto-4.ispc @@ -0,0 +1,10 @@ +// "goto" statements are only legal under "uniform" control flow + +void func(int x) { + label: + + for(int i =0 ;i