// // Copyright 2022 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // trace_interpreter.cpp: // Parser and interpreter for the C-based replays. // #include "trace_interpreter.h" #include "anglebase/no_destructor.h" #include "common/gl_enum_utils.h" #include "common/string_utils.h" #include "trace_fixture.h" #define USE_SYSTEM_ZLIB #include "compression_utils_portable.h" namespace angle { namespace { bool ShouldParseFile(const std::string &file) { return EndsWith(file, ".c") || EndsWith(file, ".cpp"); } void ReplayTraceFunction(const TraceFunction &func, const TraceFunctionMap &customFunctions) { for (const CallCapture &call : func) { ReplayTraceFunctionCall(call, customFunctions); } } class Parser : angle::NonCopyable { public: Parser(const std::string &stream, TraceFunctionMap &functionsIn, TraceStringMap &stringsIn, bool verboseLogging) : mStream(stream), mFunctions(functionsIn), mStrings(stringsIn), mIndex(0), mVerboseLogging(verboseLogging) {} void parse() { while (mIndex < mStream.size()) { if (peek() == '#' || peek() == '/') { skipLine(); } else if (peek() == 'v') { ASSERT(check("void ")); readFunction(); } else if (peek() == 'c') { ASSERT(check("const ")); readMultilineString(); } else { printf("Unexpected character: '%c'\n", peek()); UNREACHABLE(); } } } private: ANGLE_INLINE char peek() const { return mStream[mIndex]; } ANGLE_INLINE char look(size_t ahead) const { return mStream[mIndex + ahead]; } ANGLE_INLINE void advance() { mIndex++; } ANGLE_INLINE void advanceTo(char delim) { while (peek() != delim) { advance(); } } bool check(const char *forString) const { return mStream.substr(mIndex, strlen(forString)) == forString; } void skipLine() { advanceTo('\n'); skipWhitespace(); } void skipWhitespace() { while (isspace(peek())) { advance(); } } void skipNonWhitespace() { while (!isspace(peek())) { advance(); } } // In our simplified trace C, every line that begins with a } either ends a function or a // string. All lines inside the function begin with whitespace. So to find the end of the // function we just need to scan for a line beginning with }. void skipFunction() { while (peek() != '}') { advanceTo('\n'); advance(); } advance(); skipWhitespace(); } void readStringAppend(std::string *stringOut, char delim) { while (peek() != delim) { if (peek() == '\\') { advance(); switch (peek()) { case 'n': *stringOut += '\n'; break; case '\"': *stringOut += '\"'; break; case '\\': *stringOut += '\\'; break; default: printf("Unrecognized escape character: \\%c\n", peek()); UNREACHABLE(); break; } } else { *stringOut += peek(); } advance(); } } void readToken(Token &token, char delim) { size_t startIndex = mIndex; advanceTo(delim); size_t tokenSize = mIndex - startIndex; ASSERT(tokenSize < kMaxTokenSize); memcpy(token, &mStream[startIndex], tokenSize); token[mIndex - startIndex] = 0; } void skipCast() { if (peek() == '(') { advanceTo(')'); advance(); } } void skipComments() { while (peek() == '/') { skipLine(); } } void readFunction() { std::string funcName; TraceFunction func; // Skip past the "void" return value. skipNonWhitespace(); advance(); readStringAppend(&funcName, '('); if (mVerboseLogging) { printf("function: %s\n", funcName.c_str()); } // Skip this function because of the switch statements. if (funcName == "ReplayFrame") { skipFunction(); return; } skipLine(); ASSERT(peek() == '{'); skipLine(); while (peek() != '}') { skipComments(); Token nameToken; readToken(nameToken, '('); advance(); ParamBuffer params; Token paramTokens[kMaxParameters]; size_t numParams = 0; skipCast(); size_t tokenStart = mIndex; while (peek() != ';') { // Skip casts. if (peek() == ',' || (peek() == ')' && mIndex != tokenStart)) { ASSERT(numParams < kMaxParameters); size_t tokenSize = mIndex - tokenStart; ASSERT(tokenSize < kMaxTokenSize); Token &token = paramTokens[numParams++]; memcpy(token, &mStream[tokenStart], tokenSize); token[tokenSize] = 0; advance(); skipWhitespace(); skipCast(); tokenStart = mIndex; } else { advance(); } } // Turn on if you want more spam. // if (mVerboseLogging) //{ // printf("call: %s(", nameToken); // for (size_t paramIndex = 0; paramIndex < numParams; ++paramIndex) // { // if (paramIndex > 0) // { // printf(", "); // } // printf("%s", paramTokens[paramIndex]); // } // printf(")\n"); //} // We pass in the strings for specific use with C string array parameters. CallCapture call = ParseCallCapture(nameToken, numParams, paramTokens, mStrings); func.push_back(std::move(call)); skipLine(); } skipLine(); addFunction(funcName, func); } void readMultilineString() { std::string name; TraceString traceStr; while (peek() != 'g') { advance(); } ASSERT(check("glShaderSource") || check("glTransformFeedbackVaryings")); readStringAppend(&name, '['); if (mVerboseLogging) { printf("string: %s\n", name.c_str()); } skipLine(); std::string str; while (peek() != '}') { advance(); readStringAppend(&str, '\"'); advance(); if (peek() == ',') { traceStr.strings.push_back(std::move(str)); } skipLine(); } skipLine(); for (const std::string &cppstr : traceStr.strings) { traceStr.pointers.push_back(cppstr.c_str()); } mStrings[name] = std::move(traceStr); } void addFunction(const std::string &funcName, TraceFunction &func) { // Run initialize immediately so we can load the binary data. if (funcName == "InitReplay") { ReplayTraceFunction(func, {}); func.clear(); } mFunctions[funcName] = std::move(func); } const std::string &mStream; TraceFunctionMap &mFunctions; TraceStringMap &mStrings; size_t mIndex; bool mVerboseLogging = false; }; void PackResourceID(ParamBuffer ¶ms, const Token &token) { ASSERT(token[0] == 'g'); const char *start = strrchr(token, '['); ASSERT(start != nullptr && EndsWith(token, "]")); uint32_t value = static_cast(atoi(start + 1)); if (BeginsWith(token, "gShaderProgramMap")) { gl::ShaderProgramID id = {value}; params.addUnnamedParam(ParamType::TShaderProgramID, id); } else if (BeginsWith(token, "gBufferMap")) { gl::BufferID id = {value}; params.addUnnamedParam(ParamType::TBufferID, id); } else if (BeginsWith(token, "gTextureMap")) { gl::TextureID id = {value}; params.addUnnamedParam(ParamType::TTextureID, id); } else if (BeginsWith(token, "gRenderbufferMap")) { gl::RenderbufferID id = {value}; params.addUnnamedParam(ParamType::TRenderbufferID, id); } else if (BeginsWith(token, "gFramebufferMap")) { gl::FramebufferID id = {value}; params.addUnnamedParam(ParamType::TFramebufferID, id); } else if (BeginsWith(token, "gSyncMap")) { gl::SyncID id = {value}; params.addUnnamedParam(ParamType::TSyncID, id); } else if (BeginsWith(token, "gTransformFeedbackMap")) { gl::TransformFeedbackID id = {value}; params.addUnnamedParam(ParamType::TTransformFeedbackID, id); } else if (BeginsWith(token, "gVertexArrayMap")) { gl::VertexArrayID id = {value}; params.addUnnamedParam(ParamType::TVertexArrayID, id); } else if (BeginsWith(token, "gQueryMap")) { gl::QueryID id = {value}; params.addUnnamedParam(ParamType::TQueryID, id); } else if (BeginsWith(token, "gSamplerMap")) { gl::SamplerID id = {value}; params.addUnnamedParam(ParamType::TSamplerID, id); } else { printf("Unknown resource map: %s\n", token); UNREACHABLE(); } } template void PackIntParameter(ParamBuffer ¶ms, ParamType paramType, const Token &token) { IntT value; if (token[0] == 'G') { ASSERT(BeginsWith(token, "GL_")); if (strchr(token, '|') == 0) { value = static_cast(gl::StringToGLenum(token)); } else { value = static_cast(gl::StringToGLbitfield(token)); } } else { if (!isdigit(token[0]) && !(token[0] == '-' && isdigit(token[1]))) { printf("Expected number, got %s\n", token); UNREACHABLE(); } if (token[0] == '0' && token[1] == 'x') { value = static_cast(strtol(token, nullptr, 16)); } else { value = static_cast(atoi(token)); } } params.addUnnamedParam(paramType, value); } uint32_t GetStringArrayOffset(const Token &token, const char *prefixString) { const char *offsetString = &token[strlen(prefixString)]; return atoi(offsetString); } template void PackMemPointer(ParamBuffer ¶ms, ParamType paramType, uint32_t offset, uint8_t *mem) { ASSERT(gBinaryData); params.addUnnamedParam(paramType, reinterpret_cast(&mem[offset])); } template void PackMutablePointerParameter(ParamBuffer ¶ms, ParamType paramType, const Token &token) { if (token[0] == '0' && token[1] == 0) { params.addUnnamedParam(paramType, reinterpret_cast(0)); } else if (token[0] == '&') { ASSERT(BeginsWith(token, "&gReadBuffer[") && EndsWith(token, "]")); uint32_t offset = GetStringArrayOffset(token, "&gReadBuffer["); PackMemPointer(params, paramType, offset, gReadBuffer); } else if (token[0] == 'g') { ASSERT(strcmp(token, "gReadBuffer") == 0); params.addUnnamedParam(paramType, reinterpret_cast(gReadBuffer)); } else { UNREACHABLE(); } } template void PackConstPointerParameter(ParamBuffer ¶ms, ParamType paramType, const Token &token) { // Handle nullptr, the literal "0". if (token[0] == '0' && token[1] == 0) { params.addUnnamedParam(paramType, reinterpret_cast(0)); } else if (token[0] == '&') { ASSERT(BeginsWith(token, "&gBinaryData[") && EndsWith(token, "]")); uint32_t offset = GetStringArrayOffset(token, "&gReadBuffer["); PackMemPointer(params, paramType, offset, gBinaryData); } else if (token[0] == 'g') { if (strcmp(token, "gResourceIDBuffer") == 0) { params.addUnnamedParam(paramType, reinterpret_cast(gResourceIDBuffer)); } else if (BeginsWith(token, "gClientArrays")) { uint32_t offset = GetStringArrayOffset(token, "gClientArrays["); params.addUnnamedParam(paramType, reinterpret_cast(gClientArrays[offset])); } else { printf("Unexpected token: %s\n", token); UNREACHABLE(); } } else { ASSERT(isdigit(token[0])); uint32_t offset = atoi(token); params.addUnnamedParam(paramType, reinterpret_cast(static_cast(offset))); } } class TraceInterpreter : angle::NonCopyable { public: TraceInterpreter() = default; ~TraceInterpreter() = default; void replayFrame(uint32_t frameIndex); void setupReplay(); void resetReplay(); const char *getSerializedContextState(uint32_t frameIndex); private: void runTraceFunction(const char *name) const; void parseTraceUncompressed(); void parseTraceGz(); TraceFunctionMap mTraceFunctions; TraceStringMap mTraceStrings; bool mVerboseLogging = true; }; void TraceInterpreter::replayFrame(uint32_t frameIndex) { char funcName[kMaxTokenSize]; snprintf(funcName, kMaxTokenSize, "ReplayFrame%u", frameIndex); runTraceFunction(funcName); } void TraceInterpreter::parseTraceUncompressed() { for (const std::string &file : gTraceInfo.traceFiles) { if (!ShouldParseFile(file)) { if (mVerboseLogging) { printf("Skipping function parsing for %s.\n", file.c_str()); } continue; } if (mVerboseLogging) { printf("Parsing functions from %s\n", file.c_str()); } std::stringstream pathStream; pathStream << gBinaryDataDir << GetPathSeparator() << file; std::string path = pathStream.str(); std::string fileData; if (!ReadFileToString(path, &fileData)) { UNREACHABLE(); } Parser parser(fileData, mTraceFunctions, mTraceStrings, mVerboseLogging); parser.parse(); } } void TraceInterpreter::parseTraceGz() { if (mVerboseLogging) { printf("Parsing functions from %s\n", gTraceGzPath.c_str()); } FILE *fp = fopen(gTraceGzPath.c_str(), "rb"); if (fp == 0) { printf("Error loading trace (gz) from: %s\n", gTraceGzPath.c_str()); exit(1); } fseek(fp, 0, SEEK_END); long size = ftell(fp); fseek(fp, 0, SEEK_SET); std::vector compressedData(size); (void)fread(compressedData.data(), 1, size, fp); uint32_t uncompressedSize = zlib_internal::GetGzipUncompressedSize(compressedData.data(), compressedData.size()); std::string uncompressedData(uncompressedSize, 0); uLong destLen = uncompressedSize; int zResult = zlib_internal::GzipUncompressHelper((uint8_t *)uncompressedData.data(), &destLen, compressedData.data(), static_cast(compressedData.size())); if (zResult != Z_OK) { printf("Failure to decompress gz trace: %s\n", gTraceGzPath.c_str()); exit(1); } Parser parser(uncompressedData, mTraceFunctions, mTraceStrings, mVerboseLogging); parser.parse(); } void TraceInterpreter::setupReplay() { if (!gTraceGzPath.empty()) { parseTraceGz(); } else { parseTraceUncompressed(); } if (mTraceFunctions.count("SetupReplay") == 0) { printf("Did not find a SetupReplay function to run among %zu parsed functions.\n", mTraceFunctions.size()); exit(1); } runTraceFunction("SetupReplay"); } void TraceInterpreter::resetReplay() { runTraceFunction("ResetReplay"); } const char *TraceInterpreter::getSerializedContextState(uint32_t frameIndex) { // TODO: Necessary for complete self-testing. http://anglebug.com/42266250 UNREACHABLE(); return nullptr; } void TraceInterpreter::runTraceFunction(const char *name) const { auto iter = mTraceFunctions.find(name); if (iter == mTraceFunctions.end()) { printf("Cannot find function: %s\n", name); UNREACHABLE(); } const TraceFunction &func = iter->second; ReplayTraceFunction(func, mTraceFunctions); } TraceInterpreter &GetInterpreter() { static angle::base::NoDestructor> sTraceInterpreter( new TraceInterpreter()); return *sTraceInterpreter.get()->get(); } } // anonymous namespace template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { if (token[0] == 'g') { PackResourceID(params, token); } else { PackIntParameter(params, ParamType::TGLuint, token); } } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { if (BeginsWith(token, "gUniformLocations")) { const char *start = strrchr(token, '['); ASSERT(start != nullptr && EndsWith(token, "]")); int32_t value = atoi(start + 1); gl::UniformLocation loc = {value}; params.addUnnamedParam(ParamType::TUniformLocation, loc); } else { PackIntParameter(params, ParamType::TGLint, token); } } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { void *value = 0; params.addUnnamedParam(ParamType::TvoidPointer, value); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TGLintConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackMutablePointerParameter(params, ParamType::TGLintPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { params.addUnnamedParam(ParamType::TGLuint64, static_cast(std::strtoull(token, nullptr, 10))); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { params.addUnnamedParam(ParamType::TGLint64, static_cast(std::strtoll(token, nullptr, 10))); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { if (token[0] == '"') { ASSERT(EndsWith(token, "\"")); ParamCapture param(params.getNextParamName(), ParamType::TGLcharConstPointer); std::vector data(&token[1], &token[strlen(token) - 1]); data.push_back(0); param.data.push_back(std::move(data)); param.value.GLcharConstPointerVal = reinterpret_cast(param.data[0].data()); params.addParam(std::move(param)); } else { PackConstPointerParameter(params, ParamType::TGLcharConstPointer, token); } } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TvoidConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackMutablePointerParameter(params, ParamType::TGLuintPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TGLuintConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { params.addUnnamedParam(ParamType::TGLfloat, std::stof(token)); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackIntParameter(params, ParamType::TGLubyte, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackMutablePointerParameter(params, ParamType::TGLfloatPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TGLfloatConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackResourceID(params, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { // Find the string that corresponds to "token". Currently we only support string arrays. auto iter = strings.find(token); if (iter == strings.end()) { printf("Could not find string: %s\n", token); UNREACHABLE(); } const TraceString &traceStr = iter->second; params.addUnnamedParam(ParamType::TGLcharConstPointerPointer, traceStr.pointers.data()); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackIntParameter(params, ParamType::TGLshort, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TGLshortConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackMutablePointerParameter(params, ParamType::TGLubytePointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } #if defined(ANGLE_PLATFORM_WINDOWS) template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } #endif // defined(ANGLE_PLATFORM_WINDOWS) #if defined(ANGLE_PLATFORM_WINDOWS) || defined(ANGLE_PLATFORM_ANDROID) template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { UNREACHABLE(); } #endif // defined(ANGLE_PLATFORM_WINDOWS) || defined(ANGLE_PLATFORM_ANDROID) #if defined(ANGLE_PLATFORM_APPLE) || !defined(ANGLE_IS_64_BIT_CPU) template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackConstPointerParameter(params, ParamType::TGLuint64ConstPointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackMutablePointerParameter(params, ParamType::TGLint64Pointer, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackIntParameter(params, ParamType::TGLint64, token); } template <> void PackParameter(ParamBuffer ¶ms, const Token &token, const TraceStringMap &strings) { PackIntParameter(params, ParamType::TGLuint64, token); } #endif // defined(ANGLE_PLATFORM_APPLE) || !defined(ANGLE_IS_64_BIT_CPU) GLuint GetResourceIDMapValue(ResourceIDType resourceIDType, GLuint key) { switch (resourceIDType) { case ResourceIDType::Buffer: return gBufferMap[key]; case ResourceIDType::FenceNV: return gFenceNVMap[key]; case ResourceIDType::Framebuffer: return gFramebufferMap[key]; case ResourceIDType::ProgramPipeline: return gProgramPipelineMap[key]; case ResourceIDType::Query: return gQueryMap[key]; case ResourceIDType::Renderbuffer: return gRenderbufferMap[key]; case ResourceIDType::Sampler: return gSamplerMap[key]; case ResourceIDType::Semaphore: return gSemaphoreMap[key]; case ResourceIDType::ShaderProgram: return gShaderProgramMap[key]; case ResourceIDType::Texture: return gTextureMap[key]; case ResourceIDType::TransformFeedback: return gTransformFeedbackMap[key]; case ResourceIDType::VertexArray: return gVertexArrayMap[key]; default: printf("Incompatible resource ID type: %d\n", static_cast(resourceIDType)); UNREACHABLE(); return 0; } } } // namespace angle extern "C" { void SetupReplay() { angle::GetInterpreter().setupReplay(); } void ReplayFrame(uint32_t frameIndex) { angle::GetInterpreter().replayFrame(frameIndex); } void ResetReplay() { angle::GetInterpreter().resetReplay(); } const char *GetSerializedContextState(uint32_t frameIndex) { return angle::GetInterpreter().getSerializedContextState(frameIndex); } } // extern "C"