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