Add assert() statement support. Issue #106.
This commit is contained in:
37
builtins.m4
37
builtins.m4
@@ -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
|
||||
;;
|
||||
|
||||
@@ -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
|
||||
---------------------------------
|
||||
|
||||
|
||||
1
ispc.cpp
1
ispc.cpp
@@ -283,6 +283,7 @@ Opt::Opt() {
|
||||
fastMath = false;
|
||||
fastMaskedVload = false;
|
||||
unrollLoops = true;
|
||||
disableAsserts = false;
|
||||
disableBlendedMaskedStores = false;
|
||||
disableCoherentControlFlow = false;
|
||||
disableUniformControlFlow = false;
|
||||
|
||||
5
ispc.h
5
ispc.h
@@ -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
1
lex.ll
@@ -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; }
|
||||
|
||||
3
main.cpp
3
main.cpp
@@ -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;
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
14
parse.yy
14
parse.yy
@@ -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
104
stmt.cpp
@@ -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
25
stmt.h
@@ -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
15
tests/assert-1.ispc
Normal 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
17
tests/assert-2.ispc
Normal 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
17
tests/assert-3.ispc
Normal 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
16
tests/assert-4.ispc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user