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