slang_rs_pragma_handler.cpp revision 46a0334d8901576e20db68e15930a19f8f789cb8
1/* 2 * Copyright 2010, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "slang_rs_pragma_handler.h" 18 19#include <sstream> 20#include <string> 21 22#include "clang/AST/ASTContext.h" 23#include "clang/AST/Attr.h" 24 25#include "clang/Basic/TokenKinds.h" 26 27#include "clang/Lex/LiteralSupport.h" 28#include "clang/Lex/Preprocessor.h" 29#include "clang/Lex/Token.h" 30 31#include "slang_assert.h" 32#include "slang_rs_context.h" 33#include "slang_rs_export_reduce.h" 34#include "slang_version.h" 35 36namespace slang { 37 38namespace { // Anonymous namespace 39 40class RSExportTypePragmaHandler : public RSPragmaHandler { 41 private: 42 void handleItem(const std::string &Item) { 43 mContext->addPragma(this->getName(), Item); 44 mContext->addExportType(Item); 45 } 46 47 public: 48 RSExportTypePragmaHandler(llvm::StringRef Name, RSContext *Context) 49 : RSPragmaHandler(Name, Context) { } 50 51 void HandlePragma(clang::Preprocessor &PP, 52 clang::PragmaIntroducerKind Introducer, 53 clang::Token &FirstToken) { 54 this->handleItemListPragma(PP, FirstToken); 55 } 56}; 57 58class RSJavaPackageNamePragmaHandler : public RSPragmaHandler { 59 public: 60 RSJavaPackageNamePragmaHandler(llvm::StringRef Name, RSContext *Context) 61 : RSPragmaHandler(Name, Context) { } 62 63 void HandlePragma(clang::Preprocessor &PP, 64 clang::PragmaIntroducerKind Introducer, 65 clang::Token &FirstToken) { 66 // FIXME: Need to validate the extracted package name from pragma. 67 // Currently "all chars" specified in pragma will be treated as package 68 // name. 69 // 70 // 18.1 The Grammar of the Java Programming Language 71 // (http://java.sun.com/docs/books/jls/third_edition/html/syntax.html#18.1) 72 // 73 // CompilationUnit: 74 // [[Annotations] package QualifiedIdentifier ; ] {ImportDeclaration} 75 // {TypeDeclaration} 76 // 77 // QualifiedIdentifier: 78 // Identifier { . Identifier } 79 // 80 // Identifier: 81 // IDENTIFIER 82 // 83 // 3.8 Identifiers 84 // (http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.8) 85 // 86 // 87 88 clang::Token &PragmaToken = FirstToken; 89 std::string PackageName; 90 91 // Skip first token, "java_package_name" 92 PP.LexUnexpandedToken(PragmaToken); 93 94 // Now, the current token must be clang::tok::lpara 95 if (PragmaToken.isNot(clang::tok::l_paren)) 96 return; 97 98 while (PragmaToken.isNot(clang::tok::eod)) { 99 // Lex package name 100 PP.LexUnexpandedToken(PragmaToken); 101 102 bool Invalid; 103 std::string Spelling = PP.getSpelling(PragmaToken, &Invalid); 104 if (!Invalid) 105 PackageName.append(Spelling); 106 107 // Pre-mature end (syntax error will be triggered by preprocessor later) 108 if (PragmaToken.is(clang::tok::eod) || PragmaToken.is(clang::tok::eof)) { 109 break; 110 } else { 111 // Next token is ')' (end of pragma) 112 const clang::Token &NextTok = PP.LookAhead(0); 113 if (NextTok.is(clang::tok::r_paren)) { 114 mContext->addPragma(this->getName(), PackageName); 115 mContext->setReflectJavaPackageName(PackageName); 116 // Lex until meets clang::tok::eod 117 do { 118 PP.LexUnexpandedToken(PragmaToken); 119 } while (PragmaToken.isNot(clang::tok::eod)); 120 break; 121 } 122 } 123 } 124 } 125}; 126 127class RSReducePragmaHandler : public RSPragmaHandler { 128 public: 129 RSReducePragmaHandler(llvm::StringRef Name, RSContext *Context) 130 : RSPragmaHandler(Name, Context) { } 131 132 void HandlePragma(clang::Preprocessor &PP, 133 clang::PragmaIntroducerKind Introducer, 134 clang::Token &FirstToken) override { 135 // #pragma rs reduce(name) 136 // initializer(initializename) 137 // accumulator(accumulatename) 138 // combiner(combinename) 139 // outconverter(outconvertname) 140 // halter(haltname) 141 142 const clang::SourceLocation PragmaLocation = FirstToken.getLocation(); 143 144 clang::Token &PragmaToken = FirstToken; 145 146 // Grab "reduce(name)" ("reduce" is already known to be the first 147 // token) and all the "keyword(value)" contributions 148 KeywordValueMapType KeywordValueMap({std::make_pair(RSExportReduceNew::KeyReduce, ""), 149 std::make_pair(RSExportReduceNew::KeyInitializer, ""), 150 std::make_pair(RSExportReduceNew::KeyAccumulator, ""), 151 std::make_pair(RSExportReduceNew::KeyCombiner, ""), 152 std::make_pair(RSExportReduceNew::KeyOutConverter, "")}); 153 if (mContext->getTargetAPI() >= SLANG_FEATURE_GENERAL_REDUCTION_HALTER_API) { 154 // Halter functionality has not been released, nor has its 155 // specification been finalized with partners. We do not have a 156 // specification that extends through the full RenderScript 157 // software stack, either. 158 KeywordValueMap.insert(std::make_pair(RSExportReduceNew::KeyHalter, "")); 159 } 160 while (PragmaToken.is(clang::tok::identifier)) { 161 if (!ProcessKeywordAndValue(PP, PragmaToken, KeywordValueMap)) 162 return; 163 } 164 165 // Make sure there's no end-of-line garbage 166 if (PragmaToken.isNot(clang::tok::eod)) { 167 PP.Diag(PragmaToken.getLocation(), 168 PP.getDiagnostics().getCustomDiagID( 169 clang::DiagnosticsEngine::Error, 170 "did not expect '%0' here for '#pragma rs %1'")) 171 << PP.getSpelling(PragmaToken) << getName(); 172 return; 173 } 174 175 // Make sure we have an accumulator 176 if (KeywordValueMap[RSExportReduceNew::KeyAccumulator].empty()) { 177 PP.Diag(PragmaLocation, PP.getDiagnostics().getCustomDiagID( 178 clang::DiagnosticsEngine::Error, 179 "missing '%0' for '#pragma rs %1'")) 180 << RSExportReduceNew::KeyAccumulator << getName(); 181 return; 182 } 183 184 // Make sure the reduction kernel name is unique. (If we were 185 // worried there might be a VERY large number of pragmas, then we 186 // could do something more efficient than walking a list to search 187 // for duplicates.) 188 for (auto I = mContext->export_reduce_new_begin(), 189 E = mContext->export_reduce_new_end(); 190 I != E; ++I) { 191 if ((*I)->getNameReduce() == KeywordValueMap[RSExportReduceNew::KeyReduce]) { 192 PP.Diag(PragmaLocation, PP.getDiagnostics().getCustomDiagID( 193 clang::DiagnosticsEngine::Error, 194 "reduction kernel '%0' declared multiple " 195 "times (first one is at %1)")) 196 << KeywordValueMap[RSExportReduceNew::KeyReduce] 197 << (*I)->getLocation().printToString(PP.getSourceManager()); 198 return; 199 } 200 } 201 202 // Check API version. 203 if (mContext->getTargetAPI() < SLANG_FEATURE_GENERAL_REDUCTION_API) { 204 PP.Diag(PragmaLocation, 205 PP.getDiagnostics().getCustomDiagID( 206 clang::DiagnosticsEngine::Error, 207 "reduction kernels are not supported in SDK levels %0-%1")) 208 << SLANG_MINIMUM_TARGET_API 209 << (SLANG_FEATURE_GENERAL_REDUCTION_API - 1); 210 return; 211 } 212 213 // Handle backward reference from pragma (see Backend::HandleTopLevelDecl for forward reference). 214 MarkUsed(PP, KeywordValueMap[RSExportReduceNew::KeyInitializer]); 215 MarkUsed(PP, KeywordValueMap[RSExportReduceNew::KeyAccumulator]); 216 MarkUsed(PP, KeywordValueMap[RSExportReduceNew::KeyCombiner]); 217 MarkUsed(PP, KeywordValueMap[RSExportReduceNew::KeyOutConverter]); 218 MarkUsed(PP, KeywordValueMap[RSExportReduceNew::KeyHalter]); 219 220 mContext->addExportReduceNew(RSExportReduceNew::Create(mContext, PragmaLocation, 221 KeywordValueMap[RSExportReduceNew::KeyReduce], 222 KeywordValueMap[RSExportReduceNew::KeyInitializer], 223 KeywordValueMap[RSExportReduceNew::KeyAccumulator], 224 KeywordValueMap[RSExportReduceNew::KeyCombiner], 225 KeywordValueMap[RSExportReduceNew::KeyOutConverter], 226 KeywordValueMap[RSExportReduceNew::KeyHalter])); 227 } 228 229 private: 230 typedef std::map<std::string, std::string> KeywordValueMapType; 231 232 void MarkUsed(clang::Preprocessor &PP, const std::string &FunctionName) { 233 if (FunctionName.empty()) 234 return; 235 236 clang::ASTContext &ASTC = mContext->getASTContext(); 237 clang::TranslationUnitDecl *TUDecl = ASTC.getTranslationUnitDecl(); 238 slangAssert(TUDecl); 239 if (const clang::IdentifierInfo *II = PP.getIdentifierInfo(FunctionName)) { 240 for (auto Decl : TUDecl->lookup(II)) { 241 clang::FunctionDecl *FDecl = Decl->getAsFunction(); 242 if (!FDecl || !FDecl->isThisDeclarationADefinition()) 243 continue; 244 if (!FDecl->hasAttr<clang::UsedAttr>()) { 245 // Handle backward reference from pragma (see RSReducePragmaHandler::HandlePragma 246 // for forward reference). 247 FDecl->addAttr(clang::UsedAttr::CreateImplicit(ASTC)); 248 } 249 } 250 } 251 } 252 253 // Return comma-separated list of all keys in the map 254 static std::string ListKeywords(const KeywordValueMapType &KeywordValueMap) { 255 std::string Ret; 256 bool First = true; 257 for (auto const &entry : KeywordValueMap) { 258 if (First) 259 First = false; 260 else 261 Ret += ", "; 262 Ret += "'"; 263 Ret += entry.first; 264 Ret += "'"; 265 } 266 return Ret; 267 } 268 269 // Parse "keyword(value)" and set KeywordValueMap[keyword] = value. (Both 270 // "keyword" and "value" are identifiers.) 271 // Does both syntactic validation and the following semantic validation: 272 // - The keyword must be present in the map. 273 // - The map entry for the keyword must not contain a value. 274 bool ProcessKeywordAndValue(clang::Preprocessor &PP, 275 clang::Token &PragmaToken, 276 KeywordValueMapType &KeywordValueMap) { 277 // The current token must be an identifier in KeywordValueMap 278 KeywordValueMapType::iterator Entry; 279 if (PragmaToken.isNot(clang::tok::identifier) || 280 ((Entry = KeywordValueMap.find( 281 PragmaToken.getIdentifierInfo()->getName())) == 282 KeywordValueMap.end())) { 283 // Note that we should never get here for the "reduce" token 284 // itself, which should already have been recognized. 285 PP.Diag(PragmaToken.getLocation(), 286 PP.getDiagnostics().getCustomDiagID( 287 clang::DiagnosticsEngine::Error, 288 "did not recognize '%0' for '#pragma %1'; expected one of " 289 "the following keywords: %2")) 290 << PragmaToken.getIdentifierInfo()->getName() << getName() 291 << ListKeywords(KeywordValueMap); 292 return false; 293 } 294 // ... and there must be no value for this keyword yet 295 if (!Entry->second.empty()) { 296 PP.Diag(PragmaToken.getLocation(), 297 PP.getDiagnostics().getCustomDiagID( 298 clang::DiagnosticsEngine::Error, 299 "more than one '%0' for '#pragma rs %1'")) 300 << Entry->first << getName(); 301 return false; 302 } 303 PP.LexUnexpandedToken(PragmaToken); 304 305 // The current token must be clang::tok::l_paren 306 if (PragmaToken.isNot(clang::tok::l_paren)) { 307 PP.Diag(PragmaToken.getLocation(), 308 PP.getDiagnostics().getCustomDiagID( 309 clang::DiagnosticsEngine::Error, 310 "missing '(' after '%0' for '#pragma rs %1'")) 311 << Entry->first << getName(); 312 return false; 313 } 314 PP.LexUnexpandedToken(PragmaToken); 315 316 // The current token must be an identifier (a name) 317 if (PragmaToken.isNot(clang::tok::identifier)) { 318 PP.Diag(PragmaToken.getLocation(), 319 PP.getDiagnostics().getCustomDiagID( 320 clang::DiagnosticsEngine::Error, 321 "missing name after '%0(' for '#pragma rs %1'")) 322 << Entry->first << getName(); 323 return false; 324 } 325 const std::string Name = PragmaToken.getIdentifierInfo()->getName(); 326 PP.LexUnexpandedToken(PragmaToken); 327 328 // The current token must be clang::tok::r_paren 329 if (PragmaToken.isNot(clang::tok::r_paren)) { 330 PP.Diag(PragmaToken.getLocation(), 331 PP.getDiagnostics().getCustomDiagID( 332 clang::DiagnosticsEngine::Error, 333 "missing ')' after '%0(%1' for '#pragma rs %2'")) 334 << Entry->first << Name << getName(); 335 return false; 336 } 337 PP.LexUnexpandedToken(PragmaToken); 338 339 // Success 340 Entry->second = Name; 341 return true; 342 } 343}; 344 345class RSReflectLicensePragmaHandler : public RSPragmaHandler { 346 private: 347 void handleItem(const std::string &Item) { 348 mContext->addPragma(this->getName(), Item); 349 mContext->setLicenseNote(Item); 350 } 351 352 public: 353 RSReflectLicensePragmaHandler(llvm::StringRef Name, RSContext *Context) 354 : RSPragmaHandler(Name, Context) { } 355 356 void HandlePragma(clang::Preprocessor &PP, 357 clang::PragmaIntroducerKind Introducer, 358 clang::Token &FirstToken) { 359 this->handleOptionalStringLiteralParamPragma(PP, FirstToken); 360 } 361}; 362 363class RSVersionPragmaHandler : public RSPragmaHandler { 364 private: 365 void handleInt(clang::Preprocessor &PP, 366 clang::Token &Tok, 367 const int v) { 368 if (v != 1) { 369 PP.Diag(Tok, 370 PP.getDiagnostics().getCustomDiagID( 371 clang::DiagnosticsEngine::Error, 372 "pragma for version in source file must be set to 1")); 373 mContext->setVersion(1); 374 return; 375 } 376 std::stringstream ss; 377 ss << v; 378 mContext->addPragma(this->getName(), ss.str()); 379 mContext->setVersion(v); 380 } 381 382 public: 383 RSVersionPragmaHandler(llvm::StringRef Name, RSContext *Context) 384 : RSPragmaHandler(Name, Context) { } 385 386 void HandlePragma(clang::Preprocessor &PP, 387 clang::PragmaIntroducerKind Introducer, 388 clang::Token &FirstToken) { 389 this->handleIntegerParamPragma(PP, FirstToken); 390 } 391}; 392 393// Handles the pragmas rs_fp_full, rs_fp_relaxed, and rs_fp_imprecise. 394// There's one instance of this handler for each of the above values. 395// Only getName() differs between the instances. 396class RSPrecisionPragmaHandler : public RSPragmaHandler { 397public: 398 RSPrecisionPragmaHandler(llvm::StringRef Name, RSContext *Context) 399 : RSPragmaHandler(Name, Context) {} 400 401 void HandlePragma(clang::Preprocessor &PP, 402 clang::PragmaIntroducerKind Introducer, 403 clang::Token &Token) { 404 std::string Precision = getName(); 405 // We are deprecating rs_fp_imprecise. 406 if (Precision == "rs_fp_imprecise") { 407 PP.Diag(Token, PP.getDiagnostics().getCustomDiagID( 408 clang::DiagnosticsEngine::Warning, 409 "rs_fp_imprecise is deprecated. Assuming " 410 "rs_fp_relaxed instead.")); 411 Precision = "rs_fp_relaxed"; 412 } 413 // Check if we have already encountered a precision pragma already. 414 std::string PreviousPrecision = mContext->getPrecision(); 415 if (!PreviousPrecision.empty()) { 416 // If the previous specified a different value, it's an error. 417 if (PreviousPrecision != Precision) { 418 PP.Diag(Token, PP.getDiagnostics().getCustomDiagID( 419 clang::DiagnosticsEngine::Error, 420 "Multiple float precisions specified. Encountered " 421 "%0 previously.")) 422 << PreviousPrecision; 423 } 424 // Otherwise we ignore redundant entries. 425 return; 426 } 427 428 mContext->addPragma(Precision, ""); 429 mContext->setPrecision(Precision); 430 } 431}; 432 433} // namespace 434 435void RSPragmaHandler::handleItemListPragma(clang::Preprocessor &PP, 436 clang::Token &FirstToken) { 437 clang::Token &PragmaToken = FirstToken; 438 439 // Skip first token, like "export_var" 440 PP.LexUnexpandedToken(PragmaToken); 441 442 // Now, the current token must be clang::tok::lpara 443 if (PragmaToken.isNot(clang::tok::l_paren)) 444 return; 445 446 while (PragmaToken.isNot(clang::tok::eod)) { 447 // Lex variable name 448 PP.LexUnexpandedToken(PragmaToken); 449 if (PragmaToken.is(clang::tok::identifier)) 450 this->handleItem(PP.getSpelling(PragmaToken)); 451 else 452 break; 453 454 slangAssert(PragmaToken.isNot(clang::tok::eod)); 455 456 PP.LexUnexpandedToken(PragmaToken); 457 458 if (PragmaToken.isNot(clang::tok::comma)) 459 break; 460 } 461} 462 463void RSPragmaHandler::handleNonParamPragma(clang::Preprocessor &PP, 464 clang::Token &FirstToken) { 465 clang::Token &PragmaToken = FirstToken; 466 467 // Skip first token, like "export_var_all" 468 PP.LexUnexpandedToken(PragmaToken); 469 470 // Should be end immediately 471 if (PragmaToken.isNot(clang::tok::eod)) 472 if (PragmaToken.isNot(clang::tok::r_paren)) { 473 PP.Diag(PragmaToken, 474 PP.getDiagnostics().getCustomDiagID( 475 clang::DiagnosticsEngine::Error, 476 "expected a ')'")); 477 return; 478 } 479} 480 481void RSPragmaHandler::handleOptionalStringLiteralParamPragma( 482 clang::Preprocessor &PP, clang::Token &FirstToken) { 483 clang::Token &PragmaToken = FirstToken; 484 485 // Skip first token, like "set_reflect_license" 486 PP.LexUnexpandedToken(PragmaToken); 487 488 // Now, the current token must be clang::tok::lpara 489 if (PragmaToken.isNot(clang::tok::l_paren)) 490 return; 491 492 // If not ')', eat the following string literal as the license 493 PP.LexUnexpandedToken(PragmaToken); 494 if (PragmaToken.isNot(clang::tok::r_paren)) { 495 // Eat the whole string literal 496 clang::StringLiteralParser StringLiteral(PragmaToken, PP); 497 if (StringLiteral.hadError) { 498 // Diagnostics will be generated automatically 499 return; 500 } else { 501 this->handleItem(std::string(StringLiteral.GetString())); 502 } 503 504 // The current token should be clang::tok::r_para 505 PP.LexUnexpandedToken(PragmaToken); 506 if (PragmaToken.isNot(clang::tok::r_paren)) { 507 PP.Diag(PragmaToken, 508 PP.getDiagnostics().getCustomDiagID( 509 clang::DiagnosticsEngine::Error, 510 "expected a ')'")); 511 return; 512 } 513 } else { 514 // If no argument, remove the license 515 this->handleItem(""); 516 } 517} 518 519void RSPragmaHandler::handleIntegerParamPragma( 520 clang::Preprocessor &PP, clang::Token &FirstToken) { 521 clang::Token &PragmaToken = FirstToken; 522 523 // Skip first token, like "version" 524 PP.LexUnexpandedToken(PragmaToken); 525 526 // Now, the current token must be clang::tok::lpara 527 if (PragmaToken.isNot(clang::tok::l_paren)) { 528 // If no argument, set the version to 0 529 this->handleInt(PP, PragmaToken, 0); 530 return; 531 } 532 PP.LexUnexpandedToken(PragmaToken); 533 534 if (PragmaToken.is(clang::tok::numeric_constant)) { 535 llvm::SmallString<128> SpellingBuffer; 536 SpellingBuffer.resize(PragmaToken.getLength() + 1); 537 llvm::StringRef TokSpelling = PP.getSpelling(PragmaToken, SpellingBuffer); 538 clang::NumericLiteralParser NumericLiteral(TokSpelling, 539 PragmaToken.getLocation(), PP); 540 if (NumericLiteral.hadError) { 541 // Diagnostics will be generated automatically 542 return; 543 } else { 544 llvm::APInt Val(32, 0); 545 NumericLiteral.GetIntegerValue(Val); 546 this->handleInt(PP, PragmaToken, static_cast<int>(Val.getSExtValue())); 547 } 548 PP.LexUnexpandedToken(PragmaToken); 549 } else { 550 // If no argument, set the version to 0 551 this->handleInt(PP, PragmaToken, 0); 552 } 553 554 if (PragmaToken.isNot(clang::tok::r_paren)) { 555 PP.Diag(PragmaToken, 556 PP.getDiagnostics().getCustomDiagID( 557 clang::DiagnosticsEngine::Error, 558 "expected a ')'")); 559 return; 560 } 561 562 do { 563 PP.LexUnexpandedToken(PragmaToken); 564 } while (PragmaToken.isNot(clang::tok::eod)); 565} 566 567void AddPragmaHandlers(clang::Preprocessor &PP, RSContext *RsContext) { 568 // For #pragma rs export_type 569 PP.AddPragmaHandler("rs", 570 new RSExportTypePragmaHandler("export_type", RsContext)); 571 572 // For #pragma rs java_package_name 573 PP.AddPragmaHandler( 574 "rs", new RSJavaPackageNamePragmaHandler("java_package_name", RsContext)); 575 576 // For #pragma rs reduce 577 PP.AddPragmaHandler( 578 "rs", new RSReducePragmaHandler(RSExportReduceNew::KeyReduce, RsContext)); 579 580 // For #pragma rs set_reflect_license 581 PP.AddPragmaHandler( 582 "rs", new RSReflectLicensePragmaHandler("set_reflect_license", RsContext)); 583 584 // For #pragma version 585 PP.AddPragmaHandler(new RSVersionPragmaHandler("version", RsContext)); 586 587 // For #pragma rs_fp* 588 PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_full", RsContext)); 589 PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_relaxed", RsContext)); 590 PP.AddPragmaHandler(new RSPrecisionPragmaHandler("rs_fp_imprecise", RsContext)); 591} 592 593 594} // namespace slang 595