diff --git a/ast.cpp b/ast.cpp index 746bc0ec..5eaddeb3 100644 --- a/ast.cpp +++ b/ast.cpp @@ -98,6 +98,7 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, StmtList *sl; PrintStmt *ps; AssertStmt *as; + DeleteStmt *dels; if ((es = dynamic_cast(node)) != NULL) es->expr = (Expr *)WalkAST(es->expr, preFunc, postFunc, data); @@ -160,6 +161,8 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, ps->values = (Expr *)WalkAST(ps->values, preFunc, postFunc, data); else if ((as = dynamic_cast(node)) != NULL) as->expr = (Expr *)WalkAST(as->expr, preFunc, postFunc, data); + else if ((dels = dynamic_cast(node)) != NULL) + dels->expr = (Expr *)WalkAST(dels->expr, preFunc, postFunc, data); else FATAL("Unhandled statement type in WalkAST()"); } @@ -180,6 +183,7 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, DereferenceExpr *dre; SizeOfExpr *soe; AddressOfExpr *aoe; + NewExpr *newe; if ((ue = dynamic_cast(node)) != NULL) ue->expr = (Expr *)WalkAST(ue->expr, preFunc, postFunc, data); @@ -223,6 +227,12 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc, soe->expr = (Expr *)WalkAST(soe->expr, preFunc, postFunc, data); else if ((aoe = dynamic_cast(node)) != NULL) aoe->expr = (Expr *)WalkAST(aoe->expr, preFunc, postFunc, data); + else if ((newe = dynamic_cast(node)) != NULL) { + newe->countExpr = (Expr *)WalkAST(newe->countExpr, preFunc, + postFunc, data); + newe->initExpr = (Expr *)WalkAST(newe->initExpr, preFunc, + postFunc, data); + } else if (dynamic_cast(node) != NULL || dynamic_cast(node) != NULL || dynamic_cast(node) != NULL || diff --git a/builtins.cpp b/builtins.cpp index 76ebdfa7..8c3631a2 100644 --- a/builtins.cpp +++ b/builtins.cpp @@ -391,6 +391,8 @@ lSetInternalFunctions(llvm::Module *module) { "__count_trailing_zeros_i64", "__count_leading_zeros_i32", "__count_leading_zeros_i64", + "__delete_uniform", + "__delete_varying", "__do_assert_uniform", "__do_assert_varying", "__do_print", @@ -449,6 +451,9 @@ lSetInternalFunctions(llvm::Module *module) { "__min_varying_uint32", "__min_varying_uint64", "__movmsk", + "__new_uniform", + "__new_varying32", + "__new_varying64", "__num_cores", "__packed_load_active", "__packed_store_active", diff --git a/builtins/util.m4 b/builtins/util.m4 index 4e3fc85b..563ee3e9 100644 --- a/builtins/util.m4 +++ b/builtins/util.m4 @@ -1805,6 +1805,65 @@ ok: ret void } +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; new/delete + +declare i8 * @malloc(i64) +declare void @free(i8 *) + +define i8 * @__new_uniform(i64 %size) { + %a = call i8 * @malloc(i64 %size) + ret i8 * %a +} + +define @__new_varying32( %size, %mask) { + %ret = alloca + store zeroinitializer, * %ret + %ret64 = bitcast * %ret to i64 * + + per_lane(WIDTH, %mask, ` + %sz_LANE_ID = extractelement %size, i32 LANE + %sz64_LANE_ID = zext i32 %sz_LANE_ID to i64 + %ptr_LANE_ID = call i8 * @malloc(i64 %sz64_LANE_ID) + %ptr_int_LANE_ID = ptrtoint i8 * %ptr_LANE_ID to i64 + %store_LANE_ID = getelementptr i64 * %ret64, i32 LANE + store i64 %ptr_int_LANE_ID, i64 * %store_LANE_ID') + + %r = load * %ret + ret %r +} + +define @__new_varying64( %size, %mask) { + %ret = alloca + store zeroinitializer, * %ret + %ret64 = bitcast * %ret to i64 * + + per_lane(WIDTH, %mask, ` + %sz_LANE_ID = extractelement %size, i32 LANE + %ptr_LANE_ID = call i8 * @malloc(i64 %sz_LANE_ID) + %ptr_int_LANE_ID = ptrtoint i8 * %ptr_LANE_ID to i64 + %store_LANE_ID = getelementptr i64 * %ret64, i32 LANE + store i64 %ptr_int_LANE_ID, i64 * %store_LANE_ID') + + %r = load * %ret + ret %r +} + +define void @__delete_uniform(i8 * %ptr) { + call void @free(i8 * %ptr) + ret void +} + +define void @__delete_varying( %ptr, %mask) { + per_lane(WIDTH, %mask, ` + %iptr_LANE_ID = extractelement %ptr, i32 LANE + %ptr_LANE_ID = inttoptr i64 %iptr_LANE_ID to i8 * + call void @free(i8 * %ptr_LANE_ID) + ') + ret void +} + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; read hw clock diff --git a/ctx.cpp b/ctx.cpp index 0a7dd6d0..8ac64fe5 100644 --- a/ctx.cpp +++ b/ctx.cpp @@ -2923,7 +2923,7 @@ FunctionEmitContext::SyncInst() { /** When we gathering from or scattering to a varying atomic type, we need - to add an appropraite offset to the final address for each lane right + to add an appropriate offset to the final address for each lane right before we use it. Given a varying pointer we're about to use and its type, this function determines whether these offsets are needed and returns an updated pointer that incorporates these offsets if needed. diff --git a/docs/ispc.rst b/docs/ispc.rst index c0dcd6df..5613cfa1 100644 --- a/docs/ispc.rst +++ b/docs/ispc.rst @@ -96,6 +96,9 @@ Contents: + `Declarations and Initializers`_ + `Expressions`_ + + * `Dynamic Memory Allocation`_ + + `Control Flow`_ * `Conditional Statements: "if"`_ @@ -1162,6 +1165,7 @@ in C: * The ``inline`` qualifier to indicate that a function should be inlined * Function overloading by parameter type * Hexadecimal floating-point constants +* Dynamic memory allocation with ``new`` and ``delete``. ``ispc`` also adds a number of new features that aren't in C89, C99, or C++: @@ -1966,18 +1970,123 @@ operator also work as expected. fp->b = 1; +Dynamic Memory Allocation +------------------------- + +``ispc`` programs can dynamically allocate (and free) memory, using syntax +based on C++'s ``new`` and ``delete`` operators: + +:: + + int count = ...; + int *ptr = new uniform int[count]; + // use ptr... + delete[] ptr; + +In the above code, each program instance allocates its own ``count`-sized +array of ``uniform int`` values, uses that memory, and then deallocates +that memory. Uses of ``new`` and ``delete`` in ``ispc`` programs are +serviced by corresponding calls the system C library's ``malloc()`` and +``free()`` functions. + +After a pointer has been deleted, it is illegal to access the memory it +points to. However, note that deletion happens on a per-program-instance +basis. In other words, consider the following code: + +:: + + int *ptr = new uniform int[count]; + // use ptr + if (count > 1000) + delete[] ptr; + // ... + +Here, the program instances where ``count`` is greater than 1000 have +deleted the dynamically allocated memory pointed to by ``ptr``, but the +other program instances have not. As such, it's illegal for the former set +of program instances to access ``*ptr``, but it's perfectly fine for the +latter set to continue to use the memory ``ptr`` points to. Note that it +is illegal to delete a pointer value returned by ``new`` more than one +time. + +Sometimes, it's useful to be able to do a single allocation for the entire +gang of program instances. A ``new`` statement can be qualified with +``uniform`` to indicate a single memory allocation: + +:: + + float * uniform ptr = uniform new float[10]; + +While a regular call to ``new`` returns a ``varying`` pointer (i.e. a +distinct pointer to separately-allocated memory for each program instance), +a ``uniform new`` performs a single allocation and returns a ``uniform`` +pointer. + +When using ``uniform new``, it's important to be aware of a subtlety; if +the returned pointer is stored in a varying pointer variable (as may be +appropriate and useful for the particular program being written), then the +varying pointer may inadvertently be passed to a subsequent ``delete`` +statement, which is an error: effectively + +:: + + float *ptr = uniform new float[10]; + // use ptr... + delete ptr; // ERROR: varying pointer is deleted + +In this case, ``ptr`` will be deleted multiple times, once for each +executing program instance, which is an error (unless it happens that only +a single program instance is active in the above code.) + +When using ``new`` statements, it's important to make an appropriate choice +of ``uniform`` or ``varying`` (as always, the default), for both the +``new`` operator itself as well as the type of data being allocated, based +on the program's needs. Consider the following four memory allocations: + +:: + + uniform float * uniform p1 = uniform new uniform float[10]; + float * uniform p2 = uniform new float[10]; + uniform float * p3 = new uniform float[10]; + float * p4 = new float[10]; + +Assuming that a ``float`` is 4 bytes in memory and if the gang size is 8 +program instances, then the first allocation represents a single allocation +of 40 bytes, the second is a single allocation of 8*4*10 = 320 bytes, the +third is 8 allocations of 40 bytes, and the last performs 8 allocations of +80 bytes each. + +Note in particular that varying allocations of varying data types are rarely +desirable in practice. In that case, each program instance is performing a +separate allocation of ``varying float`` memory. In this case, it's likely +that the program instances will only access a single element of each +``varying float``, which is wasteful. + +Although ``ispc`` doesn't support constructors or destructors like C++, it +is possible to provide initializer values with ``new`` statements: + +:: + + struct Point { float x, y, z; }; + Point *pptr = new Point(10, 20, 30); + +Here for example, the "x" element of the returned ``Point`` is initialized +to have the value 10 and so forth. In general, the rules for how +initializer values provided in ``new`` statements are used to initialize +complex data types follow the same rules as initializers for variables +described in `Declarations and Initializers`_. + Control Flow ------------ ``ispc`` supports most of C's control flow constructs, including ``if``, -``for``, ``while``, ``do``. It also supports variants of C's control flow +``switch``, ``for``, ``while``, ``do``. It has limited support for +``goto``, detailed below. It also supports variants of C's control flow constructs that provide hints about the expected runtime coherence of the control flow at that statement. It also provides parallel looping constructs, ``foreach`` and ``foreach_tiled``, all of which will be detailed in this section. -``ispc`` does not currently support ``switch`` statements or ``goto``. - Conditional Statements: "if" ---------------------------- diff --git a/expr.cpp b/expr.cpp index 725a1edc..b9872780 100644 --- a/expr.cpp +++ b/expr.cpp @@ -504,6 +504,153 @@ TypeConvertExpr(Expr *expr, const Type *toType, const char *errorMsgBase) { } +bool +PossiblyResolveFunctionOverloads(Expr *expr, const Type *type) { + FunctionSymbolExpr *fse = NULL; + const FunctionType *funcType = NULL; + if (dynamic_cast(type) != NULL && + (funcType = dynamic_cast(type->GetBaseType())) && + (fse = dynamic_cast(expr)) != NULL) { + // We're initializing a function pointer with a function symbol, + // which in turn may represent an overloaded function. So we need + // to try to resolve the overload based on the type of the symbol + // we're initializing here. + std::vector paramTypes; + for (int i = 0; i < funcType->GetNumParameters(); ++i) + paramTypes.push_back(funcType->GetParameterType(i)); + + if (fse->ResolveOverloads(expr->pos, paramTypes) == false) + return false; + } + return true; +} + + + +/** Utility routine that emits code to initialize a symbol given an + initializer expression. + + @param lvalue Memory location of storage for the symbol's data + @param symName Name of symbol (used in error messages) + @param symType Type of variable being initialized + @param initExpr Expression for the initializer + @param ctx FunctionEmitContext to use for generating instructions + @param pos Source file position of the variable being initialized +*/ +void +InitSymbol(llvm::Value *lvalue, const Type *symType, Expr *initExpr, + FunctionEmitContext *ctx, SourcePos pos) { + if (initExpr == NULL) + // leave it uninitialized + return; + + // If the initializer is a straight up expression that isn't an + // ExprList, then we'll see if we can type convert it to the type of + // the variable. + if (dynamic_cast(initExpr) == NULL) { + if (PossiblyResolveFunctionOverloads(initExpr, symType) == false) + return; + initExpr = TypeConvertExpr(initExpr, symType, "initializer"); + + if (initExpr != NULL) { + llvm::Value *initializerValue = initExpr->GetValue(ctx); + if (initializerValue != NULL) + // Bingo; store the value in the variable's storage + ctx->StoreInst(initializerValue, lvalue); + return; + } + } + + // Atomic types and enums can't be initialized with { ... } initializer + // expressions, so print an error and return if that's what we've got + // here.. + if (dynamic_cast(symType) != NULL || + dynamic_cast(symType) != NULL || + dynamic_cast(symType) != NULL) { + ExprList *elist = dynamic_cast(initExpr); + if (elist != NULL) { + if (elist->exprs.size() == 1) + InitSymbol(lvalue, symType, elist->exprs[0], ctx, pos); + else + Error(initExpr->pos, "Expression list initializers can't be used " + "with type \"%s\".", symType->GetString().c_str()); + } + return; + } + + const ReferenceType *rt = dynamic_cast(symType); + if (rt) { + if (!Type::Equal(initExpr->GetType(), rt)) { + Error(initExpr->pos, "Initializer for reference type \"%s\" must have same " + "reference type itself. \"%s\" is incompatible.", + rt->GetString().c_str(), initExpr->GetType()->GetString().c_str()); + return; + } + + llvm::Value *initializerValue = initExpr->GetValue(ctx); + if (initializerValue) + ctx->StoreInst(initializerValue, lvalue); + return; + } + + // There are two cases for initializing structs, arrays and vectors; + // either a single initializer may be provided (float foo[3] = 0;), in + // which case all of the elements are initialized to the given value, + // or an initializer list may be provided (float foo[3] = { 1,2,3 }), + // in which case the elements are initialized with the corresponding + // values. + const CollectionType *collectionType = + dynamic_cast(symType); + if (collectionType != NULL) { + std::string name; + if (dynamic_cast(symType) != NULL) + name = "struct"; + else if (dynamic_cast(symType) != NULL) + name = "array"; + else if (dynamic_cast(symType) != NULL) + name = "vector"; + else + FATAL("Unexpected CollectionType in InitSymbol()"); + + ExprList *exprList = dynamic_cast(initExpr); + if (exprList != NULL) { + // The { ... } case; make sure we have the same number of + // expressions in the ExprList as we have struct members + int nInits = exprList->exprs.size(); + if (nInits != collectionType->GetElementCount()) { + Error(initExpr->pos, "Initializer for %s type \"%s\" requires " + "%d values; %d provided.", name.c_str(), + symType->GetString().c_str(), + collectionType->GetElementCount(), nInits); + return; + } + + // Initialize each element with the corresponding value from + // the ExprList + for (int i = 0; i < nInits; ++i) { + llvm::Value *ep; + if (dynamic_cast(symType) != NULL) + ep = ctx->AddElementOffset(lvalue, i, NULL, "element"); + else + ep = ctx->GetElementPtrInst(lvalue, LLVMInt32(0), LLVMInt32(i), + PointerType::GetUniform(collectionType->GetElementType(i)), + "gep"); + + InitSymbol(ep, collectionType->GetElementType(i), + exprList->exprs[i], ctx, pos); + } + } + else + Error(initExpr->pos, "Can't assign type \"%s\" to \"%s\".", + initExpr->GetType()->GetString().c_str(), + collectionType->GetString().c_str()); + return; + } + + FATAL("Unexpected Type in InitSymbol()"); +} + + /////////////////////////////////////////////////////////////////////////// /** Given an atomic or vector type, this returns a boolean type with the @@ -6527,3 +6674,211 @@ NullPointerExpr::EstimateCost() const { return 0; } + +/////////////////////////////////////////////////////////////////////////// +// NewExpr + +NewExpr::NewExpr(int typeQual, const Type *t, Expr *init, Expr *count, + SourcePos tqPos, SourcePos p) + : Expr(p) { + allocType = t; + if (allocType != NULL && allocType->HasUnboundVariability()) + allocType = allocType->ResolveUnboundVariability(Type::Varying); + + initExpr = init; + countExpr = count; + + /* (The below cases actually should be impossible, since the parser + doesn't allow more than a single type qualifier before a "new".) */ + if ((typeQual & ~(TYPEQUAL_UNIFORM | TYPEQUAL_VARYING)) != 0) { + Error(tqPos, "Illegal type qualifiers in \"new\" expression (only " + "\"uniform\" and \"varying\" are allowed."); + isVarying = false; + } + else if ((typeQual & TYPEQUAL_UNIFORM) != 0 && + (typeQual & TYPEQUAL_VARYING) != 0) { + Error(tqPos, "Illegal to provide both \"uniform\" and \"varying\" " + "qualifiers to \"new\" expression."); + isVarying = false; + } + else + // If no type qualifier is given before the 'new', treat it as a + // varying new. + isVarying = (typeQual == 0) || (typeQual & TYPEQUAL_VARYING); +} + + +llvm::Value * +NewExpr::GetValue(FunctionEmitContext *ctx) const { + bool do32Bit = (g->target.is32Bit || g->opt.force32BitAddressing); + + // Determine how many elements we need to allocate. Note that this + // will be a varying value if this is a varying new. + llvm::Value *countValue; + if (countExpr != NULL) { + countValue = countExpr->GetValue(ctx); + if (countValue == NULL) { + Assert(m->errorCount > 0); + return NULL; + } + } + else { + if (isVarying) { + if (do32Bit) countValue = LLVMInt32Vector(1); + else countValue = LLVMInt64Vector(1); + } + else { + if (do32Bit) countValue = LLVMInt32(1); + else countValue = LLVMInt64(1); + } + } + + // Compute the total amount of memory to allocate, allocSize, as the + // product of the number of elements to allocate and the size of a + // single element. + llvm::Value *eltSize = g->target.SizeOf(allocType->LLVMType(g->ctx), + ctx->GetCurrentBasicBlock()); + if (isVarying) + eltSize = ctx->SmearUniform(eltSize, "smear_size"); + llvm::Value *allocSize = ctx->BinaryOperator(llvm::Instruction::Mul, countValue, + eltSize, "alloc_size"); + + // Determine which allocation builtin function to call: uniform or + // varying, and taking 32-bit or 64-bit allocation counts. + llvm::Function *func; + if (isVarying) { + if (do32Bit) + func = m->module->getFunction("__new_varying32"); + else + func = m->module->getFunction("__new_varying64"); + } + else { + if (allocSize->getType() != LLVMTypes::Int64Type) + allocSize = ctx->SExtInst(allocSize, LLVMTypes::Int64Type, + "alloc_size64"); + func = m->module->getFunction("__new_uniform"); + } + Assert(func != NULL); + + // Make the call for the the actual allocation. + llvm::Value *ptrValue = ctx->CallInst(func, NULL, allocSize, "new"); + + // Now handle initializers and returning the right type for the result. + const Type *retType = GetType(); + if (retType == NULL) + return NULL; + if (isVarying) { + if (g->target.is32Bit) + // Convert i64 vector values to i32 if we are compiling to a + // 32-bit target. + ptrValue = ctx->TruncInst(ptrValue, LLVMTypes::VoidPointerVectorType, + "ptr_to_32bit"); + + if (initExpr != NULL) { + // If we have an initializer expression, emit code that checks + // to see if each lane is active and if so, runs the code to do + // the initialization. Note that we're we're taking advantage + // of the fact that the __new_varying*() functions are + // implemented to return NULL for program instances that aren't + // executing; more generally, we should be using the current + // execution mask for this... + for (int i = 0; i < g->target.vectorWidth; ++i) { + llvm::BasicBlock *bbInit = ctx->CreateBasicBlock("init_ptr"); + llvm::BasicBlock *bbSkip = ctx->CreateBasicBlock("skip_init"); + llvm::Value *p = ctx->ExtractInst(ptrValue, i); + llvm::Value *nullValue = g->target.is32Bit ? LLVMInt32(0) : + LLVMInt64(0); + // Is the pointer for the current lane non-zero? + llvm::Value *nonNull = ctx->CmpInst(llvm::Instruction::ICmp, + llvm::CmpInst::ICMP_NE, + p, nullValue, "non_null"); + ctx->BranchInst(bbInit, bbSkip, nonNull); + + // Initialize the memory pointed to by the pointer for the + // current lane. + ctx->SetCurrentBasicBlock(bbInit); + LLVM_TYPE_CONST llvm::Type *ptrType = + retType->GetAsUniformType()->LLVMType(g->ctx); + llvm::Value *ptr = ctx->IntToPtrInst(p, ptrType); + InitSymbol(ptr, allocType, initExpr, ctx, pos); + ctx->BranchInst(bbSkip); + + ctx->SetCurrentBasicBlock(bbSkip); + } + } + + return ptrValue; + } + else { + // For uniform news, we just need to cast the void * to be a + // pointer of the return type and to run the code for initializers, + // if present. + LLVM_TYPE_CONST llvm::Type *ptrType = retType->LLVMType(g->ctx); + ptrValue = ctx->BitCastInst(ptrValue, ptrType, "cast_new_ptr"); + + if (initExpr != NULL) + InitSymbol(ptrValue, allocType, initExpr, ctx, pos); + + return ptrValue; + } +} + + +const Type * +NewExpr::GetType() const { + if (allocType == NULL) + return NULL; + + return isVarying ? PointerType::GetVarying(allocType) : + PointerType::GetUniform(allocType); +} + + +Expr * +NewExpr::TypeCheck() { + // Here we only need to make sure that if we have an expression giving + // a number of elements to allocate that it can be converted to an + // integer of the appropriate variability. + if (countExpr == NULL) + return this; + + const Type *countType; + if ((countType = countExpr->GetType()) == NULL) + return NULL; + + if (isVarying == false && countType->IsVaryingType()) { + Error(pos, "Illegal to provide \"varying\" allocation count with " + "\"uniform new\" expression."); + return NULL; + } + + // Figure out the type that the allocation count should be + const Type *t = (g->target.is32Bit || g->opt.force32BitAddressing) ? + AtomicType::UniformUInt32 : AtomicType::UniformUInt64; + if (isVarying) + t = t->GetAsVaryingType(); + + countExpr = TypeConvertExpr(countExpr, t, "item count"); + if (countExpr == NULL) + return NULL; + + return this; +} + + +Expr * +NewExpr::Optimize() { + return this; +} + + +void +NewExpr::Print() const { + printf("new (%s)", allocType ? allocType->GetString().c_str() : "NULL"); +} + + +int +NewExpr::EstimateCost() const { + return COST_NEW; +} diff --git a/expr.h b/expr.h index 48388475..3bc74d49 100644 --- a/expr.h +++ b/expr.h @@ -685,6 +685,38 @@ public: }; +/** An expression representing a "new" expression, used for dynamically + allocating memory. +*/ +class NewExpr : public Expr { +public: + NewExpr(int typeQual, const Type *type, Expr *initializer, Expr *count, + SourcePos tqPos, SourcePos p); + + llvm::Value *GetValue(FunctionEmitContext *ctx) const; + const Type *GetType() const; + Expr *TypeCheck(); + Expr *Optimize(); + void Print() const; + int EstimateCost() const; + + /** Type of object to allocate storage for. */ + const Type *allocType; + /** Expression giving the number of elements to allocate, when the + "new Foo[expr]" form is used. This may be NULL, in which case a + single element of the given type will be allocated. */ + Expr *countExpr; + /** Optional initializer expression used to initialize the allocated + memory. */ + Expr *initExpr; + /** Indicates whether this is a "varying new" or "uniform new" + (i.e. whether a separate allocation is performed per program + instance, or whether a single allocation is performed for the + entire gang of program instances.) */ + bool isVarying; +}; + + /** This function indicates whether it's legal to convert from fromType to toType. If the optional errorMsgBase and source position parameters are provided, then an error message is issued if the type conversion @@ -703,4 +735,20 @@ bool CanConvertTypes(const Type *fromType, const Type *toType, */ Expr *TypeConvertExpr(Expr *expr, const Type *toType, const char *errorMsgBase); +/** Utility routine that emits code to initialize a symbol given an + initializer expression. + + @param lvalue Memory location of storage for the symbol's data + @param symName Name of symbol (used in error messages) + @param symType Type of variable being initialized + @param initExpr Expression for the initializer + @param ctx FunctionEmitContext to use for generating instructions + @param pos Source file position of the variable being initialized +*/ +void +InitSymbol(llvm::Value *lvalue, const Type *symType, Expr *initExpr, + FunctionEmitContext *ctx, SourcePos pos); + +bool PossiblyResolveFunctionOverloads(Expr *expr, const Type *type); + #endif // ISPC_EXPR_H diff --git a/ispc.h b/ispc.h index 009470e2..9ebfef53 100644 --- a/ispc.h +++ b/ispc.h @@ -418,6 +418,7 @@ enum { COST_ASSIGN = 1, COST_COHERENT_BREAK_CONTINE = 4, COST_COMPLEX_ARITH_OP = 4, + COST_DELETE = 32, COST_DEREF = 4, COST_FUNCALL = 4, COST_FUNPTR_UNIFORM = 12, @@ -425,6 +426,7 @@ enum { COST_GATHER = 8, COST_GOTO = 4, COST_LOAD = 2, + COST_NEW = 32, COST_REGULAR_BREAK_CONTINUE = 2, COST_RETURN = 4, COST_SELECT = 4, diff --git a/lex.ll b/lex.ll index f21df180..9797e4e5 100644 --- a/lex.ll +++ b/lex.ll @@ -93,6 +93,8 @@ continue { return TOKEN_CONTINUE; } creturn { return TOKEN_CRETURN; } default { return TOKEN_DEFAULT; } do { return TOKEN_DO; } +delete { return TOKEN_DELETE; } +delete\[\] { return TOKEN_DELETE; } double { return TOKEN_DOUBLE; } else { return TOKEN_ELSE; } enum { return TOKEN_ENUM; } @@ -112,6 +114,7 @@ int16 { return TOKEN_INT16; } int32 { return TOKEN_INT; } int64 { return TOKEN_INT64; } launch { return TOKEN_LAUNCH; } +new { return TOKEN_NEW; } NULL { return TOKEN_NULL; } print { return TOKEN_PRINT; } reference { Error(*yylloc, "\"reference\" qualifier is no longer supported; " diff --git a/parse.yy b/parse.yy index 4be1ab7e..de93bf77 100644 --- a/parse.yy +++ b/parse.yy @@ -106,13 +106,14 @@ static void lFinalizeEnumeratorSymbols(std::vector &enums, const EnumType *enumType); static const char *lBuiltinTokens[] = { - "assert", "bool", "break", "case", "cbreak", "ccontinue", "cdo", "cfor", - "cif", "cwhile", "const", "continue", "creturn", "default", "do", "double", - "else", "enum", "export", "extern", "false", "float", "for", "foreach", - "foreach_tiled", "goto", "if", "inline", "int", "int8", "int16", - "int32", "int64", "launch", "NULL", "print", "return", "signed", "sizeof", - "static", "struct", "switch", "sync", "task", "true", "typedef", "uniform", - "unsigned", "varying", "void", "while", NULL + "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", + "int", "int8", "int16", "int32", "int64", "launch", "new", "NULL", + "print", "return", "signed", "sizeof", "static", "struct", "switch", + "sync", "task", "true", "typedef", "uniform", "unsigned", "varying", + "void", "while", NULL }; static const char *lParamListTokens[] = { @@ -170,7 +171,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 TOKEN_SIZEOF TOKEN_NEW TOKEN_DELETE %token TOKEN_EXTERN TOKEN_EXPORT TOKEN_STATIC TOKEN_INLINE TOKEN_TASK %token TOKEN_UNIFORM TOKEN_VARYING TOKEN_TYPEDEF TOKEN_SOA @@ -189,7 +190,7 @@ struct ForeachDimension { %type multiplicative_expression additive_expression shift_expression %type relational_expression equality_expression and_expression %type exclusive_or_expression inclusive_or_expression -%type logical_and_expression logical_or_expression +%type logical_and_expression logical_or_expression new_expression %type conditional_expression assignment_expression expression %type initializer constant_expression for_test %type argument_expression_list initializer_list @@ -197,7 +198,7 @@ struct ForeachDimension { %type statement labeled_statement compound_statement for_init_statement %type expression_statement selection_statement iteration_statement %type jump_statement statement_list declaration_statement print_statement -%type assert_statement sync_statement +%type assert_statement sync_statement delete_statement %type declaration parameter_declaration %type init_declarator_list @@ -215,7 +216,7 @@ struct ForeachDimension { %type enum_specifier %type specifier_qualifier_list struct_or_union_specifier -%type type_specifier type_name +%type type_specifier type_name rate_qualified_new_type %type short_vec_specifier %type atomic_var_type_specifier @@ -225,7 +226,7 @@ struct ForeachDimension { %type string_constant %type struct_or_union_name enum_identifier goto_identifier -%type int_constant soa_width_specifier +%type int_constant soa_width_specifier rate_qualified_new %type foreach_dimension_specifier %type foreach_dimension_list @@ -448,8 +449,36 @@ conditional_expression { $$ = new SelectExpr($1, $3, $5, Union(@1,@5)); } ; -assignment_expression +rate_qualified_new + : TOKEN_NEW { $$ = 0; } + | TOKEN_UNIFORM TOKEN_NEW { $$ = TYPEQUAL_UNIFORM; } + | TOKEN_VARYING TOKEN_NEW { $$ = TYPEQUAL_VARYING; } + ; + +rate_qualified_new_type + : type_specifier { $$ = $1; } + | TOKEN_UNIFORM type_specifier { $$ = $2->GetAsUniformType(); } + | TOKEN_VARYING type_specifier { $$ = $2->GetAsVaryingType(); } + ; + +new_expression : conditional_expression + | rate_qualified_new rate_qualified_new_type + { + $$ = new NewExpr($1, $2, NULL, NULL, @1, Union(@1, @2)); + } + | rate_qualified_new rate_qualified_new_type '(' initializer_list ')' + { + $$ = new NewExpr($1, $2, $4, NULL, @1, Union(@1, @2)); + } + | rate_qualified_new rate_qualified_new_type '[' expression ']' + { + $$ = new NewExpr($1, $2, NULL, $4, @1, Union(@1, @4)); + } + ; + +assignment_expression + : new_expression | unary_expression '=' assignment_expression { $$ = new AssignExpr(AssignExpr::Assign, $1, $3, Union(@1, @3)); } | unary_expression TOKEN_MUL_ASSIGN assignment_expression @@ -1240,6 +1269,7 @@ statement | print_statement | assert_statement | sync_statement + | delete_statement | error { std::vector builtinTokens; @@ -1473,6 +1503,13 @@ sync_statement { $$ = new ExprStmt(new SyncExpr(@1), @1); } ; +delete_statement + : TOKEN_DELETE expression ';' + { + $$ = new DeleteStmt($2, Union(@1, @2)); + } + ; + print_statement : TOKEN_PRINT '(' string_constant ')' ';' { diff --git a/stmt.cpp b/stmt.cpp index fda693e5..5e5fe27d 100644 --- a/stmt.cpp +++ b/stmt.cpp @@ -119,153 +119,6 @@ DeclStmt::DeclStmt(const std::vector &v, SourcePos p) } -static bool -lPossiblyResolveFunctionOverloads(Expr *expr, const Type *type) { - FunctionSymbolExpr *fse = NULL; - const FunctionType *funcType = NULL; - if (dynamic_cast(type) != NULL && - (funcType = dynamic_cast(type->GetBaseType())) && - (fse = dynamic_cast(expr)) != NULL) { - // We're initializing a function pointer with a function symbol, - // which in turn may represent an overloaded function. So we need - // to try to resolve the overload based on the type of the symbol - // we're initializing here. - std::vector paramTypes; - for (int i = 0; i < funcType->GetNumParameters(); ++i) - paramTypes.push_back(funcType->GetParameterType(i)); - - if (fse->ResolveOverloads(expr->pos, paramTypes) == false) - return false; - } - return true; -} - - -/** Utility routine that emits code to initialize a symbol given an - initializer expression. - - @param lvalue Memory location of storage for the symbol's data - @param symName Name of symbol (used in error messages) - @param symType Type of variable being initialized - @param initExpr Expression for the initializer - @param ctx FunctionEmitContext to use for generating instructions - @param pos Source file position of the variable being initialized -*/ -static void -lInitSymbol(llvm::Value *lvalue, const char *symName, const Type *symType, - Expr *initExpr, FunctionEmitContext *ctx, SourcePos pos) { - if (initExpr == NULL) - // leave it uninitialized - return; - - // If the initializer is a straight up expression that isn't an - // ExprList, then we'll see if we can type convert it to the type of - // the variable. - if (dynamic_cast(initExpr) == NULL) { - if (lPossiblyResolveFunctionOverloads(initExpr, symType) == false) - return; - initExpr = TypeConvertExpr(initExpr, symType, "initializer"); - - if (initExpr != NULL) { - llvm::Value *initializerValue = initExpr->GetValue(ctx); - if (initializerValue != NULL) - // Bingo; store the value in the variable's storage - ctx->StoreInst(initializerValue, lvalue); - return; - } - } - - // Atomic types and enums can't be initialized with { ... } initializer - // expressions, so print an error and return if that's what we've got - // here.. - if (dynamic_cast(symType) != NULL || - dynamic_cast(symType) != NULL || - dynamic_cast(symType) != NULL) { - ExprList *elist = dynamic_cast(initExpr); - if (elist != NULL) { - if (elist->exprs.size() == 1) - lInitSymbol(lvalue, symName, symType, elist->exprs[0], ctx, - pos); - else - Error(initExpr->pos, "Expression list initializers can't be used for " - "variable \"%s\' with type \"%s\".", symName, - symType->GetString().c_str()); - } - return; - } - - const ReferenceType *rt = dynamic_cast(symType); - if (rt) { - if (!Type::Equal(initExpr->GetType(), rt)) { - Error(initExpr->pos, "Initializer for reference type \"%s\" must have same " - "reference type itself. \"%s\" is incompatible.", - rt->GetString().c_str(), initExpr->GetType()->GetString().c_str()); - return; - } - - llvm::Value *initializerValue = initExpr->GetValue(ctx); - if (initializerValue) - ctx->StoreInst(initializerValue, lvalue); - return; - } - - // There are two cases for initializing structs, arrays and vectors; - // either a single initializer may be provided (float foo[3] = 0;), in - // which case all of the elements are initialized to the given value, - // or an initializer list may be provided (float foo[3] = { 1,2,3 }), - // in which case the elements are initialized with the corresponding - // values. - const CollectionType *collectionType = - dynamic_cast(symType); - if (collectionType != NULL) { - std::string name; - if (dynamic_cast(symType) != NULL) - name = "struct"; - else if (dynamic_cast(symType) != NULL) - name = "array"; - else if (dynamic_cast(symType) != NULL) - name = "vector"; - else - FATAL("Unexpected CollectionType in lInitSymbol()"); - - ExprList *exprList = dynamic_cast(initExpr); - if (exprList != NULL) { - // The { ... } case; make sure we have the same number of - // expressions in the ExprList as we have struct members - int nInits = exprList->exprs.size(); - if (nInits != collectionType->GetElementCount()) { - Error(initExpr->pos, "Initializer for %s \"%s\" requires " - "%d values; %d provided.", name.c_str(), symName, - collectionType->GetElementCount(), nInits); - return; - } - - // Initialize each element with the corresponding value from - // the ExprList - for (int i = 0; i < nInits; ++i) { - llvm::Value *ep; - if (dynamic_cast(symType) != NULL) - ep = ctx->AddElementOffset(lvalue, i, NULL, "element"); - else - ep = ctx->GetElementPtrInst(lvalue, LLVMInt32(0), LLVMInt32(i), - PointerType::GetUniform(collectionType->GetElementType(i)), - "gep"); - - lInitSymbol(ep, symName, collectionType->GetElementType(i), - exprList->exprs[i], ctx, pos); - } - } - else - Error(initExpr->pos, "Can't assign type \"%s\" to \"%s\".", - initExpr->GetType()->GetString().c_str(), - collectionType->GetString().c_str()); - return; - } - - FATAL("Unexpected Type in lInitSymbol()"); -} - - static bool lHasUnsizedArrays(const Type *type) { const ArrayType *at = dynamic_cast(type); @@ -333,7 +186,7 @@ DeclStmt::EmitCode(FunctionEmitContext *ctx) const { // zero value. llvm::Constant *cinit = NULL; if (initExpr != NULL) { - if (lPossiblyResolveFunctionOverloads(initExpr, sym->type) == false) + if (PossiblyResolveFunctionOverloads(initExpr, sym->type) == false) continue; // FIXME: we only need this for function pointers; it was // already done for atomic types and enums in @@ -377,8 +230,7 @@ DeclStmt::EmitCode(FunctionEmitContext *ctx) const { // And then get it initialized... sym->parentFunction = ctx->GetFunction(); - lInitSymbol(sym->storagePtr, sym->name.c_str(), sym->type, - initExpr, ctx, sym->pos); + InitSymbol(sym->storagePtr, sym->type, initExpr, ctx, sym->pos); } } } @@ -646,6 +498,15 @@ lCheckAllOffSafety(ASTNode *node, void *data) { return false; } + if (dynamic_cast(node) != NULL || + dynamic_cast(node) != NULL) { + // We definitely don't want to run the uniform variants of these if + // the mask is all off. It's also worth skipping the overhead of + // executing the varying versions of them in the all-off mask case. + *okPtr = false; + return false; + } + if (g->target.allOffMaskIsSafe == true) // Don't worry about memory accesses if we have a target that can // safely run them with the mask all off @@ -2880,3 +2741,82 @@ AssertStmt::EstimateCost() const { return COST_ASSERT; } + +/////////////////////////////////////////////////////////////////////////// +// DeleteStmt + +DeleteStmt::DeleteStmt(Expr *e, SourcePos p) + : Stmt(p) { + expr = e; +} + + +void +DeleteStmt::EmitCode(FunctionEmitContext *ctx) const { + const Type *exprType; + if (expr == NULL || ((exprType = expr->GetType()) == NULL)) { + Assert(m->errorCount > 0); + return; + } + + llvm::Value *exprValue = expr->GetValue(ctx); + if (exprValue == NULL) { + Assert(m->errorCount > 0); + return; + } + + // Typechecking should catch this + Assert(dynamic_cast(exprType) != NULL); + + if (exprType->IsUniformType()) { + // For deletion of a uniform pointer, we just need to cast the + // pointer type to a void pointer type, to match what + // __delete_uniform() from the builtins expects. + exprValue = ctx->BitCastInst(exprValue, LLVMTypes::VoidPointerType, + "ptr_to_void"); + llvm::Function *func = m->module->getFunction("__delete_uniform"); + Assert(func != NULL); + + ctx->CallInst(func, NULL, exprValue, ""); + } + else { + // Varying pointers are arrays of ints, and __delete_varying() + // takes a vector of i64s (even for 32-bit targets). Therefore, we + // only need to extend to 64-bit values on 32-bit targets before + // calling it. + llvm::Function *func = m->module->getFunction("__delete_varying"); + Assert(func != NULL); + if (g->target.is32Bit) + exprValue = ctx->ZExtInst(exprValue, LLVMTypes::Int64VectorType, + "ptr_to_64"); + ctx->CallInst(func, NULL, exprValue, ""); + } +} + + +void +DeleteStmt::Print(int indent) const { + printf("%*cDelete Stmt", indent, ' '); +} + + +Stmt * +DeleteStmt::TypeCheck() { + const Type *exprType; + if (expr == NULL || ((exprType = expr->GetType()) == NULL)) + return NULL; + + if (dynamic_cast(exprType) == NULL) { + Error(pos, "Illegal to delete non-pointer type \"%s\".", + exprType->GetString().c_str()); + return NULL; + } + + return this; +} + + +int +DeleteStmt::EstimateCost() const { + return COST_DELETE; +} diff --git a/stmt.h b/stmt.h index 8b22603a..f557a3f3 100644 --- a/stmt.h +++ b/stmt.h @@ -442,4 +442,21 @@ public: Expr *expr; }; + +/** Representation of a delete statement in the program. +*/ +class DeleteStmt : public Stmt { +public: + DeleteStmt(Expr *e, SourcePos p); + + void EmitCode(FunctionEmitContext *ctx) const; + void Print(int indent) const; + + Stmt *TypeCheck(); + int EstimateCost() const; + + /** Expression that gives the pointer value to be deleted. */ + Expr *expr; +}; + #endif // ISPC_STMT_H diff --git a/tests/new-delete-1.ispc b/tests/new-delete-1.ispc new file mode 100644 index 00000000..f8fc1599 --- /dev/null +++ b/tests/new-delete-1.ispc @@ -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]; + float * uniform buf = uniform new float[programCount]; + for (uniform int i = 0; i < programCount; ++i) + buf[i] = i; + RET[programIndex] = buf[a-1]; + delete buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = programIndex; +} diff --git a/tests/new-delete-2.ispc b/tests/new-delete-2.ispc new file mode 100644 index 00000000..bf24a0c4 --- /dev/null +++ b/tests/new-delete-2.ispc @@ -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]; + uniform float * uniform buf = uniform new uniform float[programCount]; + for (uniform int i = 0; i < programCount; ++i) + buf[i] = i; + RET[programIndex] = buf[a-1]; + delete buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = programIndex; +} diff --git a/tests/new-delete-3.ispc b/tests/new-delete-3.ispc new file mode 100644 index 00000000..676f9886 --- /dev/null +++ b/tests/new-delete-3.ispc @@ -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]; + float * uniform buf = uniform new float[programCount+1]; + for (uniform int i = 0; i < programCount+1; ++i) { + buf[i] = i+a; + } + RET[programIndex] = buf[a]; + delete buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 2 + 2*programIndex; +} diff --git a/tests/new-delete-4.ispc b/tests/new-delete-4.ispc new file mode 100644 index 00000000..de5488b9 --- /dev/null +++ b/tests/new-delete-4.ispc @@ -0,0 +1,14 @@ + +export uniform int width() { return programCount; } + + +export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) { + float a = aFOO[programIndex]; + float * uniform buf = uniform new float(2*b); + RET[programIndex] = buf[0]; + delete[] buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 10; +} diff --git a/tests/new-delete-5.ispc b/tests/new-delete-5.ispc new file mode 100644 index 00000000..ab99df2e --- /dev/null +++ b/tests/new-delete-5.ispc @@ -0,0 +1,17 @@ + +export uniform int width() { return programCount; } + +struct Point { + uniform float x, y, z; +}; + +export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) { + float a = aFOO[programIndex]; + varying Point * uniform buf = uniform new varying Point(a, b, 1234.); + RET[programIndex] = buf->y; + delete buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 5; +} diff --git a/tests/new-delete-6.ispc b/tests/new-delete-6.ispc new file mode 100644 index 00000000..90018b93 --- /dev/null +++ b/tests/new-delete-6.ispc @@ -0,0 +1,17 @@ + +export uniform int width() { return programCount; } + +struct Point { + float x, y, z; +}; + +export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) { + float a = aFOO[programIndex]; + Point * varying buf = new Point(0., b, a); + RET[programIndex] = buf->z; + delete buf; +} + +export void result(uniform float RET[]) { + RET[programIndex] = 1+programIndex; +} diff --git a/tests_errors/func-call-through-variable.ispc b/tests_errors/func-call-through-variable.ispc new file mode 100644 index 00000000..3a857e79 --- /dev/null +++ b/tests_errors/func-call-through-variable.ispc @@ -0,0 +1,47 @@ +// Must provide function name or function pointer for function call expression + +export void saxpy_ispc(uniform int N, +uniform float scale, +uniform float X[], +uniform float Y[], +uniform float result[]) +{ +foreach (i = 0 ... N) { +result[i] = scale * X[i] + Y[i]; +} +} + +task void saxpy_ispc_task(uniform int N, +uniform int span, +uniform float scale, +uniform float X[], +uniform float Y[], +uniform float result[]) +{ +uniform int indexStart; +uniform int indexEnd; +indexStart = (taskIndex * span); +indexEnd = min(N, indexStart + (span)/8); +foreach (i = indexStart ... indexEnd) { +result[i] = scale * X[i] + Y[i]; +} +uniform int k =0; +for (k=0; k<8;k++) { +indexStart = (((7-taskIndex-k)%8) * span) + k(span/8); +indexEnd = min(N, indexStart + (span)/8); +foreach (i = indexStart ... indexEnd) { +result[i] = scale * X[i] + Y[i]; +} +} +} +export void saxpy_ispc_withtasks(uniform int N, +uniform float scale, +uniform float X[], +uniform float Y[], +uniform float result[]) +{ + +uniform int span = N / 8; // 8 tasks + +launch[N/span] < saxpy_ispc_task(N, span, scale, X, Y, result) >; +} diff --git a/tests_errors/new-delete-1.ispc b/tests_errors/new-delete-1.ispc new file mode 100644 index 00000000..551b66d7 --- /dev/null +++ b/tests_errors/new-delete-1.ispc @@ -0,0 +1,5 @@ +// Illegal to delete non-pointer type + +void func(int a) { + delete a; +} diff --git a/tests_errors/new-delete-2.ispc b/tests_errors/new-delete-2.ispc new file mode 100644 index 00000000..fbbb1cd6 --- /dev/null +++ b/tests_errors/new-delete-2.ispc @@ -0,0 +1,5 @@ +// Syntax error + +int * func(int a) { + return const new int[a]; +} diff --git a/tests_errors/new-delete-3.ispc b/tests_errors/new-delete-3.ispc new file mode 100644 index 00000000..e34ea98d --- /dev/null +++ b/tests_errors/new-delete-3.ispc @@ -0,0 +1,5 @@ +// Syntax error + +int * func(int a) { + return new int[a](10); +} diff --git a/tests_errors/new-delete-4.ispc b/tests_errors/new-delete-4.ispc new file mode 100644 index 00000000..49fe6214 --- /dev/null +++ b/tests_errors/new-delete-4.ispc @@ -0,0 +1,7 @@ +// Type conversion only possible from atomic types + +struct P { int x; }; + +int * func(P p) { + return new int[p]; +} diff --git a/tests_errors/new-delete-5.ispc b/tests_errors/new-delete-5.ispc new file mode 100644 index 00000000..d25518b5 --- /dev/null +++ b/tests_errors/new-delete-5.ispc @@ -0,0 +1,5 @@ +// Illegal to provide "varying" allocation count with "uniform new" expression + +int * func(int x) { + return uniform new int[x]; +} diff --git a/tests_errors/new-delete-6.ispc b/tests_errors/new-delete-6.ispc new file mode 100644 index 00000000..a148298b --- /dev/null +++ b/tests_errors/new-delete-6.ispc @@ -0,0 +1,5 @@ +// Can't convert from varying type "int32 *" to uniform type "int32 * uniform" for return + +int * uniform func(int x) { + return new int[x]; +} diff --git a/tests_errors/new-delete-7.ispc b/tests_errors/new-delete-7.ispc new file mode 100644 index 00000000..12a3b79a --- /dev/null +++ b/tests_errors/new-delete-7.ispc @@ -0,0 +1,12 @@ +// Can't convert from varying type "float" to uniform type "uniform float" for initializer + +struct Point { + uniform float x, y, z; +}; + +export void f_fu(uniform float RET[], uniform float aFOO[], uniform float b) { + float a = aFOO[programIndex]; + uniform Point * uniform buf = uniform new uniform Point(a, b, 1234.); + RET[programIndex] = buf->y; + delete buf; +}