/* * 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