| /* |
| * Copyright 2017, 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 <algorithm> |
| #include <iostream> |
| #include <string> |
| |
| #include "clang/AST/APValue.h" |
| |
| #include "slang_assert.h" |
| #include "slang_rs_export_foreach.h" |
| #include "slang_rs_export_func.h" |
| #include "slang_rs_export_reduce.h" |
| #include "slang_rs_export_type.h" |
| #include "slang_rs_export_var.h" |
| #include "slang_rs_reflection.h" |
| #include "slang_rs_reflection_state.h" |
| |
| #include "bcinfo/MetadataExtractor.h" |
| |
| namespace slang { |
| |
| static bool equal(const clang::APValue &a, const clang::APValue &b) { |
| if (a.getKind() != b.getKind()) |
| return false; |
| switch (a.getKind()) { |
| case clang::APValue::Float: |
| return a.getFloat().bitwiseIsEqual(b.getFloat()); |
| case clang::APValue::Int: |
| return a.getInt() == b.getInt(); |
| case clang::APValue::Vector: { |
| unsigned NumElements = a.getVectorLength(); |
| if (NumElements != b.getVectorLength()) |
| return false; |
| for (unsigned i = 0; i < NumElements; ++i) { |
| if (!equal(a.getVectorElt(i), b.getVectorElt(i))) |
| return false; |
| } |
| return true; |
| } |
| default: |
| slangAssert(false && "unexpected APValue kind"); |
| return false; |
| } |
| } |
| |
| ReflectionState::~ReflectionState() { |
| slangAssert(mState==S_Initial || mState==S_ClosedJava64 || mState==S_Bad); |
| delete mStringSet; |
| } |
| |
| void ReflectionState::openJava32(size_t NumFiles) { |
| if (kDisabled) |
| return; |
| slangAssert(mState==S_Initial); |
| mState = S_OpenJava32; |
| mStringSet = new llvm::StringSet<>; |
| mFiles.BeginCollecting(NumFiles); |
| } |
| |
| void ReflectionState::closeJava32() { |
| if (kDisabled) |
| return; |
| slangAssert(mState==S_OpenJava32 && (mForEachOpen < 0) && !mOutputClassOpen && (mRecordsState != RS_Open)); |
| mState = S_ClosedJava32; |
| mRSC = nullptr; |
| } |
| |
| void ReflectionState::openJava64() { |
| if (kDisabled) |
| return; |
| slangAssert(mState==S_ClosedJava32); |
| mState = S_OpenJava64; |
| mFiles.BeginUsing(); |
| } |
| |
| void ReflectionState::closeJava64() { |
| if (kDisabled) |
| return; |
| slangAssert(mState==S_OpenJava64 && (mForEachOpen < 0) && !mOutputClassOpen && (mRecordsState != RS_Open)); |
| mState = S_ClosedJava64; |
| mRSC = nullptr; |
| } |
| |
| llvm::StringRef ReflectionState::canon(const std::string &String) { |
| slangAssert(isCollecting()); |
| // NOTE: llvm::StringSet does not permit the empty string as a member |
| return String.empty() ? llvm::StringRef() : mStringSet->insert(String).first->getKey(); |
| } |
| |
| std::string ReflectionState::getUniqueTypeName(const RSExportType *T) { |
| return RSReflectionJava::GetTypeName(T, RSReflectionJava::TypeNamePseudoC); |
| } |
| |
| void ReflectionState::nextFile(const RSContext *RSC, |
| const std::string &PackageName, |
| const std::string &RSSourceFileName) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| mRSC = RSC; |
| |
| slangAssert(mRecordsState != RS_Open); |
| mRecordsState = RS_Initial; |
| |
| if (isCollecting()) { |
| File &file = mFiles.CollectNext(); |
| file.mPackageName = PackageName; |
| file.mRSSourceFileName = RSSourceFileName; |
| } |
| if (isUsing()) { |
| File &file = mFiles.UseNext(); |
| slangAssert(file.mRSSourceFileName == RSSourceFileName); |
| if (file.mPackageName != PackageName) |
| mRSC->ReportError("in file '%0' Java package name is '%1' for 32-bit targets " |
| "but '%2' for 64-bit targets") |
| << RSSourceFileName << file.mPackageName << PackageName; |
| } |
| } |
| |
| void ReflectionState::dump() { |
| const size_t NumFiles = mFiles.Size(); |
| for (int i = 0; i < NumFiles; ++i) { |
| const File &file = mFiles[i]; |
| std::cout << "file = \"" << file.mRSSourceFileName << "\", " |
| << "package = \"" << file.mPackageName << "\"" << std::endl; |
| |
| // NOTE: "StringMap iteration order, however, is not guaranteed to |
| // be deterministic". So sort before dumping. |
| typedef const llvm::StringMap<File::Record>::MapEntryTy *RecordsEntryTy; |
| std::vector<RecordsEntryTy> Records; |
| Records.reserve(file.mRecords.size()); |
| for (auto I = file.mRecords.begin(), E = file.mRecords.end(); I != E; I++) |
| Records.push_back(&(*I)); |
| std::sort(Records.begin(), Records.end(), |
| [](RecordsEntryTy a, RecordsEntryTy b) { return a->getKey().compare(b->getKey())==-1; }); |
| for (auto Record : Records) { |
| const auto &Val = Record->getValue(); |
| std::cout << " (Record) name=\"" << Record->getKey().str() << "\"" |
| << " allocSize=" << Val.mAllocSize |
| << " postPadding=" << Val.mPostPadding |
| << " ordinary=" << Val.mOrdinary |
| << " matchedByName=" << Val.mMatchedByName |
| << std::endl; |
| const size_t NumFields = Val.mFieldCount; |
| for (int fieldIdx = 0; fieldIdx < NumFields; ++fieldIdx) { |
| const auto &field = Val.mFields[fieldIdx]; |
| std::cout << " (Field) name=\"" << field.mName << "\" (" |
| << field.mPrePadding << ", \"" << field.mType.str() |
| << "\"(" << field.mStoreSize << ")@" << field.mOffset |
| << ", " << field.mPostPadding << ")" << std::endl; |
| } |
| } |
| |
| const size_t NumVars = file.mVariables.Size(); |
| for (int varIdx = 0; varIdx < NumVars; ++varIdx) { |
| const auto &var = file.mVariables[varIdx]; |
| std::cout << " (Var) name=\"" << var.mName << "\" type=\"" << var.mType.str() |
| << "\" const=" << var.mIsConst << " initialized=" << (var.mInitializerCount != 0) |
| << " allocSize=" << var.mAllocSize << std::endl; |
| } |
| |
| for (int feIdx = 0; feIdx < file.mForEachCount; ++feIdx) { |
| const auto &fe = file.mForEaches[feIdx]; |
| std::cout << " (ForEach) ordinal=" << feIdx << " state="; |
| switch (fe.mState) { |
| case File::ForEach::S_Initial: |
| std::cout << "initial" << std::endl; |
| continue; |
| case File::ForEach::S_Collected: |
| std::cout << "collected"; |
| break; |
| case File::ForEach::S_UseMatched: |
| std::cout << "usematched"; |
| break; |
| default: |
| std::cout << fe.mState; |
| break; |
| } |
| std::cout << " name=\"" << fe.mName << "\" kernel=" << fe.mIsKernel |
| << " hasOut=" << fe.mHasOut << " out=\"" << fe.mOut.str() |
| << "\" metadata=0x" << std::hex << fe.mSignatureMetadata << std::dec |
| << std::endl; |
| const size_t NumIns = fe.mIns.Size(); |
| for (int insIdx = 0; insIdx < NumIns; ++insIdx) |
| std::cout << " (In) " << fe.mIns[insIdx].str() << std::endl; |
| const size_t NumParams = fe.mParams.Size(); |
| for (int paramsIdx = 0; paramsIdx < NumParams; ++paramsIdx) |
| std::cout << " (Param) " << fe.mParams[paramsIdx].str() << std::endl; |
| } |
| |
| for (auto feBad : mForEachesBad) { |
| std::cout << " (ForEachBad) ordinal=" << feBad->getOrdinal() |
| << " name=\"" << feBad->getName() << "\"" |
| << std::endl; |
| } |
| |
| const size_t NumInvokables = file.mInvokables.Size(); |
| for (int invIdx = 0; invIdx < NumInvokables; ++invIdx) { |
| const auto &inv = file.mInvokables[invIdx]; |
| std::cout << " (Invokable) name=\"" << inv.mName << "\"" << std::endl; |
| const size_t NumParams = inv.mParamCount; |
| for (int paramsIdx = 0; paramsIdx < NumParams; ++paramsIdx) |
| std::cout << " (Param) " << inv.mParams[paramsIdx].str() << std::endl; |
| } |
| |
| const size_t NumReduces = file.mReduces.Size(); |
| for (int redIdx = 0; redIdx < NumReduces; ++redIdx) { |
| const auto &red = file.mReduces[redIdx]; |
| std::cout << " (Reduce) name=\"" << red.mName |
| << "\" result=\"" << red.mResult.str() |
| << "\" exportable=" << red.mIsExportable |
| << std::endl; |
| const size_t NumIns = red.mAccumInCount; |
| for (int insIdx = 0; insIdx < NumIns; ++insIdx) |
| std::cout << " (In) " << red.mAccumIns[insIdx].str() << std::endl; |
| } |
| } |
| } |
| |
| // ForEach ///////////////////////////////////////////////////////////////////////////////////// |
| |
| void ReflectionState::beginForEaches(size_t Count) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| if (isCollecting()) { |
| auto &file = mFiles.Current(); |
| file.mForEaches = new File::ForEach[Count]; |
| file.mForEachCount = Count; |
| } |
| if (isUsing()) { |
| slangAssert(mForEachesBad.empty()); |
| mNumForEachesMatchedByOrdinal = 0; |
| } |
| } |
| |
| // Keep this in sync with RSReflectionJava::genExportForEach(). |
| void ReflectionState::beginForEach(const RSExportForEach *EF) { |
| slangAssert(!isClosed() && (mForEachOpen < 0)); |
| if (!isActive()) |
| return; |
| |
| const bool IsKernel = EF->isKernelStyle(); |
| const std::string& Name = EF->getName(); |
| const unsigned Ordinal = EF->getOrdinal(); |
| const size_t InCount = EF->getInTypes().size(); |
| const size_t ParamCount = EF->params_count(); |
| |
| const RSExportType *OET = EF->getOutType(); |
| if (OET && !IsKernel) { |
| slangAssert(OET->getClass() == RSExportType::ExportClassPointer); |
| OET = static_cast<const RSExportPointerType *>(OET)->getPointeeType(); |
| } |
| const std::string OutType = (OET ? getUniqueTypeName(OET) : ""); |
| const bool HasOut = (EF->hasOut() || EF->hasReturn()); |
| |
| mForEachOpen = Ordinal; |
| mForEachFatal = true; // we'll set this to false if everything looks ok |
| |
| auto &file = mFiles.Current(); |
| auto &foreaches = file.mForEaches; |
| if (isCollecting()) { |
| slangAssert(Ordinal < file.mForEachCount); |
| auto &foreach = foreaches[Ordinal]; |
| slangAssert(foreach.mState == File::ForEach::S_Initial); |
| foreach.mState = File::ForEach::S_Collected; |
| foreach.mName = Name; |
| foreach.mIns.BeginCollecting(InCount); |
| foreach.mParams.BeginCollecting(ParamCount); |
| foreach.mOut = canon(OutType); |
| foreach.mHasOut = HasOut; |
| foreach.mSignatureMetadata = 0; |
| foreach.mIsKernel = IsKernel; |
| } |
| if (isUsing()) { |
| if (Ordinal >= file.mForEachCount) { |
| mForEachesBad.push_back(EF); |
| return; |
| } |
| |
| auto &foreach = foreaches[Ordinal]; |
| slangAssert(foreach.mState == File::ForEach::S_Collected); |
| foreach.mState = File::ForEach::S_UseMatched; |
| ++mNumForEachesMatchedByOrdinal; |
| |
| if (foreach.mName != Name) { |
| // Order matters because it determines slot number |
| mForEachesBad.push_back(EF); |
| return; |
| } |
| |
| // At this point, we have matching ordinal and matching name. |
| |
| if (foreach.mIsKernel != IsKernel) { |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has __attribute__((kernel)) for %select{32|64}1-bit targets " |
| "but not for %select{64|32}1-bit targets") |
| << Name << IsKernel; |
| return; |
| } |
| |
| if ((foreach.mHasOut != HasOut) || !foreach.mOut.equals(OutType)) { |
| // There are several different patterns we need to handle: |
| // (1) Two different non-void* output types |
| // (2) One non-void* output type, one void* output type |
| // (3) One non-void* output type, one no-output |
| // (4) One void* output type, one no-output |
| if (foreach.mHasOut && HasOut) { |
| if (foreach.mOut.size() && OutType.size()) { |
| // (1) Two different non-void* output types |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has output type '%1' for 32-bit targets " |
| "but output type '%2' for 64-bit targets") |
| << Name << foreach.mOut.str() << OutType; |
| } else { |
| // (2) One non-void* return type, one void* output type |
| const bool hasTyped64 = OutType.size(); |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has output type '%1' for %select{32|64}2-bit targets " |
| "but has untyped output for %select{64|32}2-bit targets") |
| << Name << (foreach.mOut.str() + OutType) << hasTyped64; |
| } |
| } else { |
| const std::string CombinedOutType = (foreach.mOut.str() + OutType); |
| if (CombinedOutType.size()) { |
| // (3) One non-void* output type, one no-output |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has output type '%1' for %select{32|64}2-bit targets " |
| "but no output for %select{64|32}2-bit targets") |
| << Name << CombinedOutType << HasOut; |
| } else { |
| // (4) One void* output type, one no-output |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has untyped output for %select{32|64}1-bit targets " |
| "but no output for %select{64|32}1-bit targets") |
| << Name << HasOut; |
| } |
| } |
| } |
| |
| bool BadCount = false; |
| if (foreach.mIns.Size() != InCount) { |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has %1 input%s1 for 32-bit targets " |
| "but %2 input%s2 for 64-bit targets") |
| << Name << unsigned(foreach.mIns.Size()) << unsigned(InCount); |
| BadCount = true; |
| } |
| if (foreach.mParams.Size() != ParamCount) { |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has %1 usrData parameter%s1 for 32-bit targets " |
| "but %2 usrData parameter%s2 for 64-bit targets") |
| << Name << unsigned(foreach.mParams.Size()) << unsigned(ParamCount); |
| BadCount = true; |
| } |
| |
| if (BadCount) |
| return; |
| |
| foreach.mIns.BeginUsing(); |
| foreach.mParams.BeginUsing(); |
| } |
| |
| mForEachFatal = false; |
| } |
| |
| void ReflectionState::addForEachIn(const RSExportForEach *EF, const RSExportType *Type) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mForEachOpen == EF->getOrdinal()); |
| |
| // Type may be nullptr in the case of void*. See RSExportForEach::Create(). |
| if (Type && !EF->isKernelStyle()) { |
| slangAssert(Type->getClass() == RSExportType::ExportClassPointer); |
| Type = static_cast<const RSExportPointerType *>(Type)->getPointeeType(); |
| } |
| const std::string TypeName = (Type ? getUniqueTypeName(Type) : std::string()); |
| |
| auto &ins = mFiles.Current().mForEaches[EF->getOrdinal()].mIns; |
| if (isCollecting()) { |
| ins.CollectNext() = canon(TypeName); |
| } |
| if (isUsing()) { |
| if (mForEachFatal) |
| return; |
| |
| if (!ins.UseNext().equals(TypeName)) { |
| if (ins.Current().size() && TypeName.size()) { |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 input of foreach kernel '%1' " |
| "has type '%2' for 32-bit targets " |
| "but type '%3' for 64-bit targets") |
| << unsigned(ins.CurrentIdx() + 1) |
| << EF->getName() |
| << ins.Current().str() |
| << TypeName; |
| } else { |
| const bool hasType64 = TypeName.size(); |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 input of foreach kernel '%1' " |
| "has type '%2' for %select{32|64}3-bit targets " |
| "but is untyped for %select{64|32}3-bit targets") |
| << unsigned(ins.CurrentIdx() + 1) |
| << EF->getName() |
| << (ins.Current().str() + TypeName) |
| << hasType64; |
| } |
| } |
| } |
| } |
| |
| void ReflectionState::addForEachParam(const RSExportForEach *EF, const RSExportType *Type) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mForEachOpen == EF->getOrdinal()); |
| |
| const std::string TypeName = getUniqueTypeName(Type); |
| |
| auto ¶ms = mFiles.Current().mForEaches[EF->getOrdinal()].mParams; |
| if (isCollecting()) { |
| params.CollectNext() = canon(TypeName); |
| } |
| if (isUsing()) { |
| if (mForEachFatal) |
| return; |
| |
| if (!params.UseNext().equals(TypeName)) { |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 usrData parameter of foreach kernel '%1' " |
| "has type '%2' for 32-bit targets " |
| "but type '%3' for 64-bit targets") |
| << unsigned(params.CurrentIdx() + 1) |
| << EF->getName() |
| << params.Current().str() |
| << TypeName; |
| } |
| } |
| } |
| |
| void ReflectionState::addForEachSignatureMetadata(const RSExportForEach *EF, unsigned Metadata) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mForEachOpen == EF->getOrdinal()); |
| |
| // These are properties in the metadata that we need to check. |
| const unsigned SpecialParameterBits = bcinfo::MD_SIG_X|bcinfo::MD_SIG_Y|bcinfo::MD_SIG_Z|bcinfo::MD_SIG_Ctxt; |
| |
| #ifndef __DISABLE_ASSERTS |
| { |
| // These are properties in the metadata that we already check in |
| // some other way. |
| const unsigned BoringBits = bcinfo::MD_SIG_In|bcinfo::MD_SIG_Out|bcinfo::MD_SIG_Usr|bcinfo::MD_SIG_Kernel; |
| |
| slangAssert((Metadata & ~(SpecialParameterBits | BoringBits)) == 0); |
| } |
| #endif |
| |
| auto &mSignatureMetadata = mFiles.Current().mForEaches[EF->getOrdinal()].mSignatureMetadata; |
| if (isCollecting()) { |
| mSignatureMetadata = Metadata; |
| } |
| if (isUsing()) { |
| if (mForEachFatal) |
| return; |
| |
| if ((mSignatureMetadata & SpecialParameterBits) != (Metadata & SpecialParameterBits)) { |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' has different special parameters " |
| "for 32-bit targets than for 64-bit targets") |
| << EF->getName(); |
| } |
| } |
| } |
| |
| void ReflectionState::endForEach() { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mForEachOpen >= 0); |
| if (isUsing() && !mForEachFatal) { |
| slangAssert(mFiles.Current().mForEaches[mForEachOpen].mIns.isFinished()); |
| slangAssert(mFiles.Current().mForEaches[mForEachOpen].mParams.isFinished()); |
| } |
| |
| mForEachOpen = -1; |
| } |
| |
| void ReflectionState::endForEaches() { |
| slangAssert(mForEachOpen < 0); |
| if (!isUsing()) |
| return; |
| |
| const auto &file = mFiles.Current(); |
| |
| if (!mForEachesBad.empty()) { |
| std::sort(mForEachesBad.begin(), mForEachesBad.end(), |
| [](const RSExportForEach *a, const RSExportForEach *b) { return a->getOrdinal() < b->getOrdinal(); }); |
| // Note that after the sort, all kernels that are bad because of |
| // name mismatch precede all kernels that are bad because of |
| // too-high ordinal. |
| |
| // 32-bit and 64-bit compiles need to see foreach kernels in the |
| // same order, because of slot number assignment. Once we see the |
| // first name mismatch in the sequence of foreach kernels, it |
| // doesn't make sense to issue further diagnostics regarding |
| // foreach kernels except those that still happen to match by name |
| // and ordinal (we already handled those diagnostics between |
| // beginForEach() and endForEach()). |
| bool ForEachesOrderFatal = false; |
| |
| for (const RSExportForEach *EF : mForEachesBad) { |
| if (EF->getOrdinal() >= file.mForEachCount) { |
| mRSC->ReportError(EF->getLocation(), |
| "foreach kernel '%0' is only present for 64-bit targets") |
| << EF->getName(); |
| } else { |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 foreach kernel is '%1' for 32-bit targets " |
| "but '%2' for 64-bit targets") |
| << (EF->getOrdinal() + 1) |
| << mFiles.Current().mForEaches[EF->getOrdinal()].mName |
| << EF->getName(); |
| ForEachesOrderFatal = true; |
| break; |
| } |
| } |
| |
| mForEachesBad.clear(); |
| |
| if (ForEachesOrderFatal) |
| return; |
| } |
| |
| if (mNumForEachesMatchedByOrdinal == file.mForEachCount) |
| return; |
| for (unsigned ord = 0; ord < file.mForEachCount; ord++) { |
| const auto &fe = file.mForEaches[ord]; |
| if (fe.mState == File::ForEach::S_Collected) { |
| mRSC->ReportError("in file '%0' foreach kernel '%1' is only present for 32-bit targets") |
| << file.mRSSourceFileName << fe.mName; |
| } |
| } |
| } |
| |
| // Invokable /////////////////////////////////////////////////////////////////////////////////// |
| |
| // Keep this in sync with RSReflectionJava::genExportFunction(). |
| void ReflectionState::declareInvokable(const RSExportFunc *EF) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| const std::string& Name = EF->getName(/*Mangle=*/false); |
| const size_t ParamCount = EF->getNumParameters(); |
| |
| auto &invokables = mFiles.Current().mInvokables; |
| if (isCollecting()) { |
| auto &invokable = invokables.CollectNext(); |
| invokable.mName = Name; |
| invokable.mParamCount = ParamCount; |
| if (EF->hasParam()) { |
| unsigned FieldIdx = 0; |
| invokable.mParams = new llvm::StringRef[ParamCount]; |
| for (RSExportFunc::const_param_iterator I = EF->params_begin(), |
| E = EF->params_end(); |
| I != E; I++, FieldIdx++) { |
| invokable.mParams[FieldIdx] = canon(getUniqueTypeName((*I)->getType())); |
| } |
| } |
| } |
| if (isUsing()) { |
| if (mInvokablesOrderFatal) |
| return; |
| |
| if (invokables.isFinished()) { |
| // This doesn't actually break reflection, but that's a |
| // coincidence of the fact that we reflect during the 64-bit |
| // compilation pass rather than the 32-bit compilation pass, and |
| // of the fact that the "extra" invokable(s) are at the end. |
| mRSC->ReportError(EF->getLocation(), |
| "invokable function '%0' is only present for 64-bit targets") |
| << Name; |
| return; |
| } |
| |
| auto &invokable = invokables.UseNext(); |
| |
| if (invokable.mName != Name) { |
| // Order matters because it determines slot number |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 invokable function is '%1' for 32-bit targets " |
| "but '%2' for 64-bit targets") |
| << unsigned(invokables.CurrentIdx() + 1) |
| << invokable.mName |
| << Name; |
| mInvokablesOrderFatal = true; |
| return; |
| } |
| |
| if (invokable.mParamCount != ParamCount) { |
| mRSC->ReportError(EF->getLocation(), |
| "invokable function '%0' has %1 parameter%s1 for 32-bit targets " |
| "but %2 parameter%s2 for 64-bit targets") |
| << Name << unsigned(invokable.mParamCount) << unsigned(ParamCount); |
| return; |
| } |
| if (EF->hasParam()) { |
| unsigned FieldIdx = 0; |
| for (RSExportFunc::const_param_iterator I = EF->params_begin(), |
| E = EF->params_end(); |
| I != E; I++, FieldIdx++) { |
| const std::string Type = getUniqueTypeName((*I)->getType()); |
| if (!invokable.mParams[FieldIdx].equals(Type)) { |
| mRSC->ReportError(EF->getLocation(), |
| "%ordinal0 parameter of invokable function '%1' " |
| "has type '%2' for 32-bit targets " |
| "but type '%3' for 64-bit targets") |
| << (FieldIdx + 1) |
| << Name |
| << invokable.mParams[FieldIdx].str() |
| << Type; |
| } |
| } |
| } |
| } |
| } |
| |
| void ReflectionState::endInvokables() { |
| if (!isUsing() || mInvokablesOrderFatal) |
| return; |
| |
| auto &invokables = mFiles.Current().mInvokables; |
| while (!invokables.isFinished()) { |
| const auto &invokable = invokables.UseNext(); |
| mRSC->ReportError("in file '%0' invokable function '%1' is only present for 32-bit targets") |
| << mFiles.Current().mRSSourceFileName << invokable.mName; |
| } |
| } |
| |
| // Record ////////////////////////////////////////////////////////////////////////////////////// |
| |
| void ReflectionState::beginRecords() { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mRecordsState != RS_Open); |
| mRecordsState = RS_Open; |
| mNumRecordsMatchedByName = 0; |
| } |
| |
| void ReflectionState::endRecords() { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mRecordsState == RS_Open); |
| mRecordsState = RS_Closed; |
| |
| if (isUsing()) { |
| const File &file = mFiles.Current(); |
| if (mNumRecordsMatchedByName == file.mRecords.size()) |
| return; |
| // NOTE: "StringMap iteration order, however, is not guaranteed to |
| // be deterministic". So sort by name before reporting. |
| // Alternatively, if we record additional information, we could |
| // sort by source location or by order in which we discovered the |
| // need to export. |
| std::vector<llvm::StringRef> Non64RecordNames; |
| for (auto I = file.mRecords.begin(), E = file.mRecords.end(); I != E; I++) |
| if (!I->getValue().mMatchedByName && I->getValue().mOrdinary) |
| Non64RecordNames.push_back(I->getKey()); |
| std::sort(Non64RecordNames.begin(), Non64RecordNames.end(), |
| [](llvm::StringRef a, llvm::StringRef b) { return a.compare(b)==-1; }); |
| for (auto N : Non64RecordNames) |
| mRSC->ReportError("in file '%0' structure '%1' is exported only for 32-bit targets") |
| << file.mRSSourceFileName << N.str(); |
| } |
| } |
| |
| void ReflectionState::declareRecord(const RSExportRecordType *ERT, bool Ordinary) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| slangAssert(mRecordsState == RS_Open); |
| |
| auto &records = mFiles.Current().mRecords; |
| if (isCollecting()) { |
| // Keep struct/field layout in sync with |
| // RSReflectionJava::genPackVarOfType() and |
| // RSReflectionJavaElementBuilder::genAddElement() |
| |
| // Save properties of record |
| |
| const size_t FieldCount = ERT->fields_size(); |
| File::Record::Field *Fields = new File::Record::Field[FieldCount]; |
| |
| size_t Pos = 0; // Relative position of field within record |
| unsigned FieldIdx = 0; |
| for (RSExportRecordType::const_field_iterator I = ERT->fields_begin(), E = ERT->fields_end(); |
| I != E; I++, FieldIdx++) { |
| const RSExportRecordType::Field *FieldExport = *I; |
| size_t FieldOffset = FieldExport->getOffsetInParent(); |
| const RSExportType *T = FieldExport->getType(); |
| size_t FieldStoreSize = T->getStoreSize(); |
| size_t FieldAllocSize = T->getAllocSize(); |
| |
| slangAssert(FieldOffset >= Pos); |
| slangAssert(FieldAllocSize >= FieldStoreSize); |
| |
| auto &FieldState = Fields[FieldIdx]; |
| FieldState.mName = FieldExport->getName(); |
| FieldState.mType = canon(getUniqueTypeName(T)); |
| FieldState.mPrePadding = FieldOffset - Pos; |
| FieldState.mPostPadding = FieldAllocSize - FieldStoreSize; |
| FieldState.mOffset = FieldOffset; |
| FieldState.mStoreSize = FieldStoreSize; |
| |
| Pos = FieldOffset + FieldAllocSize; |
| } |
| |
| slangAssert(ERT->getAllocSize() >= Pos); |
| |
| // Insert record into map |
| |
| slangAssert(records.find(ERT->getName()) == records.end()); |
| File::Record &record = records[ERT->getName()]; |
| record.mFields = Fields; |
| record.mFieldCount = FieldCount; |
| record.mPostPadding = ERT->getAllocSize() - Pos; |
| record.mAllocSize = ERT->getAllocSize(); |
| record.mOrdinary = Ordinary; |
| record.mMatchedByName = false; |
| } |
| if (isUsing()) { |
| if (!Ordinary) |
| return; |
| |
| const auto RIT = records.find(ERT->getName()); |
| if (RIT == records.end()) { |
| // This doesn't actually break reflection, but that's a |
| // coincidence of the fact that we reflect during the 64-bit |
| // compilation pass rather than the 32-bit compilation pass, so |
| // a record that's only classified as exported during the 64-bit |
| // compilation pass doesn't cause any problems. |
| mRSC->ReportError(ERT->getLocation(), "structure '%0' is exported only for 64-bit targets") |
| << ERT->getName(); |
| return; |
| } |
| File::Record &record = RIT->getValue(); |
| record.mMatchedByName = true; |
| ++mNumRecordsMatchedByName; |
| slangAssert(record.mOrdinary); |
| |
| if (ERT->fields_size() != record.mFieldCount) { |
| mRSC->ReportError(ERT->getLocation(), |
| "exported structure '%0' has %1 field%s1 for 32-bit targets " |
| "but %2 field%s2 for 64-bit targets") |
| << ERT->getName() << unsigned(record.mFieldCount) << unsigned(ERT->fields_size()); |
| return; |
| } |
| |
| // Note that we are deliberately NOT comparing layout properties |
| // (such as Field offsets and sizes, or Record allocation size); |
| // we need to tolerate layout differences between 32-bit |
| // compilation and 64-bit compilation. |
| |
| unsigned FieldIdx = 0; |
| for (RSExportRecordType::const_field_iterator I = ERT->fields_begin(), E = ERT->fields_end(); |
| I != E; I++, FieldIdx++) { |
| const RSExportRecordType::Field &FieldExport = **I; |
| const File::Record::Field &FieldState = record.mFields[FieldIdx]; |
| if (FieldState.mName != FieldExport.getName()) { |
| mRSC->ReportError(ERT->getLocation(), |
| "%ordinal0 field of exported structure '%1' " |
| "is '%2' for 32-bit targets " |
| "but '%3' for 64-bit targets") |
| << (FieldIdx + 1) << ERT->getName() << FieldState.mName << FieldExport.getName(); |
| return; |
| } |
| const std::string FieldExportType = getUniqueTypeName(FieldExport.getType()); |
| if (!FieldState.mType.equals(FieldExportType)) { |
| mRSC->ReportError(ERT->getLocation(), |
| "field '%0' of exported structure '%1' " |
| "has type '%2' for 32-bit targets " |
| "but type '%3' for 64-bit targets") |
| << FieldState.mName << ERT->getName() << FieldState.mType.str() << FieldExportType; |
| } |
| } |
| } |
| } |
| |
| ReflectionState::Record32 |
| ReflectionState::getRecord32(const RSExportRecordType *ERT) { |
| if (isUsing()) { |
| const auto &Records = mFiles.Current().mRecords; |
| const auto RIT = Records.find(ERT->getName()); |
| if (RIT != Records.end()) |
| return Record32(&RIT->getValue()); |
| } |
| return Record32(); |
| } |
| |
| // Reduce ////////////////////////////////////////////////////////////////////////////////////// |
| |
| void ReflectionState::declareReduce(const RSExportReduce *ER, bool IsExportable) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return; |
| |
| auto &reduces = mFiles.Current().mReduces; |
| if (isCollecting()) { |
| auto &reduce = reduces.CollectNext(); |
| reduce.mName = ER->getNameReduce(); |
| |
| const auto &InTypes = ER->getAccumulatorInTypes(); |
| const size_t InTypesSize = InTypes.size(); |
| reduce.mAccumInCount = InTypesSize; |
| reduce.mAccumIns = new llvm::StringRef[InTypesSize]; |
| unsigned InTypesIdx = 0; |
| for (const auto &InType : InTypes) |
| reduce.mAccumIns[InTypesIdx++] = canon(getUniqueTypeName(InType)); |
| |
| reduce.mResult = canon(getUniqueTypeName(ER->getResultType())); |
| reduce.mIsExportable = IsExportable; |
| } |
| if (isUsing()) { |
| if (mReducesOrderFatal) |
| return; |
| |
| const std::string& Name = ER->getNameReduce(); |
| |
| if (reduces.isFinished()) { |
| // This doesn't actually break reflection, but that's a |
| // coincidence of the fact that we reflect during the 64-bit |
| // compilation pass rather than the 32-bit compilation pass, and |
| // of the fact that the "extra" reduction kernel(s) are at the |
| // end. |
| mRSC->ReportError(ER->getLocation(), |
| "reduction kernel '%0' is only present for 64-bit targets") |
| << Name; |
| return; |
| } |
| |
| auto &reduce = reduces.UseNext(); |
| |
| if (reduce.mName != Name) { |
| // Order matters because it determines slot number. We might be |
| // able to tolerate certain cases if we ignore non-exportable |
| // kernels in the two sequences (32-bit and 64-bit) -- non-exportable |
| // kernels do not take up slot numbers. |
| mRSC->ReportError(ER->getLocation(), |
| "%ordinal0 reduction kernel is '%1' for 32-bit targets " |
| "but '%2' for 64-bit targets") |
| << unsigned(reduces.CurrentIdx() + 1) |
| << reduce.mName |
| << Name; |
| mReducesOrderFatal = true; |
| return; |
| } |
| |
| // If at least one of the two kernels (32-bit or 64-bit) is not |
| // exporable, then there will be no reflection for that kernel, |
| // and so any mismatch in result type or in inputs is irrelevant. |
| // However, we may make more kernels exportable in the future. |
| // Therefore, we'll forbid mismatches anyway. |
| |
| if (reduce.mIsExportable != IsExportable) { |
| mRSC->ReportError(ER->getLocation(), |
| "reduction kernel '%0' is reflected in Java only for %select{32|64}1-bit targets") |
| << reduce.mName |
| << IsExportable; |
| } |
| |
| const std::string ResultType = getUniqueTypeName(ER->getResultType()); |
| if (!reduce.mResult.equals(ResultType)) { |
| mRSC->ReportError(ER->getLocation(), |
| "reduction kernel '%0' has result type '%1' for 32-bit targets " |
| "but result type '%2' for 64-bit targets") |
| << reduce.mName << reduce.mResult.str() << ResultType; |
| } |
| |
| const auto &InTypes = ER->getAccumulatorInTypes(); |
| if (reduce.mAccumInCount != InTypes.size()) { |
| mRSC->ReportError(ER->getLocation(), |
| "reduction kernel '%0' has %1 input%s1 for 32-bit targets " |
| "but %2 input%s2 for 64-bit targets") |
| << Name << unsigned(reduce.mAccumInCount) << unsigned(InTypes.size()); |
| return; |
| } |
| unsigned FieldIdx = 0; |
| for (const auto &InType : InTypes) { |
| const std::string InTypeName = getUniqueTypeName(InType); |
| const llvm::StringRef StateInTypeName = reduce.mAccumIns[FieldIdx++]; |
| if (!StateInTypeName.equals(InTypeName)) { |
| mRSC->ReportError(ER->getLocation(), |
| "%ordinal0 input of reduction kernel '%1' " |
| "has type '%2' for 32-bit targets " |
| "but type '%3' for 64-bit targets") |
| << FieldIdx |
| << Name |
| << StateInTypeName.str() |
| << InTypeName; |
| } |
| } |
| } |
| } |
| |
| void ReflectionState::endReduces() { |
| if (!isUsing() || mReducesOrderFatal) |
| return; |
| |
| auto &reduces = mFiles.Current().mReduces; |
| while (!reduces.isFinished()) { |
| const auto &reduce = reduces.UseNext(); |
| mRSC->ReportError("in file '%0' reduction kernel '%1' is only present for 32-bit targets") |
| << mFiles.Current().mRSSourceFileName << reduce.mName; |
| } |
| } |
| |
| // Variable //////////////////////////////////////////////////////////////////////////////////// |
| |
| // Keep this in sync with initialization handling in |
| // RSReflectionJava::genScriptClassConstructor(). |
| ReflectionState::Val32 ReflectionState::declareVariable(const RSExportVar *EV) { |
| slangAssert(!isClosed()); |
| if (!isActive()) |
| return NoVal32(); |
| |
| auto &variables = mFiles.Current().mVariables; |
| if (isCollecting()) { |
| auto &variable = variables.CollectNext(); |
| variable.mName = EV->getName(); |
| variable.mType = canon(getUniqueTypeName(EV->getType())); |
| variable.mAllocSize = EV->getType()->getAllocSize(); |
| variable.mIsConst = EV->isConst(); |
| if (!EV->getInit().isUninit()) { |
| variable.mInitializerCount = 1; |
| variable.mInitializers = new clang::APValue[1]; |
| variable.mInitializers[0] = EV->getInit(); |
| } else if (EV->getArraySize()) { |
| variable.mInitializerCount = EV->getNumInits(); |
| variable.mInitializers = new clang::APValue[variable.mInitializerCount]; |
| for (size_t i = 0; i < variable.mInitializerCount; ++i) |
| variable.mInitializers[i] = EV->getInitArray(i); |
| } else { |
| variable.mInitializerCount = 0; |
| } |
| return NoVal32(); |
| } |
| |
| /*-- isUsing() -----------------------------------------------------------*/ |
| |
| slangAssert(isUsing()); |
| |
| if (mVariablesOrderFatal) |
| return NoVal32(); |
| |
| if (variables.isFinished()) { |
| // This doesn't actually break reflection, but that's a |
| // coincidence of the fact that we reflect during the 64-bit |
| // compilation pass rather than the 32-bit compilation pass, and |
| // of the fact that the "extra" variable(s) are at the end. |
| mRSC->ReportError(EV->getLocation(), "global variable '%0' is only present for 64-bit targets") |
| << EV->getName(); |
| return NoVal32(); |
| } |
| |
| const auto &variable = variables.UseNext(); |
| |
| if (variable.mName != EV->getName()) { |
| // Order matters because it determines slot number |
| mRSC->ReportError(EV->getLocation(), |
| "%ordinal0 global variable is '%1' for 32-bit targets " |
| "but '%2' for 64-bit targets") |
| << unsigned(variables.CurrentIdx() + 1) |
| << variable.mName |
| << EV->getName(); |
| mVariablesOrderFatal = true; |
| return NoVal32(); |
| } |
| |
| const std::string TypeName = getUniqueTypeName(EV->getType()); |
| |
| if (!variable.mType.equals(TypeName)) { |
| mRSC->ReportError(EV->getLocation(), |
| "global variable '%0' has type '%1' for 32-bit targets " |
| "but type '%2' for 64-bit targets") |
| << EV->getName() |
| << variable.mType.str() |
| << TypeName; |
| return NoVal32(); |
| } |
| |
| if (variable.mIsConst != EV->isConst()) { |
| mRSC->ReportError(EV->getLocation(), |
| "global variable '%0' has inconsistent 'const' qualification " |
| "between 32-bit targets and 64-bit targets") |
| << EV->getName(); |
| return NoVal32(); |
| } |
| |
| // NOTE: Certain syntactically different but semantically |
| // equivalent initialization patterns are unnecessarily rejected |
| // as errors. |
| // |
| // Background: |
| // |
| // . A vector initialized with a scalar value is treated |
| // by reflection as if all elements of the vector are |
| // initialized with the scalar value. |
| // . A vector may be initialized with a vector of greater |
| // length; reflection ignores the extra initializers. |
| // . If only the beginning of a vector is explicitly |
| // initialized, reflection treats it as if trailing elements are |
| // initialized to zero (by issuing explicit assignments to those |
| // trailing elements). |
| // . If only the beginning of an array is explicitly initialized, |
| // reflection treats it as if trailing elements are initialized |
| // to zero (by Java rules for newly-created arrays). |
| // |
| // Unnecessarily rejected as errors: |
| // |
| // . One compile initializes a vector with a scalar, and |
| // another initializes it with a vector whose elements |
| // are the scalar, as in |
| // |
| // int2 x = |
| // #ifdef __LP64__ |
| // 1 |
| // #else |
| // { 1, 1 } |
| // #endif |
| // |
| // . Compiles initialize a vector with vectors of different |
| // lengths, but the initializers agree up to the length |
| // of the variable being initialized, as in |
| // |
| // int2 x = { 1, 2 |
| // #ifdef __LP64__ |
| // 3 |
| // #else |
| // 4 |
| // #endif |
| // }; |
| // |
| // . Two compiles agree with the initializer for a vector or |
| // array, except that one has some number of explicit trailing |
| // zeroes, as in |
| // |
| // int x[4] = { 3, 2, 1 |
| // #ifdef __LP64__ |
| // , 0 |
| // #endif |
| // }; |
| |
| bool MismatchedInitializers = false; |
| if (!EV->getInit().isUninit()) { |
| // Use phase has a scalar initializer. |
| // Make sure that Collect phase had a matching scalar initializer. |
| if ((variable.mInitializerCount != 1) || |
| !equal(variable.mInitializers[0], EV->getInit())) |
| MismatchedInitializers = true; |
| } else if (EV->getArraySize()) { |
| const size_t UseSize = EV->getNumInits(); |
| if (variable.mInitializerCount != UseSize) |
| MismatchedInitializers = true; |
| else { |
| for (int i = 0; i < UseSize; ++i) |
| if (!equal(variable.mInitializers[i], EV->getInitArray(i))) { |
| MismatchedInitializers = true; |
| break; |
| } |
| } |
| } else if (variable.mInitializerCount != 0) { |
| // Use phase does not have a scalar initializer, variable is not |
| // an array, and Collect phase has an initializer. This is an error. |
| MismatchedInitializers = true; |
| } |
| |
| if (MismatchedInitializers) { |
| mRSC->ReportError(EV->getLocation(), |
| "global variable '%0' is initialized differently for 32-bit targets " |
| "than for 64-bit targets") |
| << EV->getName(); |
| return NoVal32(); |
| } |
| |
| return Val32(true, variable.mAllocSize); |
| } |
| |
| void ReflectionState::endVariables() { |
| if (!isUsing() || mVariablesOrderFatal) |
| return; |
| |
| auto &variables = mFiles.Current().mVariables; |
| while (!variables.isFinished()) { |
| const auto &variable = variables.UseNext(); |
| mRSC->ReportError("in file '%0' global variable '%1' is only present for 32-bit targets") |
| << mFiles.Current().mRSSourceFileName << variable.mName; |
| } |
| } |
| |
| } // namespace slang |