/* * Copyright (C) 2011 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 "ApiGen.h" #include "EntryPoint.h" #include #include #include "strUtils.h" #include #include /* Define this to 1 to enable support for the 'isLarge' variable flag * that instructs the encoder to send large data buffers by a direct * write through the pipe (i.e. without copying it into a temporary * buffer. This has definite performance benefits when using a QEMU Pipe. * * Set to 0 otherwise. */ #define WITH_LARGE_SUPPORT 1 // Set to 1 to ensure buffers passed to/from EGL/GL are properly aligned. // This prevents crashes with certain backends (e.g. OSMesa). #define USE_ALIGNED_BUFFERS 1 EntryPoint * ApiGen::findEntryByName(const std::string & name) { EntryPoint * entry = NULL; size_t n = this->size(); for (size_t i = 0; i < n; i++) { if (at(i).name() == name) { entry = &(at(i)); break; } } return entry; } void ApiGen::printHeader(FILE *fp) const { fprintf(fp, "// Generated Code - DO NOT EDIT !!\n"); fprintf(fp, "// generated by 'emugen'\n"); } int ApiGen::genProcTypes(const std::string &filename, SideType side) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); const char* basename = m_basename.c_str(); fprintf(fp, "#ifndef __%s_%s_proc_t_h\n", basename, sideString(side)); fprintf(fp, "#define __%s_%s_proc_t_h\n", basename, sideString(side)); fprintf(fp, "\n\n"); fprintf(fp, "\n#include \"%s_types.h\"\n",basename); fprintf(fp, "#ifndef %s_APIENTRY\n",basename); fprintf(fp, "#define %s_APIENTRY \n",basename); fprintf(fp, "#endif\n"); for (size_t i = 0; i < size(); i++) { EntryPoint *e = &at(i); fprintf(fp, "typedef "); e->retval().printType(fp); fprintf(fp, " (%s_APIENTRY *%s_%s_proc_t) (", basename, e->name().c_str(), sideString(side)); if (side == CLIENT_SIDE) { fprintf(fp, "void * ctx"); } if (e->customDecoder() && side == SERVER_SIDE) { fprintf(fp, "void *ctx"); } VarsArray & evars = e->vars(); size_t n = evars.size(); for (size_t j = 0; j < n; j++) { if (!evars[j].isVoid()) { if (j != 0 || side == CLIENT_SIDE || (side == SERVER_SIDE && e->customDecoder())) fprintf(fp, ", "); evars[j].printType(fp); } } fprintf(fp, ");\n"); } fprintf(fp, "\n\n#endif\n"); return 0; } int ApiGen::genFuncTable(const std::string &filename, SideType side) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); fprintf(fp, "#ifndef __%s_%s_ftable_t_h\n", m_basename.c_str(), sideString(side)); fprintf(fp, "#define __%s_%s_ftable_t_h\n", m_basename.c_str(), sideString(side)); fprintf(fp, "\n\n"); fprintf(fp, "static const struct _%s_funcs_by_name {\n", m_basename.c_str()); fprintf(fp, "\tconst char *name;\n" \ "\tvoid *proc;\n" \ "} %s_funcs_by_name[] = {\n", m_basename.c_str()); for (size_t i = 0; i < size(); i++) { EntryPoint *e = &at(i); if (e->notApi()) continue; fprintf(fp, "\t{\"%s\", (void*)%s},\n", e->name().c_str(), e->name().c_str()); } fprintf(fp, "};\n"); fprintf(fp, "static const int %s_num_funcs = sizeof(%s_funcs_by_name) / sizeof(struct _%s_funcs_by_name);\n", m_basename.c_str(), m_basename.c_str(), m_basename.c_str()); fprintf(fp, "\n\n#endif\n"); return 0; } int ApiGen::genContext(const std::string & filename, SideType side) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); fprintf(fp, "#ifndef __%s_%s_context_t_h\n", m_basename.c_str(), sideString(side)); fprintf(fp, "#define __%s_%s_context_t_h\n", m_basename.c_str(), sideString(side)); fprintf(fp, "\n#include \"%s_%s_proc.h\"\n", m_basename.c_str(), side == CLIENT_SIDE ? "client" : "server"); fprintf(fp, "\n#include \"%s_types.h\"\n", m_basename.c_str()); StringVec & contextHeaders = side == CLIENT_SIDE ? m_clientContextHeaders : m_serverContextHeaders; for (size_t i = 0; i < contextHeaders.size(); i++) { fprintf(fp, "#include %s\n", contextHeaders[i].c_str()); } fprintf(fp, "\n"); fprintf(fp, "\nstruct %s_%s_context_t {\n\n", m_basename.c_str(), sideString(side)); // API entry points for (size_t i = 0; i < size(); i++) { EntryPoint *e = &at(i); fprintf(fp, "\t%s_%s_proc_t %s;\n", e->name().c_str(), sideString(side), e->name().c_str()); } // virtual destructor fprintf(fp, "\t virtual ~%s_%s_context_t() {}\n", m_basename.c_str(), sideString(side)); // accessor if (side == CLIENT_SIDE || side == WRAPPER_SIDE) { fprintf(fp, "\n\ttypedef %s_%s_context_t *CONTEXT_ACCESSOR_TYPE(void);\n", m_basename.c_str(), sideString(side)); fprintf(fp, "\tstatic void setContextAccessor(CONTEXT_ACCESSOR_TYPE *f);\n"); } // init function fprintf(fp, "\tint initDispatchByName( void *(*getProc)(const char *name, void *userData), void *userData);\n"); //client site set error virtual func if (side == CLIENT_SIDE) { fprintf(fp, "\tvirtual void setError(unsigned int error){ (void)error; };\n"); fprintf(fp, "\tvirtual unsigned int getError(){ return 0; };\n"); } fprintf(fp, "};\n"); fprintf(fp, "\n#endif\n"); fclose(fp); return 0; } int ApiGen::genEntryPoints(const std::string & filename, SideType side) { if (side != CLIENT_SIDE && side != WRAPPER_SIDE) { fprintf(stderr, "Entry points are only defined for Client and Wrapper components\n"); return -999; } FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return errno; } printHeader(fp); fprintf(fp, "#include \n"); fprintf(fp, "#include \n"); fprintf(fp, "#include \"%s_%s_context.h\"\n", m_basename.c_str(), sideString(side)); fprintf(fp, "\n"); fprintf(fp, "#ifndef GL_TRUE\n"); fprintf(fp, "extern \"C\" {\n"); for (size_t i = 0; i < size(); i++) { fprintf(fp, "\t"); at(i).print(fp, false); fprintf(fp, ";\n"); } fprintf(fp, "};\n\n"); fprintf(fp, "#endif\n"); fprintf(fp, "#ifndef GET_CONTEXT\n"); fprintf(fp, "static %s_%s_context_t::CONTEXT_ACCESSOR_TYPE *getCurrentContext = NULL;\n", m_basename.c_str(), sideString(side)); fprintf(fp, "void %s_%s_context_t::setContextAccessor(CONTEXT_ACCESSOR_TYPE *f) { getCurrentContext = f; }\n", m_basename.c_str(), sideString(side)); fprintf(fp, "#define GET_CONTEXT %s_%s_context_t * ctx = getCurrentContext()\n", m_basename.c_str(), sideString(side)); fprintf(fp, "#endif\n\n"); for (size_t i = 0; i < size(); i++) { EntryPoint *e = &at(i); e->print(fp); fprintf(fp, "{\n"); fprintf(fp, "\tGET_CONTEXT;\n"); bool shouldReturn = !e->retval().isVoid(); bool shouldCallWithContext = (side == CLIENT_SIDE); //param check if (shouldCallWithContext) { for (size_t j=0; jvars().size(); j++) { if (e->vars()[j].paramCheckExpression() != "") fprintf(fp, "\t%s\n", e->vars()[j].paramCheckExpression().c_str()); } } fprintf(fp, "\t%sctx->%s(%s", shouldReturn ? "return " : "", e->name().c_str(), shouldCallWithContext ? "ctx" : ""); size_t nvars = e->vars().size(); for (size_t j = 0; j < nvars; j++) { if (!e->vars()[j].isVoid()) { fprintf(fp, "%s %s", j != 0 || shouldCallWithContext ? "," : "", e->vars()[j].name().c_str()); } } fprintf(fp, ");\n"); fprintf(fp, "}\n\n"); } fclose(fp); return 0; } int ApiGen::genOpcodes(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return errno; } printHeader(fp); fprintf(fp, "#ifndef __GUARD_%s_opcodes_h_\n", m_basename.c_str()); fprintf(fp, "#define __GUARD_%s_opcodes_h_\n\n", m_basename.c_str()); for (size_t i = 0; i < size(); i++) { fprintf(fp, "#define OP_%s \t\t\t\t\t%u\n", at(i).name().c_str(), (unsigned int)i + m_baseOpcode); } fprintf(fp, "#define OP_last \t\t\t\t\t%u\n", (unsigned int)size() + m_baseOpcode); fprintf(fp,"\n\n#endif\n"); fclose(fp); return 0; } int ApiGen::genAttributesTemplate(const std::string &filename ) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } for (size_t i = 0; i < size(); i++) { if (at(i).hasPointers()) { fprintf(fp, "#"); at(i).print(fp); fprintf(fp, "%s\n\n", at(i).name().c_str()); } } fclose(fp); return 0; } int ApiGen::genEncoderHeader(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); std::string classname = m_basename + "_encoder_context_t"; fprintf(fp, "\n#ifndef GUARD_%s\n", classname.c_str()); fprintf(fp, "#define GUARD_%s\n\n", classname.c_str()); fprintf(fp, "#include \"IOStream.h\"\n"); fprintf(fp, "#include \"ChecksumCalculator.h\"\n"); fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(CLIENT_SIDE)); for (size_t i = 0; i < m_encoderHeaders.size(); i++) { fprintf(fp, "#include %s\n", m_encoderHeaders[i].c_str()); } fprintf(fp, "\n"); fprintf(fp, "struct %s : public %s_%s_context_t {\n\n", classname.c_str(), m_basename.c_str(), sideString(CLIENT_SIDE)); fprintf(fp, "\tIOStream *m_stream;\n"); fprintf(fp, "\tChecksumCalculator *m_checksumCalculator;\n\n"); fprintf(fp, "\t%s(IOStream *stream, ChecksumCalculator *checksumCalculator);\n", classname.c_str()); fprintf(fp, "};\n\n"); fprintf(fp, "#endif // GUARD_%s", classname.c_str()); fclose(fp); return 0; } // Format the byte length expression for a given variable into a user-provided buffer // If the variable type is not a pointer, this is simply its size as a decimal constant // If the variable is a pointer, this will be an expression provided by the .attrib file // through the 'len' attribute. // // Returns 1 if the variable is a pointer, 0 otherwise // static int getVarEncodingSizeExpression(Var& var, EntryPoint* e, char* buff, size_t bufflen) { int ret = 0; if (!var.isPointer()) { snprintf(buff, bufflen, "%u", (unsigned int) var.type()->bytes()); } else { ret = 1; const char* lenExpr = var.lenExpression().c_str(); const char* varname = var.name().c_str(); if (e != NULL && lenExpr[0] == '\0') { fprintf(stderr, "%s: data len is undefined for '%s'\n", e->name().c_str(), varname); } if (var.nullAllowed()) { snprintf(buff, bufflen, "((%s != NULL) ? %s : 0)", varname, lenExpr); } else { snprintf(buff, bufflen, "%s", lenExpr); } } return ret; } static int writeVarEncodingSize(Var& var, FILE* fp) { int ret = 0; if (!var.isPointer()) { fprintf(fp, "%u", (unsigned int) var.type()->bytes()); } else { ret = 1; fprintf(fp, "__size_%s", var.name().c_str()); } return ret; } static void writeVarEncodingExpression(Var& var, FILE* fp) { const char* varname = var.name().c_str(); if (var.isPointer()) { // encode a pointer header fprintf(fp, "\t*(unsigned int *)(ptr) = __size_%s; ptr += 4;\n", varname); Var::PointerDir dir = var.pointerDir(); if (dir == Var::POINTER_INOUT || dir == Var::POINTER_IN) { if (var.nullAllowed()) { fprintf(fp, "\tif (%s != NULL) ", varname); } else { fprintf(fp, "\t"); } if (var.packExpression().size() != 0) { fprintf(fp, "%s;", var.packExpression().c_str()); } else { fprintf(fp, "memcpy(ptr, %s, __size_%s);", varname, varname); } fprintf(fp, "ptr += __size_%s;\n", varname); } } else { // encode a non pointer variable if (!var.isVoid()) { fprintf(fp, "\t\tmemcpy(ptr, &%s, %u); ptr += %u;\n", varname, (unsigned) var.type()->bytes(), (unsigned) var.type()->bytes()); } } } #if WITH_LARGE_SUPPORT static void writeVarLargeEncodingExpression(Var& var, FILE* fp) { const char* varname = var.name().c_str(); fprintf(fp, "\tstream->writeFully(&__size_%s,4);\n", varname); fprintf(fp, "\tif (useChecksum) checksumCalculator->addBuffer(&__size_%s,4);\n", varname); if (var.nullAllowed()) { fprintf(fp, "\tif (%s != NULL) {\n", varname); } if (var.writeExpression() != "") { fprintf(fp, "%s", var.writeExpression().c_str()); } else { fprintf(fp, "\t\tstream->writeFully(%s, __size_%s);\n", varname, varname); fprintf(fp, "\t\tif (useChecksum) checksumCalculator->addBuffer(%s, __size_%s);\n", varname, varname); } if (var.nullAllowed()) fprintf(fp, "\t}\n"); } #endif /* WITH_LARGE_SUPPORT */ static void writeEncodingChecksumValidatorOnReturn(const char* funcName, FILE* fp) { fprintf(fp, "\tif (useChecksum) {\n" "\t\tstd::unique_ptr checksumBuf(new unsigned char[checksumSize]);\n" "\t\tstream->readback(checksumBuf.get(), checksumSize);\n" "\t\tif (!checksumCalculator->validate(checksumBuf.get(), checksumSize)) {\n" "\t\t\tALOGE(\"%s: GL communication error, please report this issue to b.android.com.\\n\");\n" "\t\t\tabort();\n" "\t\t}\n" "\t}\n", funcName ); } int ApiGen::genEncoderImpl(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); fprintf(fp, "\n\n"); fprintf(fp, "#include \n"); fprintf(fp, "#include \n"); fprintf(fp, "#include \"%s_opcodes.h\"\n\n", m_basename.c_str()); fprintf(fp, "#include \"%s_enc.h\"\n\n\n", m_basename.c_str()); fprintf(fp, "#include \n\n"); fprintf(fp, "namespace {\n\n"); // unsupport printout fprintf(fp, "void enc_unsupported()\n" "{\n" "\tALOGE(\"Function is unsupported\\n\");\n" "}\n\n"); // entry points; std::string classname = m_basename + "_encoder_context_t"; size_t n = size(); for (size_t i = 0; i < n; i++) { EntryPoint *e = &at(i); if (e->unsupported()) continue; e->print(fp, true, "_enc", /* classname + "::" */"", "void *self"); fprintf(fp, "{\n"); // fprintf(fp, "\n\tDBG(\">>>> %s\\n\");\n", e->name().c_str()); fprintf(fp, "\n\t%s *ctx = (%s *)self;\n", classname.c_str(), classname.c_str()); fprintf(fp, "\tIOStream *stream = ctx->m_stream;\n" "\tChecksumCalculator *checksumCalculator = ctx->m_checksumCalculator;\n" "\tbool useChecksum = checksumCalculator->getVersion() > 0;\n\n"); VarsArray & evars = e->vars(); size_t maxvars = evars.size(); size_t j; char buff[256]; // Define the __size_XXX variables that contain the size of data // associated with pointers. for (j = 0; j < maxvars; j++) { Var& var = evars[j]; if (!var.isPointer()) continue; const char* varname = var.name().c_str(); fprintf(fp, "\tconst unsigned int __size_%s = ", varname); getVarEncodingSizeExpression(var, e, buff, sizeof(buff)); fprintf(fp, "%s;\n", buff); } bool hasLargeFields = false; #if WITH_LARGE_SUPPORT // We need to take care of 'isLarge' variable in a special way // Anything before an isLarge variable can be packed into a single // buffer, which is then commited. Each isLarge variable is a pointer // to data that can be written to directly through the pipe, which // will be instant when using a QEMU pipe size_t nvars = 0; size_t npointers = 0; // First, compute the total size, 8 bytes for the opcode + payload size (without checksum) fprintf(fp, "\t unsigned char *ptr;\n"); fprintf(fp, "\t unsigned char *buf;\n"); fprintf(fp, "\t const size_t sizeWithoutChecksum = 8"); for (j = 0; j < maxvars; j++) { fprintf(fp, " + "); npointers += writeVarEncodingSize(evars[j], fp); } if (npointers > 0) { fprintf(fp, " + %zu*4", npointers); } fprintf(fp, ";\n"); // Then, size of the checksum string fprintf(fp, "\t const size_t checksumSize = checksumCalculator->checksumByteSize();\n"); // And, size of the whole thing fprintf(fp, "\t const size_t totalSize = sizeWithoutChecksum + checksumSize;\n"); // We need to divide the packet into fragments. Each fragment contains // either copied arguments to a temporary buffer, or direct writes for // large variables. // // The first fragment must also contain the opcode+payload_size+checksum_size // nvars = 0; while (nvars < maxvars || maxvars == 0) { // Skip over non-large fields for (j = nvars; j < maxvars; j++) { if (evars[j].isLarge()) break; } // Write a fragment if needed. if (nvars == 0 || j > nvars) { const char* plus = ""; if (nvars == 0 && j == maxvars) { // Simple shortcut for the common case where we don't have large variables; fprintf(fp, "\tbuf = stream->alloc(totalSize);\n"); } else { hasLargeFields = true; // allocate buffer from the stream until the first large variable fprintf(fp, "\tbuf = stream->alloc("); plus = ""; if (nvars == 0) { fprintf(fp,"8"); plus = " + "; } if (j > nvars) { npointers = 0; for (j = nvars; j < maxvars && !evars[j].isLarge(); j++) { fprintf(fp, "%s", plus); plus = " + "; npointers += writeVarEncodingSize(evars[j], fp); } if (npointers > 0) { fprintf(fp, "%s%zu*4", plus, npointers); plus = " + "; } } fprintf(fp,");\n"); } fprintf(fp, "\tptr = buf;\n"); // encode packet header if needed. if (nvars == 0) { fprintf(fp, "\tint tmp = OP_%s;memcpy(ptr, &tmp, 4); ptr += 4;\n", e->name().c_str()); fprintf(fp, "\tmemcpy(ptr, &totalSize, 4); ptr += 4;\n\n"); } if (maxvars == 0) { fprintf(fp, "\n\tif (useChecksum) checksumCalculator->addBuffer(buf, ptr-buf);\n"); break; } // encode non-large fields in this fragment for (j = nvars; j < maxvars && !evars[j].isLarge(); j++) { writeVarEncodingExpression(evars[j],fp); } fprintf(fp, "\n\tif (useChecksum) checksumCalculator->addBuffer(buf, ptr-buf);\n"); // Ensure the fragment is commited if it is followed by a large variable if (j < maxvars) { fprintf(fp, "\tstream->flush();\n"); } } // If we have one or more large variables, write them directly. // As size + data for ( ; j < maxvars && evars[j].isLarge(); j++) { writeVarLargeEncodingExpression(evars[j], fp); } nvars = j; } #else /* !WITH_LARGE_SUPPORT */ size_t nvars = evars.size(); size_t npointers = 0; fprintf(fp, "\t const size_t sizeWithoutChecksum = 8"); for (size_t j = 0; j < nvars; j++) { npointers += getVarEncodingSizeExpression(evars[j],e,buff,sizeof(buff)); fprintf(fp, " + %s", buff); } fprintf(fp, " + %u * 4;\n", (unsigned int) npointers); // Size of checksum fprintf(fp, "\t const size_t checksumSize = checksumCalculator->checksumByteSize();\n"); // Size of the whole thing fprintf(fp, "\t const size_t totalSize = sizeWithoutChecksum + checksumSize;\n"); // allocate buffer from the stream; fprintf(fp, "\t unsigned char *ptr = stream->alloc(sizeWithoutChecksum);\n\n"); // encode into the stream; fprintf(fp, "\tint tmp = OP_%s; memcpy(ptr, &tmp, 4); ptr += 4;\n", e->name().c_str()); fprintf(fp, "\tmemcpy(ptr, &sizeWithoutChecksum, 4); ptr += 4;\n\n"); // out variables for (size_t j = 0; j < nvars; j++) { writeVarEncodingExpression(evars[j], fp); } #endif /* !WITH_LARGE_SUPPORT */ // checksum if (hasLargeFields) { fprintf(fp, "\tbuf = stream->alloc(checksumSize);\n"); fprintf(fp, "\tif (useChecksum) checksumCalculator->writeChecksum(buf, checksumSize);\n\n"); } else { fprintf(fp, "\tif (useChecksum) checksumCalculator->writeChecksum(ptr, checksumSize); ptr += checksumSize;\n\n"); } // in variables; bool hasReadbackChecksum = false; for (size_t j = 0; j < nvars; j++) { if (evars[j].isPointer()) { Var::PointerDir dir = evars[j].pointerDir(); if (dir == Var::POINTER_INOUT || dir == Var::POINTER_OUT) { const char* varname = evars[j].name().c_str(); const char* indent = "\t"; if (evars[j].nullAllowed()) { fprintf(fp, "\tif (%s != NULL) {\n",varname); indent = "\t\t"; } fprintf(fp, "%sstream->readback(%s, __size_%s);\n", indent, varname, varname); fprintf(fp, "%sif (useChecksum) checksumCalculator->addBuffer(%s, __size_%s);\n", indent, varname, varname); if (evars[j].nullAllowed()) { fprintf(fp, "\t}\n"); } hasReadbackChecksum = true; } } } //XXX fprintf(fp, "\n\tDBG(\"<<<< %s\\n\");\n", e->name().c_str()); // todo - return value for pointers if (e->retval().isPointer()) { fprintf(stderr, "WARNING: %s : return value of pointer is unsupported\n", e->name().c_str()); if (e->flushOnEncode()) { fprintf(fp, "\tstream->flush();\n"); } fprintf(fp, "\t return NULL;\n"); } else if (e->retval().type()->name() != "void") { fprintf(fp, "\n\t%s retval;\n", e->retval().type()->name().c_str()); fprintf(fp, "\tstream->readback(&retval, %u);\n",(unsigned) e->retval().type()->bytes()); fprintf(fp, "\tif (useChecksum) checksumCalculator->addBuffer(&retval, %u);\n", (unsigned) e->retval().type()->bytes()); writeEncodingChecksumValidatorOnReturn(e->name().c_str(), fp); fprintf(fp, "\treturn retval;\n"); } else { if (e->flushOnEncode()) fprintf(fp, "\tstream->flush();\n"); if (hasReadbackChecksum) writeEncodingChecksumValidatorOnReturn(e->name().c_str(), fp); } fprintf(fp, "}\n\n"); } fprintf(fp, "} // namespace\n\n"); // constructor fprintf(fp, "%s::%s(IOStream *stream, ChecksumCalculator *checksumCalculator)\n{\n", classname.c_str(), classname.c_str()); fprintf(fp, "\tm_stream = stream;\n"); fprintf(fp, "\tm_checksumCalculator = checksumCalculator;\n\n"); for (size_t i = 0; i < n; i++) { EntryPoint *e = &at(i); if (e->unsupported()) { fprintf(fp, "\tthis->%s = (%s_%s_proc_t) &enc_unsupported;\n", e->name().c_str(), e->name().c_str(), sideString(CLIENT_SIDE)); } else { fprintf(fp, "\tthis->%s = &%s_enc;\n", e->name().c_str(), e->name().c_str()); } } fprintf(fp, "}\n\n"); fclose(fp); return 0; } int ApiGen::genDecoderHeader(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); std::string classname = m_basename + "_decoder_context_t"; fprintf(fp, "\n#ifndef GUARD_%s\n", classname.c_str()); fprintf(fp, "#define GUARD_%s\n\n", classname.c_str()); fprintf(fp, "#include \"IOStream.h\" \n"); fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(SERVER_SIDE)); if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0) { fprintf(fp, "\n#include \n"); fprintf(fp, "\n#include \n"); } fprintf(fp, "\n#include \"emugl/common/logging.h\"\n"); for (size_t i = 0; i < m_decoderHeaders.size(); i++) { fprintf(fp, "#include %s\n", m_decoderHeaders[i].c_str()); } fprintf(fp, "\n"); fprintf(fp, "struct %s : public %s_%s_context_t {\n\n", classname.c_str(), m_basename.c_str(), sideString(SERVER_SIDE)); fprintf(fp, "\tsize_t decode(void *buf, size_t bufsize, IOStream *stream);\n"); if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0){ fprintf(fp, "\tvoid freeShader(); \n\ \tvoid freeProgram(); \n\ \tstd::map m_programs; \n\ \tstd::map m_shaders; \n\ \tstd::mutex m_lock; \n\ "); } fprintf(fp, "\n};\n\n"); fprintf(fp, "#endif // GUARD_%s\n", classname.c_str()); fclose(fp); return 0; } int ApiGen::genContextImpl(const std::string &filename, SideType side) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); std::string classname = m_basename + "_" + sideString(side) + "_context_t"; size_t n = size(); fprintf(fp, "\n\n#include \n"); fprintf(fp, "#include \"%s_%s_context.h\"\n\n\n", m_basename.c_str(), sideString(side)); fprintf(fp, "#include \n\n"); fprintf(fp, "int %s::initDispatchByName(void *(*getProc)(const char *, void *userData), void *userData)\n{\n", classname.c_str()); for (size_t i = 0; i < n; i++) { EntryPoint *e = &at(i); fprintf(fp, "\t%s = (%s_%s_proc_t) getProc(\"%s\", userData);\n", e->name().c_str(), e->name().c_str(), sideString(side), e->name().c_str()); } fprintf(fp, "\treturn 0;\n"); fprintf(fp, "}\n\n"); fclose(fp); return 0; } int ApiGen::genDecoderImpl(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "wt"); if (fp == NULL) { perror(filename.c_str()); return -1; } printHeader(fp); std::string classname = m_basename + "_decoder_context_t"; size_t n = size(); fprintf(fp, "\n\n#include \n"); fprintf(fp, "#include \"%s_opcodes.h\"\n\n", m_basename.c_str()); fprintf(fp, "#include \"%s_dec.h\"\n\n\n", m_basename.c_str()); fprintf(fp, "#include \"ProtocolUtils.h\"\n\n"); fprintf(fp, "#include \"ChecksumCalculatorThreadInfo.h\"\n\n"); fprintf(fp, "#include \n\n"); fprintf(fp, "typedef unsigned int tsize_t; // Target \"size_t\", which is 32-bit for now. It may or may not be the same as host's size_t when emugen is compiled.\n\n"); // helper macros fprintf(fp, "# define DEBUG(...) do { if (emugl_cxt_logger) { emugl_cxt_logger(LogLevel::TRACE, __VA_ARGS__); } } while(0)\n\n"); fprintf(fp, "#ifdef CHECK_GLERROR\n" "# define SET_LASTCALL(name) sprintf(lastCall, #name)\n" "#else\n" "# define SET_LASTCALL(name) ((void)0)\n" "#endif\n\n"); // helper templates fprintf(fp, "using namespace emugl;\n\n"); // glsl shader/program free; if (strcmp(classname.c_str(), "gles2_decoder_context_t") == 0) { fprintf(fp, "void %s::freeShader(){\n", classname.c_str()); fprintf(fp, " \n\ \tauto it = m_shaders.begin();\n\ \tm_lock.lock();\n\ \twhile(it != m_shaders.end()) \n\ \t{\n\ \t\tthis->glDeleteShader(it->first);\n\ \t\tit++;\n\ \t}\n\ \tm_lock.unlock();\n\ }\n\n"); fprintf(fp, "void %s::freeProgram(){\n", classname.c_str()); fprintf(fp, " \n\ \tauto it = m_programs.begin(); \n\ \tm_lock.lock();\n\ \twhile(it != m_programs.end()) \n\ \t{\n\ \t\tthis->glDeleteProgram(it->first);\n\ \t\tit++;\n\ \t}\n\ \tm_lock.unlock();\n\ }\n\n"); } // decoder switch; fprintf(fp, "size_t %s::decode(void *buf, size_t len, IOStream *stream)\n{\n", classname.c_str()); fprintf(fp, " \n\ \tsize_t pos = 0;\n\ \tif (len < 8) return pos; \n\ \tunsigned char *ptr = (unsigned char *)buf;\n\ \tbool unknownOpcode = false; \n\ #ifdef CHECK_GL_ERROR \n\ \tchar lastCall[256] = {0}; \n\ #endif \n\ \twhile ((len - pos >= 8) && !unknownOpcode) { \n\ \t\tuint32_t opcode = *(uint32_t *)ptr; \n\ \t\tsize_t packetLen = *(uint32_t *)(ptr + 4);\n\ \t\tif (len - pos < packetLen) return pos; \n\ \t\tbool useChecksum = ChecksumCalculatorThreadInfo::getVersion() > 0;\n\ \t\tsize_t checksumSize = 0;\n\ \t\tif (useChecksum) {\n\ \t\t\tchecksumSize = ChecksumCalculatorThreadInfo::checksumByteSize();\n\ \t\t}\n\ \t\tswitch(opcode) {\n"); for (size_t f = 0; f < n; f++) { enum Pass_t { PASS_FIRST = 0, PASS_VariableDeclarations = PASS_FIRST, PASS_Protocol, PASS_TmpBuffAlloc, PASS_MemAlloc, PASS_DebugPrint, PASS_FunctionCall, PASS_FlushOutput, PASS_Epilog, PASS_LAST }; EntryPoint *e = &at(f); // construct a printout string; std::string printString = ""; for (size_t i = 0; i < e->vars().size(); i++) { Var *v = &e->vars()[i]; if (!v->isVoid()) printString += (v->isPointer() ? "%p(%u)" : v->type()->printFormat()) + " "; } printString += ""; // TODO - add for return value; fprintf(fp, "\t\tcase OP_%s: {\n", e->name().c_str()); bool totalTmpBuffExist = false; std::string totalTmpBuffOffset = "0"; std::string *tmpBufOffset = new std::string[e->vars().size()]; // construct retval type string std::string retvalType; if (!e->retval().isVoid()) { retvalType = e->retval().type()->name(); } for (int pass = PASS_FIRST; pass < PASS_LAST; pass++) { if (pass == PASS_FunctionCall && !e->retval().isVoid() && !e->retval().isPointer()) { fprintf(fp, "\t\t\t*(%s *)(&tmpBuf[%s]) = ", retvalType.c_str(), totalTmpBuffOffset.c_str()); } if (pass == PASS_FunctionCall) { fprintf(fp, "\t\t\tthis->%s(", e->name().c_str()); if (e->customDecoder()) { fprintf(fp, "this"); // add a context to the call } } else if (pass == PASS_DebugPrint) { fprintf(fp, "\t\t\tDEBUG(\"%s(%%p): %s(%s)\", stream", m_basename.c_str(), e->name().c_str(), printString.c_str()); if (e->vars().size() > 0 && !e->vars()[0].isVoid()) { fprintf(fp, ","); } } std::string varoffset = "8"; // skip the header VarsArray & evars = e->vars(); // allocate memory for out pointers; for (size_t j = 0; j < evars.size(); j++) { Var *v = & evars[j]; if (v->isVoid()) { continue; } const char* var_name = v->name().c_str(); const char* var_type_name = v->type()->name().c_str(); const unsigned var_type_bytes = v->type()->bytes(); if ((pass == PASS_FunctionCall) && (j != 0 || e->customDecoder())) { fprintf(fp, ", "); } if (pass == PASS_DebugPrint && j != 0) { fprintf(fp, ", "); } if (!v->isPointer()) { if (pass == PASS_VariableDeclarations) { fprintf(fp, "\t\t\t%s var_%s = Unpack<%s,uint%u_t>(ptr + %s);\n", var_type_name, var_name, var_type_name, var_type_bytes * 8U, varoffset.c_str()); } if (pass == PASS_FunctionCall || pass == PASS_DebugPrint) { fprintf(fp, "var_%s", var_name); } varoffset += " + " + toString(var_type_bytes); continue; } if (pass == PASS_VariableDeclarations) { fprintf(fp, "\t\t\tuint32_t size_%s __attribute__((unused)) = Unpack(ptr + %s);\n", var_name, varoffset.c_str()); } if (v->pointerDir() == Var::POINTER_IN || v->pointerDir() == Var::POINTER_INOUT) { if (pass == PASS_VariableDeclarations) { #if USE_ALIGNED_BUFFERS fprintf(fp, "\t\t\tInputBuffer inptr_%s(ptr + %s + 4, size_%s);\n", var_name, varoffset.c_str(), var_name); } if (pass == PASS_FunctionCall) { if (v->nullAllowed()) { fprintf(fp, "size_%s == 0 ? NULL : (%s)(inptr_%s.get())", var_name, var_type_name, var_name); } else { fprintf(fp, "(%s)(inptr_%s.get())", var_type_name, var_name); } } else if (pass == PASS_DebugPrint) { fprintf(fp, "(%s)(inptr_%s.get()), size_%s", var_type_name, var_name, var_name); } #else // !USE_ALIGNED_BUFFERS fprintf(fp, "unsigned char *inptr_%s = (ptr + %s + 4);\n", var_name, varoffset.c_str()); } if (pass == PASS_FunctionCall) { if (v->nullAllowed()) { fprintf(fp, "size_%s == 0 ? NULL : (%s)(inptr_%s)", var_name, var_type_name, var_name); } else { fprintf(fp, "(%s)(inptr_%s)", var_type_name, var_name); } } else if (pass == PASS_DebugPrint) { fprintf(fp, "(%s)(inptr_%s), size_%s", var_type_name, var_name, var_name); } #endif // !USE_ALIGNED_BUFFERS varoffset += " + 4 + size_"; varoffset += var_name; } else { // out pointer; if (pass == PASS_TmpBuffAlloc) { if (!totalTmpBuffExist) { fprintf(fp, "\t\t\tsize_t totalTmpSize = size_%s;\n", var_name); } else { fprintf(fp, "\t\t\ttotalTmpSize += size_%s;\n", var_name); } tmpBufOffset[j] = totalTmpBuffOffset; totalTmpBuffOffset += " + size_"; totalTmpBuffOffset += var_name; totalTmpBuffExist = true; } else if (pass == PASS_MemAlloc) { #if USE_ALIGNED_BUFFERS fprintf(fp, "\t\t\tOutputBuffer outptr_%s(&tmpBuf[%s], size_%s);\n", var_name, tmpBufOffset[j].c_str(), var_name); } else if (pass == PASS_FunctionCall) { if (v->nullAllowed()) { fprintf(fp, "size_%s == 0 ? NULL : (%s)(outptr_%s.get())", var_name, var_type_name, var_name); } else { fprintf(fp, "(%s)(outptr_%s.get())", var_type_name, var_name); } } else if (pass == PASS_DebugPrint) { fprintf(fp, "(%s)(outptr_%s.get()), size_%s", var_type_name, var_name, var_name); } if (pass == PASS_FlushOutput) { fprintf(fp, "\t\t\toutptr_%s.flush();\n", var_name); } #else // !USE_ALIGNED_BUFFERS fprintf(fp, "\t\t\tunsigned char *outptr_%s = &tmpBuf[%s];\n", var_name, tmpBufOffset[j].c_str()); fprintf(fp, "\t\t\tmemset(outptr_%s, 0, %s);\n", var_name, toString(v->type()->bytes()).c_str()); } else if (pass == PASS_FunctionCall) { if (v->nullAllowed()) { fprintf(fp, "size_%s == 0 ? NULL : (%s)(outptr_%s)", var_name, var_type_name, var_name); } else { fprintf(fp, "(%s)(outptr_%s)", var_type_name, var_name); } } else if (pass == PASS_DebugPrint) { fprintf(fp, "(%s)(outptr_%s), size_%s", var_type_name, var_name, varoffset.c_str()); } #endif // !USE_ALIGNED_BUFFERS varoffset += " + 4"; } } if (pass == PASS_Protocol) { fprintf(fp, "\t\t\tif (useChecksum) {\n" "\t\t\t\tChecksumCalculatorThreadInfo::validOrDie(ptr, %s, " "ptr + %s, checksumSize, " "\n\t\t\t\t\t\"%s::decode," " OP_%s: GL checksumCalculator failure\\n\");\n" "\t\t\t}\n", varoffset.c_str(), varoffset.c_str(), varoffset.c_str(), classname.c_str() ); varoffset += " + 4"; } if (pass == PASS_FunctionCall || pass == PASS_DebugPrint) { fprintf(fp, ");\n"); } if (pass == PASS_TmpBuffAlloc) { if (!e->retval().isVoid() && !e->retval().isPointer()) { if (!totalTmpBuffExist) fprintf(fp, "\t\t\tsize_t totalTmpSize = sizeof(%s);\n", retvalType.c_str()); else fprintf(fp, "\t\t\ttotalTmpSize += sizeof(%s);\n", retvalType.c_str()); totalTmpBuffExist = true; } if (totalTmpBuffExist) { fprintf(fp, "\t\t\ttotalTmpSize += checksumSize;\n" "\t\t\tunsigned char *tmpBuf = stream->alloc(totalTmpSize);\n"); } } if (pass == PASS_Epilog) { // send back out pointers data as well as retval if (totalTmpBuffExist) { fprintf(fp, "\t\t\tif (useChecksum) {\n" "\t\t\t\tChecksumCalculatorThreadInfo::writeChecksum(" "&tmpBuf[0], totalTmpSize - checksumSize, " "&tmpBuf[totalTmpSize - checksumSize], checksumSize);\n" "\t\t\t}\n" "\t\t\tstream->flush();\n"); } } } // pass; fprintf(fp, "\t\t\tSET_LASTCALL(\"%s\");\n", e->name().c_str()); if (strcmp(m_basename.c_str(), "gles2") == 0) { if (strcmp(e->name().c_str(), "glAttachShader") == 0){ fprintf(fp, "\n\ \t\t\tm_lock.lock();\n\ m_shaders.insert({var_shader, 1});\n\ m_lock.unlock();\n"); } else if(strcmp(e->name().c_str(), "glDeleteProgram") == 0){ fprintf(fp, "\t\t\tm_lock.lock(); \n" "\t\t\tauto pro = m_programs.find(var_program); \n" "\t\t\tif (pro != m_programs.end()) \n" "\t\t\t{ \n" "\t\t\t\tm_programs.erase(pro); \n" "\t\t\t}\n" "\t\t\tm_lock.unlock();\n"); } else if(strcmp(e->name().c_str(), "glDeleteShader") == 0){ fprintf(fp, "\t\t\tm_lock.lock(); \n\ \t\t\tauto shader = m_shaders.find(var_shader); \n\ \t\t\tif (shader != m_shaders.end()) \n\ \t\t\t{ \n\ \t\t\t\tm_shaders.erase(shader); \n\ \t\t\t} \n\ \t\t\tm_lock.unlock(); \n"); } else if(strcmp(e->name().c_str(), "glLinkProgram") == 0){ fprintf(fp, " \n\ \t\t\tm_lock.lock();\n\ \t\t\tm_programs.insert({var_program, 1});\n\ \t\t\tm_lock.unlock();\n"); } } fprintf(fp, "\t\t\tbreak;\n"); fprintf(fp, "\t\t}\n"); delete [] tmpBufOffset; } fprintf(fp, "\t\t\tdefault:\n"); fprintf(fp, "\t\t\t\tunknownOpcode = true;\n"); fprintf(fp, "\t\t} //switch\n"); if (strstr(m_basename.c_str(), "gl")) { fprintf(fp, "#ifdef CHECK_GL_ERROR\n"); fprintf(fp, "\tint err = lastCall[0] ? this->glGetError() : GL_NO_ERROR;\n"); fprintf(fp, "\tif (err) fprintf(stderr, \"%s Error: 0x%%X in %%s\\n\", err, lastCall);\n", m_basename.c_str()); fprintf(fp, "#endif\n"); } fprintf(fp, "\t\tif (!unknownOpcode) {\n"); fprintf(fp, "\t\t\tpos += packetLen;\n"); fprintf(fp, "\t\t\tptr += packetLen;\n"); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t} // while\n"); fprintf(fp, "\treturn pos;\n"); fprintf(fp, "}\n"); fclose(fp); return 0; } int ApiGen::readSpec(const std::string & filename) { FILE *specfp = fopen(filename.c_str(), "rt"); if (specfp == NULL) { return -1; } char line[1000]; unsigned int lc = 0; while (fgets(line, sizeof(line), specfp) != NULL) { lc++; EntryPoint ref; if (ref.parse(lc, std::string(line))) { push_back(ref); updateMaxEntryPointsParams(ref.vars().size()); } } fclose(specfp); return 0; } int ApiGen::readAttributes(const std::string & attribFilename) { enum { ST_NAME, ST_ATT } state; FILE *fp = fopen(attribFilename.c_str(), "rt"); if (fp == NULL) { perror(attribFilename.c_str()); return -1; } char buf[1000]; state = ST_NAME; EntryPoint *currentEntry = NULL; size_t lc = 0; bool globalAttributes = false; while (fgets(buf, sizeof(buf), fp) != NULL) { lc++; std::string line(buf); if (line.size() == 0) continue; // could that happen? if (line.at(0) == '#') continue; // comment size_t first = line.find_first_not_of(" \t\n"); if (state == ST_ATT && (first == std::string::npos || first == 0)) state = ST_NAME; line = trim(line); if (line.size() == 0 || line.at(0) == '#') continue; switch(state) { case ST_NAME: if (line == "GLOBAL") { globalAttributes = true; } else { globalAttributes = false; currentEntry = findEntryByName(line); if (currentEntry == NULL) { fprintf(stderr, "WARNING: %u: attribute of non existant entry point %s\n", (unsigned int)lc, line.c_str()); } } state = ST_ATT; break; case ST_ATT: if (globalAttributes) { setGlobalAttribute(line, lc); } else if (currentEntry != NULL) { currentEntry->setAttribute(line, lc); } break; } } return 0; } int ApiGen::setGlobalAttribute(const std::string & line, size_t lc) { size_t pos = 0; size_t last; std::string token = getNextToken(line, pos, &last, WHITESPACE); pos = last; if (token == "base_opcode") { std::string str = getNextToken(line, pos, &last, WHITESPACE); if (str.size() == 0) { fprintf(stderr, "line %u: missing value for base_opcode\n", (unsigned) lc); } else { setBaseOpcode(atoi(str.c_str())); } } else if (token == "encoder_headers") { std::string str = getNextToken(line, pos, &last, WHITESPACE); pos = last; while (str.size() != 0) { encoderHeaders().push_back(str); str = getNextToken(line, pos, &last, WHITESPACE); pos = last; } } else if (token == "client_context_headers") { std::string str = getNextToken(line, pos, &last, WHITESPACE); pos = last; while (str.size() != 0) { clientContextHeaders().push_back(str); str = getNextToken(line, pos, &last, WHITESPACE); pos = last; } } else if (token == "server_context_headers") { std::string str = getNextToken(line, pos, &last, WHITESPACE); pos = last; while (str.size() != 0) { serverContextHeaders().push_back(str); str = getNextToken(line, pos, &last, WHITESPACE); pos = last; } } else if (token == "decoder_headers") { std::string str = getNextToken(line, pos, &last, WHITESPACE); pos = last; while (str.size() != 0) { decoderHeaders().push_back(str); str = getNextToken(line, pos, &last, WHITESPACE); pos = last; } } else { fprintf(stderr, "WARNING: %u : unknown global attribute %s\n", (unsigned int)lc, line.c_str()); } return 0; }