Add assert() statement support. Issue #106.

This commit is contained in:
Matt Pharr
2011-10-15 13:22:38 -07:00
parent 1ab05c0351
commit 422b8268a9
14 changed files with 289 additions and 3 deletions

View File

@@ -1777,6 +1777,43 @@ prefetch_read(varying_int64, <$1 x i64>)
prefetch_read(varying_float, <$1 x float>)
prefetch_read(varying_double, <$1 x double>)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; assert
declare i32 @printf(i8*, ...)
declare void @abort() noreturn
define internal void @__do_assert_uniform(i8 *%str, i1 %test, <$1 x i32> %mask) {
br i1 %test, label %ok, label %fail
fail:
%call = call i32 (i8*, ...)* @printf(i8* %str)
call void @abort() noreturn
unreachable
ok:
ret void
}
define internal void @__do_assert_varying(i8 *%str, <$1 x i32> %test,
<$1 x i32> %mask) {
%nottest = xor <$1 x i32> %test,
< forloop(i, 1, eval($1-1), `i32 -1, ') i32 -1 >
%nottest_and_mask = and <$1 x i32> %nottest, %mask
%mm = call i32 @__movmsk(<$1 x i32> %nottest_and_mask)
%all_ok = icmp eq i32 %mm, 0
br i1 %all_ok, label %ok, label %fail
fail:
%call = call i32 (i8*, ...)* @printf(i8* %str)
call void @abort() noreturn
unreachable
ok:
ret void
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; stdlib transcendentals
;;

View File

@@ -89,6 +89,7 @@ Contents:
+ `Math Functions`_
+ `Output Functions`_
+ `Assertions`_
+ `Cross-Program Instance Operations`_
+ `Converting Between Array-of-Structures and Structure-of-Arrays Layout`_
+ `Packed Load and Store Operations`_
@@ -1840,6 +1841,35 @@ values for the inactive program instances aren't printed. (In other cases,
they may have garbage values or be otherwise undefined.)
Assertions
----------
The ``ispc`` standard library includes a mechanism for adding ``assert()``
statements to ``ispc`` program code. Like ``assert()`` in C, the
``assert()`` function takes a single boolean expression as an argument. If
the expression evaluates to true at runtime, then a diagnostic error
message printed and the ``abort()`` function is called.
When called with a ``varying`` quantity, an assertion triggers if the
expression evaluates to true for any any of the executing program instances
at the point where it is called. Thus, given code like:
::
int x = programIndex - 2; // (-2, -1, 0, ... )
if (x > 0)
assert(x > 0);
The ``assert()`` statement will not trigger, since the condition isn't true
for any of the executing program instances at that point. (If this
``assert()`` statement was outside of this ``if``, then it would of course
trigger.)
To disable all of the assertions in a file that is being compiled (e.g.,
for an optimized release build), use the ``--opt=disable-assertions``
command-line argument.
Cross-Program Instance Operations
---------------------------------

View File

@@ -283,6 +283,7 @@ Opt::Opt() {
fastMath = false;
fastMaskedVload = false;
unrollLoops = true;
disableAsserts = false;
disableBlendedMaskedStores = false;
disableCoherentControlFlow = false;
disableUniformControlFlow = false;

5
ispc.h
View File

@@ -226,6 +226,10 @@ struct Opt {
it will make sense. */
bool unrollLoops;
/** Indicates whether assert() statements should be ignored (for
performance in the generated code). */
bool disableAsserts;
/** On targets that don't have a masked store instruction but do have a
blending instruction, by default, we simulate masked stores by
loading the old value, blending, and storing the result. This can
@@ -365,6 +369,7 @@ enum {
COST_TYPECAST_SIMPLE = 1,
COST_UNIFORM_LOOP = 4,
COST_VARYING_LOOP = 6,
COST_ASSERT = 8,
CHECK_MASK_AT_FUNCTION_START_COST = 16,
PREDICATE_SAFE_IF_STATEMENT_COST = 6,

1
lex.ll
View File

@@ -78,6 +78,7 @@ ZO_SWIZZLE ([01]+[w-z]+)+|([01]+[rgba]+)+|([01]+[uv]+)+
"/*" { lCComment(yylloc); }
"//" { lCppComment(yylloc); }
__assert { return TOKEN_ASSERT; }
bool { return TOKEN_BOOL; }
break { return TOKEN_BREAK; }
case { return TOKEN_CASE; }

View File

@@ -83,6 +83,7 @@ static void usage(int ret) {
printf(" [-o <name>/--outfile=<name>]\tOutput filename (may be \"-\" for standard output)\n");
printf(" [-O0/-O1]\t\t\t\tSet optimization level (-O1 is default)\n");
printf(" [--opt=<option>]\t\t\tSet optimization option\n");
printf(" disable-assertions\t\tRemove assertion statements from final code.\n");
printf(" disable-loop-unroll\t\tDisable loop unrolling.\n");
printf(" fast-masked-vload\t\tFaster masked vector loads on SSE (may go past end of array)\n");
printf(" fast-math\t\t\tPerform non-IEEE-compliant optimizations of numeric expressions\n");
@@ -246,6 +247,8 @@ int main(int Argc, char *Argv[]) {
g->opt.fastMath = true;
else if (!strcmp(opt, "fast-masked-vload"))
g->opt.fastMaskedVload = true;
else if (!strcmp(opt, "disable-assertions"))
g->opt.disableAsserts = true;
else if (!strcmp(opt, "disable-loop-unroll"))
g->opt.unrollLoops = false;

View File

@@ -928,6 +928,13 @@ Module::execPreprocessor(const char* infilename, llvm::raw_string_ostream* ostre
FATAL("Unhandled target ISA in preprocessor symbol definition");
}
if (g->includeStdlib) {
if (g->opt.disableAsserts)
opts.addMacroDef("assert(x)=");
else
opts.addMacroDef("assert(x)=__assert(#x, x)");
}
for (unsigned int i = 0; i < g->cppArgs.size(); ++i) {
// Sanity check--should really begin with -D
if (g->cppArgs[i].substr(0,2) == "-D") {

View File

@@ -161,8 +161,8 @@ static const char *lParamListTokens[] = {
%token TOKEN_CASE TOKEN_DEFAULT TOKEN_IF TOKEN_ELSE TOKEN_SWITCH
%token TOKEN_WHILE TOKEN_DO TOKEN_LAUNCH
%token TOKEN_FOR TOKEN_GOTO TOKEN_CONTINUE TOKEN_BREAK TOKEN_RETURN
%token TOKEN_CIF TOKEN_CDO TOKEN_CFOR TOKEN_CWHILE
%token TOKEN_CBREAK TOKEN_CCONTINUE TOKEN_CRETURN TOKEN_SYNC TOKEN_PRINT
%token TOKEN_CIF TOKEN_CDO TOKEN_CFOR TOKEN_CWHILE TOKEN_CBREAK
%token TOKEN_CCONTINUE TOKEN_CRETURN TOKEN_SYNC TOKEN_PRINT TOKEN_ASSERT
%type <expr> primary_expression postfix_expression
%type <expr> unary_expression cast_expression launch_expression
@@ -177,7 +177,7 @@ static const char *lParamListTokens[] = {
%type <stmt> statement labeled_statement compound_statement for_init_statement
%type <stmt> expression_statement selection_statement iteration_statement
%type <stmt> jump_statement statement_list declaration_statement print_statement
%type <stmt> sync_statement
%type <stmt> assert_statement sync_statement
%type <declaration> declaration parameter_declaration
%type <declarators> init_declarator_list
@@ -1036,6 +1036,7 @@ statement
| jump_statement
| declaration_statement
| print_statement
| assert_statement
| sync_statement
| error
{
@@ -1193,6 +1194,13 @@ print_statement
}
;
assert_statement
: TOKEN_ASSERT '(' string_constant ',' expression ')'
{
$$ = new AssertStmt(*$3, $5, @1);
}
;
translation_unit
: external_declaration
| translation_unit external_declaration

104
stmt.cpp
View File

@@ -755,6 +755,13 @@ lSafeToRunWithAllLanesOff(Stmt *stmt) {
if ((ps = dynamic_cast<PrintStmt *>(stmt)) != NULL)
return lSafeToRunWithAllLanesOff(ps->values);
AssertStmt *as;
if ((as = dynamic_cast<AssertStmt *>(stmt)) != NULL)
// While this is fine for varying tests, it's not going to be
// desirable to check an assert on a uniform variable if all of the
// lanes are off.
return false;
FATAL("Unexpected stmt type in lSafeToRunWithAllLanesOff()");
return false;
}
@@ -1808,3 +1815,100 @@ PrintStmt::EstimateCost() const {
}
///////////////////////////////////////////////////////////////////////////
// AssertStmt
AssertStmt::AssertStmt(const std::string &msg, Expr *e, SourcePos p)
: Stmt(p), message(msg), expr(e) {
}
void
AssertStmt::EmitCode(FunctionEmitContext *ctx) const {
if (expr == NULL)
return;
const Type *type = expr->GetType();
if (type == NULL)
return;
bool isUniform = type->IsUniformType();
// The actual functionality to do the check and then handle falure is
// done via a builtin written in bitcode in builtins.m4.
llvm::Function *assertFunc =
isUniform ? m->module->getFunction("__do_assert_uniform") :
m->module->getFunction("__do_assert_varying");
assert(assertFunc != NULL);
#ifdef ISPC_IS_WINDOWS
char errorString[2048];
if (sprintf_s(errorString, sizeof(errorString),
"%s(%d): Assertion failed: %s\n", pos.name,
pos.first_line, message.c_str()) == -1) {
Error(pos, "Fatal error in sprintf_s() call when generating assert "
"string.");
return;
}
#else
char *errorString;
if (asprintf(&errorString, "%s:%d:%d: Assertion failed: %s\n",
pos.name, pos.first_line, pos.first_column,
message.c_str()) == -1) {
Error(pos, "Fatal error when generating assert string: asprintf() "
"unable to allocate memory!");
return;
}
#endif
std::vector<llvm::Value *> args;
args.push_back(ctx->GetStringPtr(errorString));
args.push_back(expr->GetValue(ctx));
args.push_back(ctx->GetFullMask());
ctx->CallInst(assertFunc, args, "");
#ifndef ISPC_IS_WINDOWS
free(errorString);
#endif // !ISPC_IS_WINDOWS
}
void
AssertStmt::Print(int indent) const {
printf("%*cAssert Stmt (%s)", indent, ' ', message.c_str());
}
Stmt *
AssertStmt::Optimize() {
if (expr)
expr = expr->Optimize();
return this;
}
Stmt *
AssertStmt::TypeCheck() {
if (expr)
expr = expr->TypeCheck();
if (expr) {
const Type *type = expr->GetType();
if (type) {
bool isUniform = type->IsUniformType();
if (!type->IsNumericType() && !type->IsBoolType()) {
Error(expr->pos, "Type \"%s\" can't be converted to boolean for \"assert\".",
type->GetString().c_str());
return NULL;
}
expr = new TypeCastExpr(isUniform ? AtomicType::UniformBool :
AtomicType::VaryingBool,
expr, expr->pos);
}
}
return this;
}
int
AssertStmt::EstimateCost() const {
return (expr ? expr->EstimateCost() : 0) + COST_ASSERT;
}

25
stmt.h
View File

@@ -305,4 +305,29 @@ public:
};
/** @brief Representation of an assert statement in the program.
Like print() above, since we don't have strings as first-class types in
the language, we need to do some gymnastics to support it. Like
assert() in C, assert checks the given condition and prints an error
and calls abort if the condition fails. For varying conditions, the
assert triggers if it's true for any of the program instances.
*/
class AssertStmt : public Stmt {
public:
AssertStmt(const std::string &msg, Expr *e, SourcePos p);
void EmitCode(FunctionEmitContext *ctx) const;
void Print(int indent) const;
Stmt *Optimize();
Stmt *TypeCheck();
int EstimateCost() const;
/** Message to print if the assertion fails. */
const std::string message;
/** The expression to be evaluated (that is asserted to be true). */
Expr *expr;
};
#endif // ISPC_STMT_H

15
tests/assert-1.ispc Normal file
View File

@@ -0,0 +1,15 @@
export uniform int width() { return programCount; }
export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) {
float a = aFOO[programIndex];
// Neither of these should hit!
assert(a > 0);
assert(b == 5.);
RET[programIndex] = 2*a;
}
export void result(uniform float RET[]) {
RET[programIndex] = 2 + 2*programIndex;
}

17
tests/assert-2.ispc Normal file
View File

@@ -0,0 +1,17 @@
export uniform int width() { return programCount; }
export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) {
float a = aFOO[programIndex];
a -= 2;
if (a > 0)
// This shoudln't hit, given the if test above
assert(a > 0);
assert(b == 5.);
RET[programIndex] = a+2;
}
export void result(uniform float RET[]) {
RET[programIndex] = 1 + programIndex;
}

17
tests/assert-3.ispc Normal file
View File

@@ -0,0 +1,17 @@
export uniform int width() { return programCount; }
export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) {
float a = aFOO[programIndex];
a = -a;
if (a > 0)
// This shoudln't hit, given the if test above
assert(a > 0);
assert(b == 5.);
RET[programIndex] = -a;
}
export void result(uniform float RET[]) {
RET[programIndex] = 1 + programIndex;
}

16
tests/assert-4.ispc Normal file
View File

@@ -0,0 +1,16 @@
export uniform int width() { return programCount; }
export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) {
float a = aFOO[programIndex];
if (a < 0)
// Though this condition is false, the assert shouldn't hit, since
// given the if test above, all lanes should be off.
assert(b == 0.);
RET[programIndex] = a;
}
export void result(uniform float RET[]) {
RET[programIndex] = 1 + programIndex;
}