/*
* Copyright 2011-2012, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "slang_rs_export_foreach.h"
#include <string>
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/TypeLoc.h"
#include "llvm/IR/DerivedTypes.h"
#include "slang_assert.h"
#include "slang_rs_context.h"
#include "slang_rs_export_type.h"
#include "slang_version.h"
namespace slang {
namespace {
static void ReportNameError(clang::DiagnosticsEngine *DiagEngine,
clang::ParmVarDecl const *PVD) {
slangAssert(DiagEngine && PVD);
const clang::SourceManager &SM = DiagEngine->getSourceManager();
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(), SM),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Duplicate parameter entry "
"(by position/name): '%0'"))
<< PVD->getName();
return;
}
} // namespace
// This function takes care of additional validation and construction of
// parameters related to forEach_* reflection.
bool RSExportForEach::validateAndConstructParams(
RSContext *Context, const clang::FunctionDecl *FD) {
slangAssert(Context && FD);
bool valid = true;
clang::ASTContext &C = Context->getASTContext();
clang::DiagnosticsEngine *DiagEngine = Context->getDiagnostics();
numParams = FD->getNumParams();
if (Context->getTargetAPI() < SLANG_JB_TARGET_API) {
if (!isRootRSFunc(FD)) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(), DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Non-root compute kernel %0() is "
"not supported in SDK levels %1-%2"))
<< FD->getName()
<< SLANG_MINIMUM_TARGET_API
<< (SLANG_JB_TARGET_API - 1);
return false;
}
}
mResultType = FD->getResultType().getCanonicalType();
// Compute kernel functions are required to return a void type or
// be marked explicitly as a kernel. In the case of
// "__attribute__((kernel))", we handle validation differently.
if (FD->hasAttr<clang::KernelAttr>()) {
return validateAndConstructKernelParams(Context, FD);
}
// If numParams is 0, we already marked this as a graphics root().
slangAssert(numParams > 0);
// Compute kernel functions of this type are required to return a void type.
if (mResultType != C.VoidTy) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(), DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() is required to return a "
"void type")) << FD->getName();
valid = false;
}
// Validate remaining parameter types
// TODO(all): Add support for LOD/face when we have them
size_t i = 0;
const clang::ParmVarDecl *PVD = FD->getParamDecl(i);
clang::QualType QT = PVD->getType().getCanonicalType();
// Check for const T1 *in
if (QT->isPointerType() && QT->getPointeeType().isConstQualified()) {
mIn = PVD;
i++; // advance parameter pointer
}
// Check for T2 *out
if (i < numParams) {
PVD = FD->getParamDecl(i);
QT = PVD->getType().getCanonicalType();
if (QT->isPointerType() && !QT->getPointeeType().isConstQualified()) {
mOut = PVD;
i++; // advance parameter pointer
}
}
if (!mIn && !mOut) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() must have at least one "
"parameter for in or out")) << FD->getName();
valid = false;
}
// Check for T3 *usrData
if (i < numParams) {
PVD = FD->getParamDecl(i);
QT = PVD->getType().getCanonicalType();
if (QT->isPointerType() && QT->getPointeeType().isConstQualified()) {
mUsrData = PVD;
i++; // advance parameter pointer
}
}
while (i < numParams) {
PVD = FD->getParamDecl(i);
QT = PVD->getType().getCanonicalType();
if (QT.getUnqualifiedType() != C.UnsignedIntTy) {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Unexpected kernel %0() parameter '%1' "
"of type '%2'"))
<< FD->getName() << PVD->getName() << PVD->getType().getAsString();
valid = false;
} else {
llvm::StringRef ParamName = PVD->getName();
if (ParamName.equals("x")) {
if (mX) {
ReportNameError(DiagEngine, PVD);
valid = false;
} else if (mY) {
// Can't go back to X after skipping Y
ReportNameError(DiagEngine, PVD);
valid = false;
} else {
mX = PVD;
}
} else if (ParamName.equals("y")) {
if (mY) {
ReportNameError(DiagEngine, PVD);
valid = false;
} else {
mY = PVD;
}
} else {
if (!mX && !mY) {
mX = PVD;
} else if (!mY) {
mY = PVD;
} else {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Unexpected kernel %0() parameter '%1' "
"of type '%2'"))
<< FD->getName() << PVD->getName() << PVD->getType().getAsString();
valid = false;
}
}
}
i++;
}
mSignatureMetadata = 0;
if (valid) {
// Set up the bitwise metadata encoding for runtime argument passing.
mSignatureMetadata |= (mIn ? 0x01 : 0);
mSignatureMetadata |= (mOut ? 0x02 : 0);
mSignatureMetadata |= (mUsrData ? 0x04 : 0);
mSignatureMetadata |= (mX ? 0x08 : 0);
mSignatureMetadata |= (mY ? 0x10 : 0);
}
if (Context->getTargetAPI() < SLANG_ICS_TARGET_API) {
// APIs before ICS cannot skip between parameters. It is ok, however, for
// them to omit further parameters (i.e. skipping X is ok if you skip Y).
if (mSignatureMetadata != 0x1f && // In, Out, UsrData, X, Y
mSignatureMetadata != 0x0f && // In, Out, UsrData, X
mSignatureMetadata != 0x07 && // In, Out, UsrData
mSignatureMetadata != 0x03 && // In, Out
mSignatureMetadata != 0x01) { // In
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() targeting SDK levels "
"%1-%2 may not skip parameters"))
<< FD->getName() << SLANG_MINIMUM_TARGET_API
<< (SLANG_ICS_TARGET_API - 1);
valid = false;
}
}
return valid;
}
bool RSExportForEach::validateAndConstructKernelParams(RSContext *Context,
const clang::FunctionDecl *FD) {
slangAssert(Context && FD);
bool valid = true;
clang::ASTContext &C = Context->getASTContext();
clang::DiagnosticsEngine *DiagEngine = Context->getDiagnostics();
if (Context->getTargetAPI() < SLANG_JB_MR1_TARGET_API) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() targeting SDK levels "
"%1-%2 may not use pass-by-value with "
"__attribute__((kernel))"))
<< FD->getName() << SLANG_MINIMUM_TARGET_API
<< (SLANG_JB_MR1_TARGET_API - 1);
return false;
}
// Denote that we are indeed a pass-by-value kernel.
mKernel = true;
if (mResultType != C.VoidTy) {
mReturn = true;
}
if (mResultType->isPointerType()) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getTypeSpecStartLoc(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() cannot return a "
"pointer type: '%1'"))
<< FD->getName() << mResultType.getAsString();
valid = false;
}
// Validate remaining parameter types
// TODO(all): Add support for LOD/face when we have them
size_t i = 0;
const clang::ParmVarDecl *PVD = NULL;
clang::QualType QT;
if (i < numParams) {
PVD = FD->getParamDecl(i);
QT = PVD->getType().getCanonicalType();
if (QT->isPointerType()) {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() cannot have "
"parameter '%1' of pointer type: '%2'"))
<< FD->getName() << PVD->getName() << PVD->getType().getAsString();
valid = false;
} else if (QT.getUnqualifiedType() == C.UnsignedIntTy) {
// First parameter is either input or x, y (iff it is uint32_t).
llvm::StringRef ParamName = PVD->getName();
if (ParamName.equals("x")) {
mX = PVD;
} else if (ParamName.equals("y")) {
mY = PVD;
} else {
mIn = PVD;
}
} else {
mIn = PVD;
}
i++; // advance parameter pointer
}
// Check that we have at least one allocation to use for dimensions.
if (valid && !mIn && !mReturn) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Compute kernel %0() must have at least one "
"input parameter or a non-void return "
"type")) << FD->getName();
valid = false;
}
// TODO: Abstract this block away, since it is duplicate code.
while (i < numParams) {
PVD = FD->getParamDecl(i);
QT = PVD->getType().getCanonicalType();
if (QT.getUnqualifiedType() != C.UnsignedIntTy) {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Unexpected kernel %0() parameter '%1' "
"of type '%2'"))
<< FD->getName() << PVD->getName() << PVD->getType().getAsString();
valid = false;
} else {
llvm::StringRef ParamName = PVD->getName();
if (ParamName.equals("x")) {
if (mX) {
ReportNameError(DiagEngine, PVD);
valid = false;
} else if (mY) {
// Can't go back to X after skipping Y
ReportNameError(DiagEngine, PVD);
valid = false;
} else {
mX = PVD;
}
} else if (ParamName.equals("y")) {
if (mY) {
ReportNameError(DiagEngine, PVD);
valid = false;
} else {
mY = PVD;
}
} else {
if (!mX && !mY) {
mX = PVD;
} else if (!mY) {
mY = PVD;
} else {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Unexpected kernel %0() parameter '%1' "
"of type '%2'"))
<< FD->getName() << PVD->getName() << PVD->getType().getAsString();
valid = false;
}
}
}
i++; // advance parameter pointer
}
mSignatureMetadata = 0;
if (valid) {
// Set up the bitwise metadata encoding for runtime argument passing.
mSignatureMetadata |= (mIn ? 0x01 : 0);
slangAssert(mOut == NULL);
mSignatureMetadata |= (mReturn ? 0x02 : 0);
slangAssert(mUsrData == NULL);
mSignatureMetadata |= (mUsrData ? 0x04 : 0);
mSignatureMetadata |= (mX ? 0x08 : 0);
mSignatureMetadata |= (mY ? 0x10 : 0);
mSignatureMetadata |= (mKernel ? 0x20 : 0); // pass-by-value
}
return valid;
}
RSExportForEach *RSExportForEach::Create(RSContext *Context,
const clang::FunctionDecl *FD) {
slangAssert(Context && FD);
llvm::StringRef Name = FD->getName();
RSExportForEach *FE;
slangAssert(!Name.empty() && "Function must have a name");
FE = new RSExportForEach(Context, Name);
if (!FE->validateAndConstructParams(Context, FD)) {
return NULL;
}
clang::ASTContext &Ctx = Context->getASTContext();
std::string Id(DUMMY_RS_TYPE_NAME_PREFIX"helper_foreach_param:");
Id.append(FE->getName()).append(DUMMY_RS_TYPE_NAME_POSTFIX);
// Extract the usrData parameter (if we have one)
if (FE->mUsrData) {
const clang::ParmVarDecl *PVD = FE->mUsrData;
clang::QualType QT = PVD->getType().getCanonicalType();
slangAssert(QT->isPointerType() &&
QT->getPointeeType().isConstQualified());
const clang::ASTContext &C = Context->getASTContext();
if (QT->getPointeeType().getCanonicalType().getUnqualifiedType() ==
C.VoidTy) {
// In the case of using const void*, we can't reflect an appopriate
// Java type, so we fall back to just reflecting the ain/aout parameters
FE->mUsrData = NULL;
} else {
clang::RecordDecl *RD =
clang::RecordDecl::Create(Ctx, clang::TTK_Struct,
Ctx.getTranslationUnitDecl(),
clang::SourceLocation(),
clang::SourceLocation(),
&Ctx.Idents.get(Id));
clang::FieldDecl *FD =
clang::FieldDecl::Create(Ctx,
RD,
clang::SourceLocation(),
clang::SourceLocation(),
PVD->getIdentifier(),
QT->getPointeeType(),
NULL,
/* BitWidth = */ NULL,
/* Mutable = */ false,
/* HasInit = */ clang::ICIS_NoInit);
RD->addDecl(FD);
RD->completeDefinition();
// Create an export type iff we have a valid usrData type
clang::QualType T = Ctx.getTagDeclType(RD);
slangAssert(!T.isNull());
RSExportType *ET = RSExportType::Create(Context, T.getTypePtr());
if (ET == NULL) {
fprintf(stderr, "Failed to export the function %s. There's at least "
"one parameter whose type is not supported by the "
"reflection\n", FE->getName().c_str());
return NULL;
}
slangAssert((ET->getClass() == RSExportType::ExportClassRecord) &&
"Parameter packet must be a record");
FE->mParamPacketType = static_cast<RSExportRecordType *>(ET);
}
}
if (FE->mIn) {
const clang::Type *T = FE->mIn->getType().getCanonicalType().getTypePtr();
FE->mInType = RSExportType::Create(Context, T);
if (FE->mKernel) {
slangAssert(FE->mInType);
}
}
if (FE->mKernel && FE->mReturn) {
const clang::Type *T = FE->mResultType.getTypePtr();
FE->mOutType = RSExportType::Create(Context, T);
slangAssert(FE->mOutType);
} else if (FE->mOut) {
const clang::Type *T = FE->mOut->getType().getCanonicalType().getTypePtr();
FE->mOutType = RSExportType::Create(Context, T);
}
return FE;
}
RSExportForEach *RSExportForEach::CreateDummyRoot(RSContext *Context) {
slangAssert(Context);
llvm::StringRef Name = "root";
RSExportForEach *FE = new RSExportForEach(Context, Name);
FE->mDummyRoot = true;
return FE;
}
bool RSExportForEach::isGraphicsRootRSFunc(int targetAPI,
const clang::FunctionDecl *FD) {
if (FD->hasAttr<clang::KernelAttr>()) {
return false;
}
if (!isRootRSFunc(FD)) {
return false;
}
if (FD->getNumParams() == 0) {
// Graphics root function
return true;
}
// Check for legacy graphics root function (with single parameter).
if ((targetAPI < SLANG_ICS_TARGET_API) && (FD->getNumParams() == 1)) {
const clang::QualType &IntType = FD->getASTContext().IntTy;
if (FD->getResultType().getCanonicalType() == IntType) {
return true;
}
}
return false;
}
bool RSExportForEach::isRSForEachFunc(int targetAPI,
clang::DiagnosticsEngine *DiagEngine,
const clang::FunctionDecl *FD) {
slangAssert(DiagEngine && FD);
bool hasKernelAttr = FD->hasAttr<clang::KernelAttr>();
if (FD->getStorageClass() == clang::SC_Static) {
if (hasKernelAttr) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"Invalid use of attribute kernel with "
"static function declaration: %0"))
<< FD->getName();
}
return false;
}
// Anything tagged as a kernel is definitely used with ForEach.
if (hasKernelAttr) {
return true;
}
if (isGraphicsRootRSFunc(targetAPI, FD)) {
return false;
}
// Check if first parameter is a pointer (which is required for ForEach).
unsigned int numParams = FD->getNumParams();
if (numParams > 0) {
const clang::ParmVarDecl *PVD = FD->getParamDecl(0);
clang::QualType QT = PVD->getType().getCanonicalType();
if (QT->isPointerType()) {
return true;
}
// Any non-graphics root() is automatically a ForEach candidate.
// At this point, however, we know that it is not going to be a valid
// compute root() function (due to not having a pointer parameter). We
// still want to return true here, so that we can issue appropriate
// diagnostics.
if (isRootRSFunc(FD)) {
return true;
}
}
return false;
}
bool
RSExportForEach::validateSpecialFuncDecl(int targetAPI,
clang::DiagnosticsEngine *DiagEngine,
clang::FunctionDecl const *FD) {
slangAssert(DiagEngine && FD);
bool valid = true;
const clang::ASTContext &C = FD->getASTContext();
const clang::QualType &IntType = FD->getASTContext().IntTy;
if (isGraphicsRootRSFunc(targetAPI, FD)) {
if ((targetAPI < SLANG_ICS_TARGET_API) && (FD->getNumParams() == 1)) {
// Legacy graphics root function
const clang::ParmVarDecl *PVD = FD->getParamDecl(0);
clang::QualType QT = PVD->getType().getCanonicalType();
if (QT != IntType) {
DiagEngine->Report(
clang::FullSourceLoc(PVD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"invalid parameter type for legacy "
"graphics root() function: %0"))
<< PVD->getType();
valid = false;
}
}
// Graphics root function, so verify that it returns an int
if (FD->getResultType().getCanonicalType() != IntType) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"root() is required to return "
"an int for graphics usage"));
valid = false;
}
} else if (isInitRSFunc(FD) || isDtorRSFunc(FD)) {
if (FD->getNumParams() != 0) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"%0(void) is required to have no "
"parameters")) << FD->getName();
valid = false;
}
if (FD->getResultType().getCanonicalType() != C.VoidTy) {
DiagEngine->Report(
clang::FullSourceLoc(FD->getLocation(),
DiagEngine->getSourceManager()),
DiagEngine->getCustomDiagID(clang::DiagnosticsEngine::Error,
"%0(void) is required to have a void "
"return type")) << FD->getName();
valid = false;
}
} else {
slangAssert(false && "must be called on root, init or .rs.dtor function!");
}
return valid;
}
} // namespace slang