Add support for "new" and "delete" to the language.

Issue #139.
This commit is contained in:
Matt Pharr
2012-01-27 14:47:06 -08:00
parent bdba3cd97d
commit 664dc3bdda
26 changed files with 938 additions and 167 deletions

10
ast.cpp
View File

@@ -98,6 +98,7 @@ WalkAST(ASTNode *node, ASTPreCallBackFunc preFunc, ASTPostCallBackFunc postFunc,
StmtList *sl;
PrintStmt *ps;
AssertStmt *as;
DeleteStmt *dels;
if ((es = dynamic_cast<ExprStmt *>(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<AssertStmt *>(node)) != NULL)
as->expr = (Expr *)WalkAST(as->expr, preFunc, postFunc, data);
else if ((dels = dynamic_cast<DeleteStmt *>(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<UnaryExpr *>(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<AddressOfExpr *>(node)) != NULL)
aoe->expr = (Expr *)WalkAST(aoe->expr, preFunc, postFunc, data);
else if ((newe = dynamic_cast<NewExpr *>(node)) != NULL) {
newe->countExpr = (Expr *)WalkAST(newe->countExpr, preFunc,
postFunc, data);
newe->initExpr = (Expr *)WalkAST(newe->initExpr, preFunc,
postFunc, data);
}
else if (dynamic_cast<SymbolExpr *>(node) != NULL ||
dynamic_cast<ConstExpr *>(node) != NULL ||
dynamic_cast<FunctionSymbolExpr *>(node) != NULL ||

View File

@@ -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",

View File

@@ -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 <WIDTH x i64> @__new_varying32(<WIDTH x i32> %size, <WIDTH x MASK> %mask) {
%ret = alloca <WIDTH x i64>
store <WIDTH x i64> zeroinitializer, <WIDTH x i64> * %ret
%ret64 = bitcast <WIDTH x i64> * %ret to i64 *
per_lane(WIDTH, <WIDTH x MASK> %mask, `
%sz_LANE_ID = extractelement <WIDTH x i32> %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 <WIDTH x i64> * %ret
ret <WIDTH x i64> %r
}
define <WIDTH x i64> @__new_varying64(<WIDTH x i64> %size, <WIDTH x MASK> %mask) {
%ret = alloca <WIDTH x i64>
store <WIDTH x i64> zeroinitializer, <WIDTH x i64> * %ret
%ret64 = bitcast <WIDTH x i64> * %ret to i64 *
per_lane(WIDTH, <WIDTH x MASK> %mask, `
%sz_LANE_ID = extractelement <WIDTH x i64> %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 <WIDTH x i64> * %ret
ret <WIDTH x i64> %r
}
define void @__delete_uniform(i8 * %ptr) {
call void @free(i8 * %ptr)
ret void
}
define void @__delete_varying(<WIDTH x i64> %ptr, <WIDTH x MASK> %mask) {
per_lane(WIDTH, <WIDTH x MASK> %mask, `
%iptr_LANE_ID = extractelement <WIDTH x i64> %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

View File

@@ -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.

View File

@@ -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"
----------------------------

355
expr.cpp
View File

@@ -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<const PointerType *>(type) != NULL &&
(funcType = dynamic_cast<const FunctionType *>(type->GetBaseType())) &&
(fse = dynamic_cast<FunctionSymbolExpr *>(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<const Type *> 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<ExprList *>(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<const AtomicType *>(symType) != NULL ||
dynamic_cast<const EnumType *>(symType) != NULL ||
dynamic_cast<const PointerType *>(symType) != NULL) {
ExprList *elist = dynamic_cast<ExprList *>(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<const ReferenceType *>(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<const CollectionType *>(symType);
if (collectionType != NULL) {
std::string name;
if (dynamic_cast<const StructType *>(symType) != NULL)
name = "struct";
else if (dynamic_cast<const ArrayType *>(symType) != NULL)
name = "array";
else if (dynamic_cast<const VectorType *>(symType) != NULL)
name = "vector";
else
FATAL("Unexpected CollectionType in InitSymbol()");
ExprList *exprList = dynamic_cast<ExprList *>(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<const StructType *>(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;
}

48
expr.h
View File

@@ -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

2
ispc.h
View File

@@ -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,

3
lex.ll
View File

@@ -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; "

View File

@@ -106,13 +106,14 @@ static void lFinalizeEnumeratorSymbols(std::vector<Symbol *> &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 <expr> multiplicative_expression additive_expression shift_expression
%type <expr> relational_expression equality_expression and_expression
%type <expr> exclusive_or_expression inclusive_or_expression
%type <expr> logical_and_expression logical_or_expression
%type <expr> logical_and_expression logical_or_expression new_expression
%type <expr> conditional_expression assignment_expression expression
%type <expr> initializer constant_expression for_test
%type <exprList> argument_expression_list initializer_list
@@ -197,7 +198,7 @@ struct ForeachDimension {
%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> assert_statement sync_statement
%type <stmt> assert_statement sync_statement delete_statement
%type <declaration> declaration parameter_declaration
%type <declarators> init_declarator_list
@@ -215,7 +216,7 @@ struct ForeachDimension {
%type <enumType> enum_specifier
%type <type> specifier_qualifier_list struct_or_union_specifier
%type <type> type_specifier type_name
%type <type> type_specifier type_name rate_qualified_new_type
%type <type> short_vec_specifier
%type <atomicType> atomic_var_type_specifier
@@ -225,7 +226,7 @@ struct ForeachDimension {
%type <stringVal> string_constant
%type <constCharPtr> struct_or_union_name enum_identifier goto_identifier
%type <intVal> int_constant soa_width_specifier
%type <intVal> int_constant soa_width_specifier rate_qualified_new
%type <foreachDimension> foreach_dimension_specifier
%type <foreachDimensionList> 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<std::string> 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 ')' ';'
{

240
stmt.cpp
View File

@@ -119,153 +119,6 @@ DeclStmt::DeclStmt(const std::vector<VariableDeclaration> &v, SourcePos p)
}
static bool
lPossiblyResolveFunctionOverloads(Expr *expr, const Type *type) {
FunctionSymbolExpr *fse = NULL;
const FunctionType *funcType = NULL;
if (dynamic_cast<const PointerType *>(type) != NULL &&
(funcType = dynamic_cast<const FunctionType *>(type->GetBaseType())) &&
(fse = dynamic_cast<FunctionSymbolExpr *>(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<const Type *> 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<ExprList *>(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<const AtomicType *>(symType) != NULL ||
dynamic_cast<const EnumType *>(symType) != NULL ||
dynamic_cast<const PointerType *>(symType) != NULL) {
ExprList *elist = dynamic_cast<ExprList *>(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<const ReferenceType *>(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<const CollectionType *>(symType);
if (collectionType != NULL) {
std::string name;
if (dynamic_cast<const StructType *>(symType) != NULL)
name = "struct";
else if (dynamic_cast<const ArrayType *>(symType) != NULL)
name = "array";
else if (dynamic_cast<const VectorType *>(symType) != NULL)
name = "vector";
else
FATAL("Unexpected CollectionType in lInitSymbol()");
ExprList *exprList = dynamic_cast<ExprList *>(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<const StructType *>(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<const ArrayType *>(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<NewExpr *>(node) != NULL ||
dynamic_cast<DeleteStmt *>(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<const PointerType *>(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<const PointerType *>(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;
}

17
stmt.h
View File

@@ -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

15
tests/new-delete-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];
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;
}

15
tests/new-delete-2.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];
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;
}

17
tests/new-delete-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];
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;
}

14
tests/new-delete-4.ispc Normal file
View File

@@ -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;
}

17
tests/new-delete-5.ispc Normal file
View File

@@ -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;
}

17
tests/new-delete-6.ispc Normal file
View File

@@ -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;
}

View File

@@ -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) >;
}

View File

@@ -0,0 +1,5 @@
// Illegal to delete non-pointer type
void func(int a) {
delete a;
}

View File

@@ -0,0 +1,5 @@
// Syntax error
int * func(int a) {
return const new int[a];
}

View File

@@ -0,0 +1,5 @@
// Syntax error
int * func(int a) {
return new int[a](10);
}

View File

@@ -0,0 +1,7 @@
// Type conversion only possible from atomic types
struct P { int x; };
int * func(P p) {
return new int[p];
}

View File

@@ -0,0 +1,5 @@
// Illegal to provide "varying" allocation count with "uniform new" expression
int * func(int x) {
return uniform new int[x];
}

View File

@@ -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];
}

View File

@@ -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;
}