Add foreach_unique iteration construct.

Idea via Ingo Wald / IVL compiler.
This commit is contained in:
Matt Pharr
2012-06-20 10:03:44 -07:00
parent fae47e0dfc
commit 3bc66136b2
17 changed files with 488 additions and 6 deletions

12
ast.cpp
View File

@@ -92,6 +92,7 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc,
DoStmt *dos;
ForStmt *fs;
ForeachStmt *fes;
ForeachUniqueStmt *fus;
CaseStmt *cs;
DefaultStmt *defs;
SwitchStmt *ss;
@@ -137,6 +138,10 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc,
postFunc, data);
fes->stmts = (Stmt *)WalkAST(fes->stmts, preFunc, postFunc, data);
}
else if ((fus = dynamic_cast<ForeachUniqueStmt *>(node)) != NULL) {
fus->expr = (Expr *)WalkAST(fus->expr, preFunc, postFunc, data);
fus->stmts = (Stmt *)WalkAST(fus->stmts, preFunc, postFunc, data);
}
else if ((cs = dynamic_cast<CaseStmt *>(node)) != NULL)
cs->stmts = (Stmt *)WalkAST(cs->stmts, preFunc, postFunc, data);
else if ((defs = dynamic_cast<DefaultStmt *>(node)) != NULL)
@@ -385,12 +390,17 @@ lCheckAllOffSafety(ASTNode *node, void *data) {
return false;
}
if (dynamic_cast<ForeachStmt *>(node) != NULL) {
if (dynamic_cast<ForeachStmt *>(node) != NULL ||
dynamic_cast<ForeachUniqueStmt *>(node) != NULL) {
// foreach() statements also shouldn't be run with an all-off mask.
// Since they re-establish an 'all on' mask, this would be pretty
// unintuitive. (More generally, it's possibly a little strange to
// allow foreach() in the presence of any non-uniform control
// flow...)
//
// Similarly, the implementation foreach_unique assumes as a
// precondition that the mask won't be all off going into it, so
// we'll enforce that here...
*okPtr = false;
return false;
}

View File

@@ -106,6 +106,7 @@ Contents:
* `Conditional Statements: "if"`_
* `Conditional Statements: "switch"`_
* `Basic Iteration Statements: "for", "while", and "do"`_
* `Iteration over unique elements: "foreach_unique"`_
* `Unstructured Control Flow: "goto"`_
* `"Coherent" Control Flow Statements: "cif" and Friends`_
* `Parallel Iteration Statements: "foreach" and "foreach_tiled"`_
@@ -2372,6 +2373,44 @@ 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.
Iteration over unique elements: "foreach_unique"
------------------------------------------------
It can be useful to iterate over the elements of a varying variable,
processing the subsets of of them that have the same value together. For
example, consider a varying variable ``x`` that has the values ``{1, 2, 2,
1, 1, 0, 0, 0}``, where the program is running on a target with a gang size
of 8 program instances. Here, ``x`` has three unique values across the
program instances: ``0``, ``1``, and ``2``.
The ``foreach_unique`` looping construct allows us to iterate over these
unique values. In the code below, the ``foreach_unique`` loop body
executes once for each of the three unique values, with execution mask set
to match the program instances where the varying value matches the current
unique value being processed.
::
int x = ...; // assume {1, 2, 2, 1, 1, 0, 0, 0}
foreach_unique (val in x) {
extern void func(uniform int v);
func(val);
}
In the above, ``func()`` will be called three times, once with value 0,
once with value 1, and once with value 2. When it is called for value 0,
only the last three program instances will be executing, and so forth. The
order in which the loop executes for the unique values isn't defined.
The varying expression that provides the values to be iterated over is only
evaluated once, and it must be of an atomic type (``float``, ``int``,
etc.), an ``enum`` type, or a pointer type. The iteration variable ``val``
is a variable of ``const uniform`` type of the iteration type; it can't be
modified within the loop. Finally, ``break`` and ``return`` statements are
illegal within the loop body, but ``continue`` statemetns are allowed.
Unstructured Control Flow: "goto"
---------------------------------

9
lex.ll
View File

@@ -66,7 +66,8 @@ static int allTokens[] = {
TOKEN_CONST, TOKEN_CONTINUE, TOKEN_CRETURN, TOKEN_DEFAULT, TOKEN_DO,
TOKEN_DELETE, TOKEN_DOUBLE, TOKEN_ELSE, TOKEN_ENUM,
TOKEN_EXPORT, TOKEN_EXTERN, TOKEN_FALSE, TOKEN_FLOAT, TOKEN_FOR,
TOKEN_FOREACH, TOKEN_FOREACH_TILED, TOKEN_GOTO, TOKEN_IF, TOKEN_INLINE,
TOKEN_FOREACH, TOKEN_FOREACH_TILED, TOKEN_FOREACH_UNIQUE,
TOKEN_GOTO, TOKEN_IF, TOKEN_IN, TOKEN_INLINE,
TOKEN_INT, TOKEN_INT8, TOKEN_INT16, TOKEN_INT, TOKEN_INT64, TOKEN_LAUNCH,
TOKEN_NEW, TOKEN_NULL, TOKEN_PRINT, TOKEN_RETURN, TOKEN_SOA, TOKEN_SIGNED,
TOKEN_SIZEOF, TOKEN_STATIC, TOKEN_STRUCT, TOKEN_SWITCH, TOKEN_SYNC,
@@ -115,8 +116,10 @@ void ParserInit() {
tokenToName[TOKEN_FOR] = "for";
tokenToName[TOKEN_FOREACH] = "foreach";
tokenToName[TOKEN_FOREACH_TILED] = "foreach_tiled";
tokenToName[TOKEN_FOREACH_UNIQUE] = "foreach_unique";
tokenToName[TOKEN_GOTO] = "goto";
tokenToName[TOKEN_IF] = "if";
tokenToName[TOKEN_IN] = "in";
tokenToName[TOKEN_INLINE] = "inline";
tokenToName[TOKEN_INT] = "int";
tokenToName[TOKEN_INT8] = "int8";
@@ -223,9 +226,11 @@ void ParserInit() {
tokenNameRemap["TOKEN_FOR"] = "\'for\'";
tokenNameRemap["TOKEN_FOREACH"] = "\'foreach\'";
tokenNameRemap["TOKEN_FOREACH_TILED"] = "\'foreach_tiled\'";
tokenNameRemap["TOKEN_FOREACH_UNIQUE"] = "\'foreach_unique\'";
tokenNameRemap["TOKEN_GOTO"] = "\'goto\'";
tokenNameRemap["TOKEN_IDENTIFIER"] = "identifier";
tokenNameRemap["TOKEN_IF"] = "\'if\'";
tokenNameRemap["TOKEN_IN"] = "\'in\'";
tokenNameRemap["TOKEN_INLINE"] = "\'inline\'";
tokenNameRemap["TOKEN_INT"] = "\'int\'";
tokenNameRemap["TOKEN_INT8"] = "\'int8\'";
@@ -365,8 +370,10 @@ for { RT; return TOKEN_FOR; }
__foreach_active { RT; return TOKEN_FOREACH_ACTIVE; }
foreach { RT; return TOKEN_FOREACH; }
foreach_tiled { RT; return TOKEN_FOREACH_TILED; }
foreach_unique { RT; return TOKEN_FOREACH_UNIQUE; }
goto { RT; return TOKEN_GOTO; }
if { RT; return TOKEN_IF; }
in { RT; return TOKEN_IN; }
inline { RT; return TOKEN_INLINE; }
int { RT; return TOKEN_INT; }
int8 { RT; return TOKEN_INT8; }

View File

@@ -117,7 +117,8 @@ static const char *lBuiltinTokens[] = {
"assert", "bool", "break", "case", "cbreak", "ccontinue", "cdo",
"cfor", "cif", "cwhile", "const", "continue", "creturn", "default",
"do", "delete", "double", "else", "enum", "export", "extern", "false",
"float", "for", "foreach", "foreach_tiled", "goto", "if", "inline",
"float", "for", "foreach", "foreach_tiled", "foreach_unique",
"goto", "if", "in", "inline",
"int", "int8", "int16", "int32", "int64", "launch", "new", "NULL",
"print", "return", "signed", "sizeof", "static", "struct", "switch",
"sync", "task", "true", "typedef", "uniform", "unsigned", "varying",
@@ -185,7 +186,7 @@ struct ForeachDimension {
%token TOKEN_AND_OP TOKEN_OR_OP TOKEN_MUL_ASSIGN TOKEN_DIV_ASSIGN TOKEN_MOD_ASSIGN
%token TOKEN_ADD_ASSIGN TOKEN_SUB_ASSIGN TOKEN_LEFT_ASSIGN TOKEN_RIGHT_ASSIGN
%token TOKEN_AND_ASSIGN TOKEN_OR_ASSIGN TOKEN_XOR_ASSIGN
%token TOKEN_SIZEOF TOKEN_NEW TOKEN_DELETE
%token TOKEN_SIZEOF TOKEN_NEW TOKEN_DELETE TOKEN_IN
%token TOKEN_EXTERN TOKEN_EXPORT TOKEN_STATIC TOKEN_INLINE TOKEN_TASK TOKEN_DECLSPEC
%token TOKEN_UNIFORM TOKEN_VARYING TOKEN_TYPEDEF TOKEN_SOA
@@ -195,7 +196,7 @@ struct ForeachDimension {
%token TOKEN_CASE TOKEN_DEFAULT TOKEN_IF TOKEN_ELSE TOKEN_SWITCH
%token TOKEN_WHILE TOKEN_DO TOKEN_LAUNCH TOKEN_FOREACH TOKEN_FOREACH_TILED
%token TOKEN_FOREACH_ACTIVE TOKEN_DOTDOTDOT
%token TOKEN_FOREACH_UNIQUE TOKEN_FOREACH_ACTIVE TOKEN_DOTDOTDOT
%token TOKEN_FOR TOKEN_GOTO TOKEN_CONTINUE TOKEN_BREAK TOKEN_RETURN
%token TOKEN_CIF TOKEN_CDO TOKEN_CFOR TOKEN_CWHILE TOKEN_CBREAK
%token TOKEN_CCONTINUE TOKEN_CRETURN TOKEN_SYNC TOKEN_PRINT TOKEN_ASSERT
@@ -241,7 +242,9 @@ struct ForeachDimension {
%type <declSpecs> declaration_specifiers
%type <stringVal> string_constant
%type <constCharPtr> struct_or_union_name enum_identifier goto_identifier
%type <constCharPtr> struct_or_union_name enum_identifier goto_identifier
%type <constCharPtr> foreach_unique_identifier
%type <intVal> int_constant soa_width_specifier rate_qualified_new
%type <foreachDimension> foreach_dimension_specifier
@@ -1703,6 +1706,14 @@ foreach_dimension_list
}
;
foreach_unique_scope
: TOKEN_FOREACH_UNIQUE { m->symbolTable->PushScope(); }
;
foreach_unique_identifier
: TOKEN_IDENTIFIER { $$ = yylval.stringVal->c_str(); }
;
iteration_statement
: TOKEN_WHILE '(' expression ')' statement
{ $$ = new ForStmt(NULL, $3, NULL, $5, false, @1); }
@@ -1795,6 +1806,24 @@ iteration_statement
$$ = CreateForeachActiveStmt($3, $6, Union(@1, @4));
m->symbolTable->PopScope();
}
| foreach_unique_scope '(' foreach_unique_identifier TOKEN_IN
expression ')'
{
Expr *expr = $5;
const Type *type;
if (expr != NULL &&
(expr = TypeCheck(expr)) != NULL &&
(type = expr->GetType()) != NULL) {
const Type *iterType = type->GetAsUniformType()->GetAsConstType();
Symbol *sym = new Symbol($3, @3, iterType);
m->symbolTable->AddVariable(sym);
}
}
statement
{
$$ = new ForeachUniqueStmt($3, $5, $8, @1);
m->symbolTable->PopScope();
}
;
goto_identifier

236
stmt.cpp
View File

@@ -1915,6 +1915,242 @@ ForeachStmt::Print(int indent) const {
}
///////////////////////////////////////////////////////////////////////////
// ForeachUniqueStmt
ForeachUniqueStmt::ForeachUniqueStmt(const char *iterName, Expr *e,
Stmt *s, SourcePos pos)
: Stmt(pos) {
sym = m->symbolTable->LookupVariable(iterName);
expr = e;
stmts = s;
}
void
ForeachUniqueStmt::EmitCode(FunctionEmitContext *ctx) const {
if (!ctx->GetCurrentBasicBlock())
return;
// First, allocate local storage for the symbol that we'll use for the
// uniform variable that holds the current unique value through each
// loop.
if (sym->type == NULL) {
Assert(m->errorCount > 0);
return;
}
llvm::Type *symType = sym->type->LLVMType(g->ctx);
if (symType == NULL) {
Assert(m->errorCount > 0);
return;
}
sym->storagePtr = ctx->AllocaInst(symType, sym->name.c_str());
ctx->SetDebugPos(pos);
ctx->EmitVariableDebugInfo(sym);
// The various basic blocks that we'll need in the below
llvm::BasicBlock *bbFindNext = ctx->CreateBasicBlock("foreach_find_next");
llvm::BasicBlock *bbBody = ctx->CreateBasicBlock("foreach_body");
llvm::BasicBlock *bbCheckForMore = ctx->CreateBasicBlock("foreach_check_for_more");
llvm::BasicBlock *bbDone = ctx->CreateBasicBlock("foreach_done");
// Prepare the FunctionEmitContext
ctx->StartScope();
// Save the old internal mask so that we can restore it at the end
llvm::Value *oldMask = ctx->GetInternalMask();
// Now, *maskBitsPtr will maintain a bitmask for the lanes that remain
// to be processed by a pass through the foreach_unique loop body. It
// starts out with the full execution mask (which should never be all
// off going in to this)...
llvm::Value *oldFullMask = ctx->GetFullMask();
llvm::Value *maskBitsPtr = ctx->AllocaInst(LLVMTypes::Int64Type, "mask_bits");
llvm::Value *movmsk = ctx->LaneMask(oldFullMask);
ctx->StoreInst(movmsk, maskBitsPtr);
// Officially start the loop; as far as the FunctionEmitContext is
// concerned, this can be handled the same way as a regular foreach
// loop (continue allowed but not break and return, etc.)
ctx->StartForeach();
ctx->SetContinueTarget(bbCheckForMore);
// Evaluate the varying expression we're iterating over just once.
llvm::Value *exprValue = expr->GetValue(ctx);
// And we'll store its value into locally-allocated storage, for ease
// of indexing over it with non-compile-time-constant indices.
const Type *exprType;
llvm::VectorType *llvmExprType;
if (exprValue == NULL ||
(exprType = expr->GetType()) == NULL ||
(llvmExprType =
llvm::dyn_cast<llvm::VectorType>(exprValue->getType())) == NULL) {
Assert(m->errorCount > 0);
return;
}
ctx->SetDebugPos(pos);
const Type *exprPtrType = PointerType::GetUniform(exprType);
llvm::Value *exprMem = ctx->AllocaInst(llvmExprType, "expr_mem");
ctx->StoreInst(exprValue, exprMem);
// Onward to find the first set of lanes to run the loop for
ctx->BranchInst(bbFindNext);
ctx->SetCurrentBasicBlock(bbFindNext); {
// Load the bitmask of the lanes left to be processed
llvm::Value *remainingBits = ctx->LoadInst(maskBitsPtr, "remaining_bits");
// Find the index of the first set bit in the mask
llvm::Function *ctlzFunc =
m->module->getFunction("__count_trailing_zeros_i64");
Assert(ctlzFunc != NULL);
llvm::Value *firstSet = ctx->CallInst(ctlzFunc, NULL, remainingBits,
"first_set");
// And load the corresponding element value from the temporary
// memory storing the value of the varying expr.
llvm::Value *uniqueValuePtr =
ctx->GetElementPtrInst(exprMem, LLVMInt64(0), firstSet, exprPtrType,
"unique_index_ptr");
llvm::Value *uniqueValue = ctx->LoadInst(uniqueValuePtr, "unique_value");
// If it's a varying pointer type, need to convert from the int
// type we store in the vector to the actual pointer type
if (llvm::dyn_cast<llvm::PointerType>(symType) != NULL)
uniqueValue = ctx->IntToPtrInst(uniqueValue, symType);
// Store that value in sym's storage so that the iteration variable
// has the right value inside the loop body
ctx->StoreInst(uniqueValue, sym->storagePtr);
// Set the execution mask so that it's on for any lane that a) was
// running at the start of the foreach loop, and b) where that
// lane's value of the varying expression is the same as the value
// we've selected to process this time through--i.e.:
// oldMask & (smear(element) == exprValue)
llvm::Value *uniqueSmear = ctx->SmearUniform(uniqueValue, "unique_semar");
llvm::Value *matchingLanes = NULL;
if (uniqueValue->getType()->isFloatingPointTy())
matchingLanes =
ctx->CmpInst(llvm::Instruction::FCmp, llvm::CmpInst::FCMP_OEQ,
uniqueSmear, exprValue, "matching_lanes");
else
matchingLanes =
ctx->CmpInst(llvm::Instruction::ICmp, llvm::CmpInst::ICMP_EQ,
uniqueSmear, exprValue, "matching_lanes");
matchingLanes = ctx->I1VecToBoolVec(matchingLanes);
llvm::Value *loopMask =
ctx->BinaryOperator(llvm::Instruction::And, oldMask, matchingLanes,
"foreach_unique_loop_mask");
ctx->SetInternalMask(loopMask);
// Also update the bitvector of lanes left to process in subsequent
// loop iterations:
// remainingBits &= ~movmsk(current mask)
llvm::Value *loopMaskMM = ctx->LaneMask(loopMask);
llvm::Value *notLoopMaskMM = ctx->NotOperator(loopMaskMM);
llvm::Value *newRemaining =
ctx->BinaryOperator(llvm::Instruction::And, remainingBits,
notLoopMaskMM, "new_remaining");
ctx->StoreInst(newRemaining, maskBitsPtr);
// and onward...
ctx->BranchInst(bbBody);
}
ctx->SetCurrentBasicBlock(bbBody); {
// Run the code in the body of the loop. This is easy now.
if (stmts)
stmts->EmitCode(ctx);
Assert(ctx->GetCurrentBasicBlock() != NULL);
ctx->BranchInst(bbCheckForMore);
}
ctx->SetCurrentBasicBlock(bbCheckForMore); {
// At the end of the loop body (either due to running the
// statements normally, or a continue statement in the middle of
// the loop that jumps to the end, see if there are any lanes left
// to be processed.
llvm::Value *remainingBits = ctx->LoadInst(maskBitsPtr, "remaining_bits");
llvm::Value *nonZero =
ctx->CmpInst(llvm::Instruction::ICmp, llvm::CmpInst::ICMP_NE,
remainingBits, LLVMInt64(0), "remaining_ne_zero");
ctx->BranchInst(bbFindNext, bbDone, nonZero);
}
ctx->SetCurrentBasicBlock(bbDone);
ctx->SetInternalMask(oldMask);
ctx->EndForeach();
ctx->EndScope();
}
void
ForeachUniqueStmt::Print(int indent) const {
printf("%*cForeach_unique Stmt", indent, ' ');
pos.Print();
printf("\n");
printf("%*cIter symbol: ", indent+4, ' ');
if (sym != NULL) {
printf("%s", sym->name.c_str());
if (sym->type != NULL)
printf(" %s", sym->type->GetString().c_str());
}
else
printf("NULL");
printf("\n");
printf("%*cIter expr: ", indent+4, ' ');
if (expr != NULL)
expr->Print();
else
printf("NULL");
printf("\n");
printf("%*cStmts:\n", indent+4, ' ');
if (stmts != NULL)
stmts->Print(indent+8);
else
printf("NULL");
printf("\n");
}
Stmt *
ForeachUniqueStmt::TypeCheck() {
const Type *type;
if (sym == NULL || expr == NULL || (type = expr->GetType()) == NULL)
return NULL;
if (type->IsVaryingType() == false) {
Error(expr->pos, "Iteration domain type in \"foreach_tiled\" loop "
"must be \"varying\" type, not \"%s\".",
type->GetString().c_str());
return false;
}
if (Type::IsBasicType(type) == false) {
Error(expr->pos, "Iteration domain type in \"foreach_tiled\" loop "
"must be an atomic, pointer, or enum type, not \"%s\".",
type->GetString().c_str());
return false;
}
return this;
}
int
ForeachUniqueStmt::EstimateCost() const {
return COST_VARYING_LOOP;
}
///////////////////////////////////////////////////////////////////////////
// CaseStmt

19
stmt.h
View File

@@ -260,6 +260,25 @@ public:
};
/** Parallel iteration over each unique value in the given (varying)
expression.
*/
class ForeachUniqueStmt : public Stmt {
public:
ForeachUniqueStmt(const char *iterName, Expr *expr, Stmt *stmts,
SourcePos pos);
void EmitCode(FunctionEmitContext *ctx) const;
void Print(int indent) const;
Stmt *TypeCheck();
int EstimateCost() const;
Symbol *sym;
Expr *expr;
Stmt *stmts;
};
/** @brief Statement implementation for a 'return' or 'coherent' return
statement in the program. */

View File

@@ -0,0 +1,14 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
float a = aFOO[programIndex];
foreach_unique (ua in a)
++count;
RET[programIndex] = count;
}
export void result(uniform float RET[]) {
RET[programIndex] = programCount;
}

View File

@@ -0,0 +1,15 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
float a = aFOO[programIndex];
// make sure a++ only evaluated once
foreach_unique (ua in a++)
++count;
RET[programIndex] = a;
}
export void result(uniform float RET[]) {
RET[programIndex] = programIndex + 2;
}

View File

@@ -0,0 +1,15 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
float a = aFOO[programIndex];
uniform float sum = 0;
foreach_unique (ua in a)
sum += ua;
RET[programIndex] = sum;
}
export void result(uniform float RET[]) {
RET[programIndex] = reduce_add(programIndex+1);
}

View File

@@ -0,0 +1,14 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
int a = aFOO[programIndex];
foreach_unique (ua in a & 1)
++count;
RET[programIndex] = count;
}
export void result(uniform float RET[]) {
RET[programIndex] = 2;
}

View File

@@ -0,0 +1,15 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
int a = aFOO[programIndex];
if (a & 1)
foreach_unique (ua in a & 1)
++count;
RET[programIndex] = count;
}
export void result(uniform float RET[]) {
RET[programIndex] = 1;
}

