/*
* Copyright 2010, 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_pragma_handler.h"
#include <map>
#include <sstream>
#include <string>
#include "clang/AST/ASTContext.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Lex/LiteralSupport.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/Token.h"
#include "slang_assert.h"
#include "slang_rs_context.h"
#include "slang_rs_export_reduce.h"
#include "slang_version.h"
namespace slang {
namespace { // Anonymous namespace
class RSExportTypePragmaHandler : public RSPragmaHandler {
private:
void handleItem(const std::string &Item) {
mContext->addPragma(this->getName(), Item);
mContext->addExportType(Item);
}
public:
RSExportTypePragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) { }
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &FirstToken) {
this->handleItemListPragma(PP, FirstToken);
}
};
class RSJavaPackageNamePragmaHandler : public RSPragmaHandler {
public:
RSJavaPackageNamePragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) { }
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &FirstToken) {
// FIXME: Need to validate the extracted package name from pragma.
// Currently "all chars" specified in pragma will be treated as package
// name.
//
// 18.1 The Grammar of the Java Programming Language
// (http://java.sun.com/docs/books/jls/third_edition/html/syntax.html#18.1)
//
// CompilationUnit:
// [[Annotations] package QualifiedIdentifier ; ] {ImportDeclaration}
// {TypeDeclaration}
//
// QualifiedIdentifier:
// Identifier { . Identifier }
//
// Identifier:
// IDENTIFIER
//
// 3.8 Identifiers
// (http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.8)
//
//
clang::Token &PragmaToken = FirstToken;
std::string PackageName;
// Skip first token, "java_package_name"
PP.LexUnexpandedToken(PragmaToken);
// Now, the current token must be clang::tok::lpara
if (PragmaToken.isNot(clang::tok::l_paren))
return;
while (PragmaToken.isNot(clang::tok::eod)) {
// Lex package name
PP.LexUnexpandedToken(PragmaToken);
bool Invalid;
std::string Spelling = PP.getSpelling(PragmaToken, &Invalid);
if (!Invalid)
PackageName.append(Spelling);
// Pre-mature end (syntax error will be triggered by preprocessor later)
if (PragmaToken.is(clang::tok::eod) || PragmaToken.is(clang::tok::eof)) {
break;
} else {
// Next token is ')' (end of pragma)
const clang::Token &NextTok = PP.LookAhead(0);
if (NextTok.is(clang::tok::r_paren)) {
mContext->addPragma(this->getName(), PackageName);
mContext->setReflectJavaPackageName(PackageName);
// Lex until meets clang::tok::eod
do {
PP.LexUnexpandedToken(PragmaToken);
} while (PragmaToken.isNot(clang::tok::eod));
break;
}
}
}
}
};
class RSReducePragmaHandler : public RSPragmaHandler {
public:
RSReducePragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) { }
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &FirstToken) override {
// #pragma rs reduce(name)
// initializer(initializename)
// accumulator(accumulatename)
// combiner(combinename)
// outconverter(outconvertname)
// halter(haltname)
const clang::SourceLocation PragmaLocation = FirstToken.getLocation();
clang::Token &PragmaToken = FirstToken;
// Grab "reduce(name)" ("reduce" is already known to be the first
// token) and all the "keyword(value)" contributions
KeywordValueMapType KeywordValueMap({std::make_pair(RSExportReduce::KeyReduce, ""),
std::make_pair(RSExportReduce::KeyInitializer, ""),
std::make_pair(RSExportReduce::KeyAccumulator, ""),
std::make_pair(RSExportReduce::KeyCombiner, ""),
std::make_pair(RSExportReduce::KeyOutConverter, "")});
if (mContext->getTargetAPI() >= SLANG_FEATURE_GENERAL_REDUCTION_HALTER_API) {
// Halter functionality has not been released, nor has its
// specification been finalized with partners. We do not have a
// specification that extends through the full RenderScript
// software stack, either.
KeywordValueMap.insert(std::make_pair(RSExportReduce::KeyHalter, ""));
}
while (PragmaToken.is(clang::tok::identifier)) {
if (!ProcessKeywordAndValue(PP, PragmaToken, KeywordValueMap))
return;
}
// Make sure there's no end-of-line garbage
if (PragmaToken.isNot(clang::tok::eod)) {
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"did not expect '%0' here for '#pragma rs %1'"))
<< PP.getSpelling(PragmaToken) << getName();
return;
}
// Make sure we have an accumulator
if (KeywordValueMap[RSExportReduce::KeyAccumulator].empty()) {
PP.Diag(PragmaLocation, PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"missing '%0' for '#pragma rs %1'"))
<< RSExportReduce::KeyAccumulator << getName();
return;
}
// Make sure the reduction kernel name is unique. (If we were
// worried there might be a VERY large number of pragmas, then we
// could do something more efficient than walking a list to search
// for duplicates.)
for (auto I = mContext->export_reduce_begin(),
E = mContext->export_reduce_end();
I != E; ++I) {
if ((*I)->getNameReduce() == KeywordValueMap[RSExportReduce::KeyReduce]) {
PP.Diag(PragmaLocation, PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"reduction kernel '%0' declared multiple "
"times (first one is at %1)"))
<< KeywordValueMap[RSExportReduce::KeyReduce]
<< (*I)->getLocation().printToString(PP.getSourceManager());
return;
}
}
// Check API version.
if (mContext->getTargetAPI() < SLANG_FEATURE_GENERAL_REDUCTION_API) {
PP.Diag(PragmaLocation,
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"reduction kernels are not supported in SDK levels %0-%1"))
<< SLANG_MINIMUM_TARGET_API
<< (SLANG_FEATURE_GENERAL_REDUCTION_API - 1);
return;
}
// Handle backward reference from pragma (see Backend::HandleTopLevelDecl for forward reference).
MarkUsed(PP, KeywordValueMap[RSExportReduce::KeyInitializer]);
MarkUsed(PP, KeywordValueMap[RSExportReduce::KeyAccumulator]);
MarkUsed(PP, KeywordValueMap[RSExportReduce::KeyCombiner]);
MarkUsed(PP, KeywordValueMap[RSExportReduce::KeyOutConverter]);
MarkUsed(PP, KeywordValueMap[RSExportReduce::KeyHalter]);
mContext->addExportReduce(RSExportReduce::Create(mContext, PragmaLocation,
KeywordValueMap[RSExportReduce::KeyReduce],
KeywordValueMap[RSExportReduce::KeyInitializer],
KeywordValueMap[RSExportReduce::KeyAccumulator],
KeywordValueMap[RSExportReduce::KeyCombiner],
KeywordValueMap[RSExportReduce::KeyOutConverter],
KeywordValueMap[RSExportReduce::KeyHalter]));
}
private:
typedef std::map<std::string, std::string> KeywordValueMapType;
void MarkUsed(clang::Preprocessor &PP, const std::string &FunctionName) {
if (FunctionName.empty())
return;
clang::ASTContext &ASTC = mContext->getASTContext();
clang::TranslationUnitDecl *TUDecl = ASTC.getTranslationUnitDecl();
slangAssert(TUDecl);
if (const clang::IdentifierInfo *II = PP.getIdentifierInfo(FunctionName)) {
for (auto Decl : TUDecl->lookup(II)) {
clang::FunctionDecl *FDecl = Decl->getAsFunction();
if (!FDecl || !FDecl->isThisDeclarationADefinition())
continue;
// Handle backward reference from pragma (see
// Backend::HandleTopLevelDecl for forward reference).
mContext->markUsedByReducePragma(FDecl, RSContext::CheckNameNo);
}
}
}
// Return comma-separated list of all keys in the map
static std::string ListKeywords(const KeywordValueMapType &KeywordValueMap) {
std::string Ret;
bool First = true;
for (auto const &entry : KeywordValueMap) {
if (First)
First = false;
else
Ret += ", ";
Ret += "'";
Ret += entry.first;
Ret += "'";
}
return Ret;
}
// Parse "keyword(value)" and set KeywordValueMap[keyword] = value. (Both
// "keyword" and "value" are identifiers.)
// Does both syntactic validation and the following semantic validation:
// - The keyword must be present in the map.
// - The map entry for the keyword must not contain a value.
bool ProcessKeywordAndValue(clang::Preprocessor &PP,
clang::Token &PragmaToken,
KeywordValueMapType &KeywordValueMap) {
// The current token must be an identifier in KeywordValueMap
KeywordValueMapType::iterator Entry;
if (PragmaToken.isNot(clang::tok::identifier) ||
((Entry = KeywordValueMap.find(
PragmaToken.getIdentifierInfo()->getName())) ==
KeywordValueMap.end())) {
// Note that we should never get here for the "reduce" token
// itself, which should already have been recognized.
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"did not recognize '%0' for '#pragma %1'; expected one of "
"the following keywords: %2"))
<< PragmaToken.getIdentifierInfo()->getName() << getName()
<< ListKeywords(KeywordValueMap);
return false;
}
// ... and there must be no value for this keyword yet
if (!Entry->second.empty()) {
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"more than one '%0' for '#pragma rs %1'"))
<< Entry->first << getName();
return false;
}
PP.LexUnexpandedToken(PragmaToken);
// The current token must be clang::tok::l_paren
if (PragmaToken.isNot(clang::tok::l_paren)) {
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"missing '(' after '%0' for '#pragma rs %1'"))
<< Entry->first << getName();
return false;
}
PP.LexUnexpandedToken(PragmaToken);
// The current token must be an identifier (a name)
if (PragmaToken.isNot(clang::tok::identifier)) {
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"missing name after '%0(' for '#pragma rs %1'"))
<< Entry->first << getName();
return false;
}
const std::string Name = PragmaToken.getIdentifierInfo()->getName();
PP.LexUnexpandedToken(PragmaToken);
// The current token must be clang::tok::r_paren
if (PragmaToken.isNot(clang::tok::r_paren)) {
PP.Diag(PragmaToken.getLocation(),
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"missing ')' after '%0(%1' for '#pragma rs %2'"))
<< Entry->first << Name << getName();
return false;
}
PP.LexUnexpandedToken(PragmaToken);
// Success
Entry->second = Name;
return true;
}
};
class RSReflectLicensePragmaHandler : public RSPragmaHandler {
private:
void handleItem(const std::string &Item) {
mContext->addPragma(this->getName(), Item);
mContext->setLicenseNote(Item);
}
public:
RSReflectLicensePragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) { }
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &FirstToken) {
this->handleOptionalStringLiteralParamPragma(PP, FirstToken);
}
};
class RSVersionPragmaHandler : public RSPragmaHandler {
private:
void handleInt(clang::Preprocessor &PP,
clang::Token &Tok,
const int v) {
if (v != 1) {
PP.Diag(Tok,
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"pragma for version in source file must be set to 1"));
mContext->setVersion(1);
return;
}
std::stringstream ss;
ss << v;
mContext->addPragma(this->getName(), ss.str());
mContext->setVersion(v);
}
public:
RSVersionPragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) { }
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &FirstToken) {
this->handleIntegerParamPragma(PP, FirstToken);
}
};
// Handles the pragmas rs_fp_full, rs_fp_relaxed, and rs_fp_imprecise.
// There's one instance of this handler for each of the above values.
// Only getName() differs between the instances.
class RSPrecisionPragmaHandler : public RSPragmaHandler {
public:
RSPrecisionPragmaHandler(llvm::StringRef Name, RSContext *Context)
: RSPragmaHandler(Name, Context) {}
void HandlePragma(clang::Preprocessor &PP,
clang::PragmaIntroducerKind Introducer,
clang::Token &Token) {
std::string Precision = getName();
// We are deprecating rs_fp_imprecise.
if (Precision == "rs_fp_imprecise") {
PP.Diag(Token, PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Warning,
"rs_fp_imprecise is deprecated. Assuming "
"rs_fp_relaxed instead."));
Precision = "rs_fp_relaxed";
}
// Check if we have already encountered a precision pragma already.
std::string PreviousPrecision = mContext->getPrecision();
if (!PreviousPrecision.empty()) {
// If the previous specified a different value, it's an error.
if (PreviousPrecision != Precision) {
PP.Diag(Token, PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"Multiple float precisions specified. Encountered "
"%0 previously."))
<< PreviousPrecision;
}
// Otherwise we ignore redundant entries.
return;
}
mContext->addPragma(Precision, "");
mContext->setPrecision(Precision);
}
};
} // namespace
void RSPragmaHandler::handleItemListPragma(clang::Preprocessor &PP,
clang::Token &FirstToken) {
clang::Token &PragmaToken = FirstToken;
// Skip first token, like "export_var"
PP.LexUnexpandedToken(PragmaToken);
// Now, the current token must be clang::tok::lpara
if (PragmaToken.isNot(clang::tok::l_paren))
return;
while (PragmaToken.isNot(clang::tok::eod)) {
// Lex variable name
PP.LexUnexpandedToken(PragmaToken);
if (PragmaToken.is(clang::tok::identifier))
this->handleItem(PP.getSpelling(PragmaToken));
else
break;
slangAssert(PragmaToken.isNot(clang::tok::eod));
PP.LexUnexpandedToken(PragmaToken);
if (PragmaToken.isNot(clang::tok::comma))
break;
}
}
void RSPragmaHandler::handleNonParamPragma(clang::Preprocessor &PP,
clang::Token &FirstToken) {
clang::Token &PragmaToken = FirstToken;
// Skip first token, like "export_var_all"
PP.LexUnexpandedToken(PragmaToken);
// Should be end immediately
if (PragmaToken.isNot(clang::tok::eod))
if (PragmaToken.isNot(clang::tok::r_paren)) {
PP.Diag(PragmaToken,
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"expected a ')'"));
return;
}
}
void RSPragmaHandler::handleOptionalStringLiteralParamPragma(
clang::Preprocessor &PP, clang::Token &FirstToken) {
clang::Token &PragmaToken = FirstToken;
// Skip first token, like "set_reflect_license"
PP.LexUnexpandedToken(PragmaToken);
// Now, the current token must be clang::tok::lpara
if (PragmaToken.isNot(clang::tok::l_paren))
return;
// If not ')', eat the following string literal as the license
PP.LexUnexpandedToken(PragmaToken);
if (PragmaToken.isNot(clang::tok::r_paren)) {
// Eat the whole string literal
clang::StringLiteralParser StringLiteral(PragmaToken, PP);
if (StringLiteral.hadError) {
// Diagnostics will be generated automatically
return;
} else {
this->handleItem(std::string(StringLiteral.GetString()));
}
// The current token should be clang::tok::r_para
PP.LexUnexpandedToken(PragmaToken);
if (PragmaToken.isNot(clang::tok::r_paren)) {
PP.Diag(PragmaToken,
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"expected a ')'"));
return;
}
} else {
// If no argument, remove the license
this->handleItem("");
}
}
void RSPragmaHandler::handleIntegerParamPragma(
clang::Preprocessor &PP, clang::Token &FirstToken) {
clang::Token &PragmaToken = FirstToken;
// Skip first token, like "version"
PP.LexUnexpandedToken(PragmaToken);
// Now, the current token must be clang::tok::lpara
if (PragmaToken.isNot(clang::tok::l_paren)) {
// If no argument, set the version to 0
this->handleInt(PP, PragmaToken, 0);
return;
}
PP.LexUnexpandedToken(PragmaToken);
if (PragmaToken.is(clang::tok::numeric_constant)) {
llvm::SmallString<128> SpellingBuffer;
SpellingBuffer.resize(PragmaToken.getLength() + 1);
llvm::StringRef TokSpelling = PP.getSpelling(PragmaToken, SpellingBuffer);
clang::NumericLiteralParser NumericLiteral(TokSpelling,
PragmaToken.getLocation(), PP);
if (NumericLiteral.hadError) {
// Diagnostics will be generated automatically
return;
} else {
llvm::APInt Val(32, 0);
NumericLiteral.GetIntegerValue(Val);
this->handleInt(PP, PragmaToken, static_cast<int>(Val.getSExtValue()));
}
PP.LexUnexpandedToken(PragmaToken);
} else {
// If no argument, set the version to 0
this->handleInt(PP, PragmaToken, 0);
}
if (PragmaToken.isNot(clang::tok::r_paren)) {
PP.Diag(PragmaToken,
PP.getDiagnostics().getCustomDiagID(
clang::DiagnosticsEngine::Error,
"expected a ')'"));
return;
}
do {
PP.LexUnexpandedToken(PragmaToken);
} while (PragmaToken.isNot(clang::tok::eod));
}
void AddPragmaHandlers(clang::Preprocessor &PP, RSContext *RsContext) {
// For #pragma rs export_type
PP.AddPragmaHandler("rs",
new RSExportTypePragmaHandler("export_type", RsContext));
// For #pragma rs java_package_name
PP.AddPragmaHandler(
"rs", new RSJavaPackageNamePragmaHandler("java_package_name", RsContext));
// For #pragma rs reduce
PP.AddPragmaHandler(
"rs", new RSReducePragmaHandler(RSExportReduce::KeyReduce, RsContext));
// For #pragma rs set_reflect_license
PP.AddPragmaHandler(
"rs", new RSReflectLicensePragmaHandler("set_reflect_license", RsContext));
// For #pragma version
PP.AddPragmaHandler(new RSVersionPragmaHandler("version", RsContext));
// For #pragma rs_fp*
PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_full", RsContext));
PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_relaxed", RsContext));
PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_imprecise", RsContext));
}
} // namespace slang