View File

@@ -0,0 +1,16 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
uniform int count = 0;
float * ptr = &aFOO[programIndex];
foreach_unique (p in ptr)
++count;
RET[programIndex] = count;
}
export void result(uniform float RET[]) {
RET[programIndex] = programCount;
}

View File

@@ -0,0 +1,13 @@
export uniform int width() { return programCount; }
export void f_f(uniform float RET[], uniform float aFOO[]) {
RET[programIndex] = aFOO[programIndex];
foreach_unique (p in programIndex & 1)
RET[programIndex] = p;
}
export void result(uniform float RET[]) {
RET[programIndex] = programIndex & 1;
}

View File

@@ -0,0 +1,12 @@
// Iteration domain type in "foreach_tiled" loop must be an atomic, pointer, or enum type
struct Point { float x, y; };
uniform int foo(Point p) {
uniform int count = 0;
foreach_unique (pt in p)
++count;
return count;
}

View File

@@ -0,0 +1,8 @@
// Can't assign to type "const uniform int32"
void foo(int x) {
foreach_unique (u in x)
++u;
}

View File

@@ -0,0 +1,10 @@
// Error: "break" statement is illegal outside of for/while/do loops
void foo(int x) {
foreach_unique (u in x) {
if (u == 0)
break;
}
}

View File

@@ -0,0 +1,10 @@
// Error: "return" statement is illegal inside a "foreach" loop
void foo(int x) {
foreach_unique (u in x) {
if (u == 0)
return;
}
}