/
redgpu
/
ispc
Обзор
Документация
Войти
/
redgpu
/
ispc
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/header.cpp
1 486 строк
62 KB
Alex Fuller
Ordering of the struct in __ISPC_ALIGNED_STRUCT__ changed in recent MSVC compilers
16 окт 2025, 00:52
16 окт 2025, 00:52
ff2a112
Код
Авторство
О чём код?
/* Copyright (c) 2025, Intel Corporation SPDX-License-Identifier: BSD-3-Clause */ /** @file header.cpp @brief This file contains the code to generate the header file, device and host stubs, dependency files. */ #include "expr.h" #include "ispc.h" #include "module.h" #include "sym.h" #include "target_enums.h" #include "type.h" #include "util.h" #include <ctype.h> #include <set> #include <sstream> #include <stdio.h> #include <string.h> #include <string> #include <utility> #include <vector> #include <llvm/IR/DataLayout.h> #include <llvm/IR/DerivedTypes.h> #include <llvm/Support/Casting.h> #include <llvm/Support/Path.h> using namespace ispc; /** Given a pointer to an element of a structure, see if it is a struct type or an array of a struct type. If so, return a pointer to the underlying struct type. */ static const StructType *lGetElementStructType(const Type *t) { const StructType *st = CastType<StructType>(t); if (st != nullptr) { return st; } const ArrayType *at = CastType<ArrayType>(t); if (at != nullptr) { return lGetElementStructType(at->GetElementType()); } return nullptr; } static bool lContainsPtrToVarying(const StructType *st) { int numElts = st->GetElementCount(); for (int j = 0; j < numElts; ++j) { const Type *t = st->GetElementType(j); if (t->IsVaryingType()) { return true; } } return false; } /** Emits a declaration for the given struct to the given file. This function first makes sure that declarations for any structs that are (recursively) members of this struct are emitted first. */ static void lEmitStructDecl(const StructType *st, std::vector<const StructType *> *emittedStructs, FILE *file, bool emitUnifs = true) { // if we're emitting this for a generic dispatch header file and it's // struct that only contains uniforms, don't bother if we're emitting uniforms if (!emitUnifs && !lContainsPtrToVarying(st)) { return; } // Has this struct type already been declared? (This happens if it's a // member of another struct for which we emitted a declaration // previously.) for (int i = 0; i < (int)emittedStructs->size(); ++i) { if (Type::EqualIgnoringConst(st, (*emittedStructs)[i])) { return; } } // Otherwise first make sure any contained structs have been declared. for (int i = 0; i < st->GetElementCount(); ++i) { const StructType *elementStructType = lGetElementStructType(st->GetElementType(i)); if (elementStructType != nullptr) { lEmitStructDecl(elementStructType, emittedStructs, file, emitUnifs); } } // And now it's safe to declare this one emittedStructs->push_back(st); fprintf(file, "#ifndef __ISPC_STRUCT_%s__\n", st->GetCStructName().c_str()); fprintf(file, "#define __ISPC_STRUCT_%s__\n", st->GetCStructName().c_str()); char sSOA[48]; bool pack = false, needsAlign = false; llvm::Type *stype = st->LLVMType(g->ctx); const llvm::DataLayout *DL = g->target->getDataLayout(); unsigned int alignment = st->GetAlignment(); llvm::StructType *stypeStructType = llvm::dyn_cast<llvm::StructType>(stype); Assert(stypeStructType); if (!(pack = stypeStructType->isPacked())) { for (int i = 0; !needsAlign && (i < st->GetElementCount()); ++i) { const Type *ftype = st->GetElementType(i)->GetAsNonConstType(); needsAlign |= ftype->IsVaryingType() && (CastType<StructType>(ftype) == nullptr); } } if (alignment) { needsAlign = true; } if (needsAlign && alignment == 0) { alignment = DL->getABITypeAlign(stype).value(); } if (st->GetSOAWidth() > 0) { // This has to match the naming scheme in // StructType::GetDeclaration(). snprintf(sSOA, sizeof(sSOA), "_SOA%d", st->GetSOAWidth()); } else { *sSOA = '\0'; } if (!needsAlign) { fprintf(file, "%sstruct %s%s {\n", (pack) ? "packed " : "", st->GetCStructName().c_str(), sSOA); } else { fprintf(file, "__ISPC_ALIGNED_STRUCT__(%u) %s%s {\n", alignment, st->GetCStructName().c_str(), sSOA); } for (int i = 0; i < st->GetElementCount(); ++i) { std::string name = st->GetElementName(i); const Type *ftype = st->GetElementType(i)->GetAsNonConstType(); std::string d_cpp = ftype->GetDeclaration(name, DeclarationSyntax::CPP); std::string d_c = ftype->GetDeclaration(name, DeclarationSyntax::C); bool same_decls = d_c == d_cpp; if (needsAlign && ftype->IsVaryingType() && (CastType<StructType>(ftype) == nullptr)) { unsigned uABI = DL->getABITypeAlign(ftype->LLVMStorageType(g->ctx)).value(); fprintf(file, " __ISPC_ALIGN__(%u) ", uABI); } if (!same_decls) { fprintf(file, "\n#if defined(__cplusplus)\n"); } // Don't expand arrays, pointers and structures: // their insides will be expanded automatically. if (!ftype->IsArrayType() && !ftype->IsPointerType() && ftype->IsVaryingType() && (CastType<StructType>(ftype) == nullptr)) { fprintf(file, " %s[%d];\n", d_cpp.c_str(), g->target->getVectorWidth()); if (!same_decls) { fprintf(file, "#else\n" " %s[%d];\n", d_c.c_str(), g->target->getVectorWidth()); } } else if (CastType<VectorType>(ftype) != nullptr) { fprintf(file, " struct %s;\n", d_cpp.c_str()); if (!same_decls) { fprintf(file, "#else\n" " struct %s;\n", d_c.c_str()); } } else { fprintf(file, " %s;\n", d_cpp.c_str()); if (!same_decls) { fprintf(file, "#else\n" " %s;\n", d_c.c_str()); } } if (!same_decls) { fprintf(file, "#endif // %s field\n", name.c_str()); } } fprintf(file, "};\n"); fprintf(file, "#endif\n\n"); } /** Given a set of structures that we want to print C declarations of in a header file, emit their declarations. */ static void lEmitStructDecls(std::vector<const StructType *> &structTypes, FILE *file, bool emitUnifs = true) { std::vector<const StructType *> emittedStructs; fprintf(file, "\n/* Portable alignment macro that works across different compilers and standards */\n" "#if defined(__cplusplus) && __cplusplus >= 201103L\n" "/* C++11 or newer - use alignas keyword */\n" "#define __ISPC_ALIGN__(x) alignas(x)\n" "#elif defined(__GNUC__) || defined(__clang__)\n" "/* GCC or Clang - use __attribute__ */\n" "#define __ISPC_ALIGN__(x) __attribute__((aligned(x)))\n" "#elif defined(_MSC_VER)\n" "/* Microsoft Visual C++ - use __declspec */\n" "#define __ISPC_ALIGN__(x) __declspec(align(x))\n" "#else\n" "/* Unknown compiler/standard - alignment not supported */\n" "#define __ISPC_ALIGN__(x)\n" "#warning \"Alignment not supported on this compiler\"\n" "#endif // defined(__cplusplus) && __cplusplus >= 201103L\n" "#ifndef __ISPC_ALIGNED_STRUCT__\n" "#if defined(__clang__) || !defined(_MSC_VER) || _MSC_VER > 1943\n" "// Clang, GCC, ICC, Visual Studio\n" "#define __ISPC_ALIGNED_STRUCT__(s) struct __ISPC_ALIGN__(s)\n" "#else\n" "// Older Visual Studio\n" "#define __ISPC_ALIGNED_STRUCT__(s) __ISPC_ALIGN__(s) struct\n" "#endif // defined(__clang__) || !defined(_MSC_VER) || _MSC_VER > 1943\n" "#endif // __ISPC_ALIGNED_STRUCT__\n\n"); for (unsigned int i = 0; i < structTypes.size(); ++i) { lEmitStructDecl(structTypes[i], &emittedStructs, file, emitUnifs); } } /** Emit C declarations of enumerator types to the generated header file. */ static void lEmitEnumDecls(const std::vector<const EnumType *> &enumTypes, FILE *file) { if (enumTypes.size() == 0) { return; } fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// Enumerator types with external visibility from ispc code\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n\n"); for (unsigned int i = 0; i < enumTypes.size(); ++i) { fprintf(file, "#ifndef __ISPC_ENUM_%s__\n", enumTypes[i]->GetEnumName().c_str()); fprintf(file, "#define __ISPC_ENUM_%s__\n", enumTypes[i]->GetEnumName().c_str()); std::string declaration = enumTypes[i]->GetDeclaration("", DeclarationSyntax::CPP); fprintf(file, "%s {\n", declaration.c_str()); // Print the individual enumerators for (int j = 0; j < enumTypes[i]->GetEnumeratorCount(); ++j) { const Symbol *e = enumTypes[i]->GetEnumerator(j); Assert(e->constValue != nullptr); unsigned int enumValue[1]; int count = e->constValue->GetValues(enumValue); Assert(count == 1); // Always print an initializer to set the value. We could be // 'clever' here and detect whether the implicit value given by // one plus the previous enumerator value (or zero, for the // first enumerator) is the same as the value stored with the // enumerator, though that doesn't seem worth the trouble... fprintf(file, " %s = %d%c\n", e->name.c_str(), enumValue[0], (j < enumTypes[i]->GetEnumeratorCount() - 1) ? ',' : ' '); } fprintf(file, "};\n"); fprintf(file, "#endif\n\n"); } } /** Print declarations of VectorTypes used in 'export'ed parts of the program in the header file. */ static void lEmitVectorTypedefs(const std::vector<const VectorType *> &types, FILE *file) { if (types.size() == 0) { return; } fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// Vector types with external visibility from ispc code\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n\n"); for (unsigned int i = 0; i < types.size(); ++i) { std::string baseDecl; const SequentialType *vt = types[i]->GetAsNonConstType(); if (!vt->IsUniformType()) { // Varying stuff shouldn't be visibile to / used by the // application, so at least make it not simple to access it by // not declaring the type here... continue; } int size = vt->GetElementCount(); llvm::Type *ty = vt->LLVMStorageType(g->ctx); int align = g->target->getDataLayout()->getABITypeAlign(ty).value(); baseDecl = vt->GetBaseType()->GetDeclaration("", DeclarationSyntax::CPP); fprintf(file, "#ifndef __ISPC_VECTOR_%s%d__\n", baseDecl.c_str(), size); fprintf(file, "#define __ISPC_VECTOR_%s%d__\n", baseDecl.c_str(), size); fprintf(file, "#ifdef _MSC_VER\n__declspec( align(%d) ) ", align); fprintf(file, "struct %s%d { %s v[%d]; };\n", baseDecl.c_str(), size, baseDecl.c_str(), size); fprintf(file, "#else\n"); fprintf(file, "struct %s%d { %s v[%d]; } __attribute__ ((aligned(%d)));\n", baseDecl.c_str(), size, baseDecl.c_str(), size, align); fprintf(file, "#endif\n"); fprintf(file, "#endif\n\n"); } fprintf(file, "\n"); } /** Add the given type to the vector, if that type isn't already in there. */ template <typename T> static void lAddTypeIfNew(const Type *type, std::vector<const T *> *exportedTypes) { type = type->GetAsNonConstType(); // Linear search, so this ends up being n^2. It's unlikely this will // matter in practice, though. for (unsigned int i = 0; i < exportedTypes->size(); ++i) { if (Type::Equal((*exportedTypes)[i], type)) { return; } } const T *castType = CastType<T>(type); Assert(castType != nullptr); exportedTypes->push_back(castType); } /** Given an arbitrary type that appears in the app/ispc interface, add it to an appropriate vector if it is a struct, enum, or short vector type. Then, if it's a struct, recursively process its members to do the same. */ static void lGetExportedTypes(const Type *type, std::vector<const StructType *> *exportedStructTypes, std::vector<const EnumType *> *exportedEnumTypes, std::vector<const VectorType *> *exportedVectorTypes) { const ArrayType *arrayType = CastType<ArrayType>(type); const StructType *structType = CastType<StructType>(type); const FunctionType *ftype = CastType<FunctionType>(type); if (CastType<ReferenceType>(type) != nullptr) { lGetExportedTypes(type->GetReferenceTarget(), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } else if (CastType<PointerType>(type) != nullptr) { lGetExportedTypes(type->GetBaseType(), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } else if (arrayType != nullptr) { lGetExportedTypes(arrayType->GetElementType(), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } else if (structType != nullptr) { lAddTypeIfNew(type, exportedStructTypes); for (int i = 0; i < structType->GetElementCount(); ++i) { lGetExportedTypes(structType->GetElementType(i), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } } else if (CastType<UndefinedStructType>(type) != nullptr) { // do nothing ; } else if (CastType<EnumType>(type) != nullptr) { lAddTypeIfNew(type, exportedEnumTypes); } else if (CastType<VectorType>(type) != nullptr) { lAddTypeIfNew(type, exportedVectorTypes); } else if (ftype != nullptr) { // Handle Return Types lGetExportedTypes(ftype->GetReturnType(), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); // And now the parameter types... for (int j = 0; j < ftype->GetNumParameters(); ++j) { lGetExportedTypes(ftype->GetParameterType(j), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } } else { Assert(CastType<AtomicType>(type) != nullptr); } } /** Given a set of functions, return the set of structure and vector types present in the parameters to them. */ static void lGetExportedParamTypes(const std::vector<Symbol *> &funcs, std::vector<const StructType *> *exportedStructTypes, std::vector<const EnumType *> *exportedEnumTypes, std::vector<const VectorType *> *exportedVectorTypes) { for (unsigned int i = 0; i < funcs.size(); ++i) { const FunctionType *ftype = CastType<FunctionType>(funcs[i]->type); Assert(ftype != nullptr); // Handle the return type lGetExportedTypes(ftype->GetReturnType(), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); // And now the parameter types... for (int j = 0; j < ftype->GetNumParameters(); ++j) { lGetExportedTypes(ftype->GetParameterType(j), exportedStructTypes, exportedEnumTypes, exportedVectorTypes); } } } static void lPrintFunctionDeclarations(FILE *file, const std::vector<Symbol *> &funcs, bool useExternC = 1, bool rewriteForDispatch = false) { if (useExternC) { fprintf(file, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\nextern " "\"C\" {\n#endif // __cplusplus\n"); } // fprintf(file, "#ifdef __cplusplus\nextern \"C\" {\n#endif // __cplusplus\n"); for (unsigned int i = 0; i < funcs.size(); ++i) { const FunctionType *ftype = CastType<FunctionType>(funcs[i]->type); Assert(ftype); std::string c_decl, cpp_decl; std::string fname = funcs[i]->name; if (g->calling_conv == CallingConv::x86_vectorcall) { fname = "__vectorcall " + fname; } if (rewriteForDispatch) { c_decl = ftype->GetDeclarationForDispatch(fname, DeclarationSyntax::C); cpp_decl = ftype->GetDeclarationForDispatch(fname, DeclarationSyntax::CPP); } else { c_decl = ftype->GetDeclaration(fname, DeclarationSyntax::C); cpp_decl = ftype->GetDeclaration(fname, DeclarationSyntax::CPP); } if (c_decl == cpp_decl) { fprintf(file, " extern %s;\n", c_decl.c_str()); } else { fprintf(file, "#if defined(__cplusplus)\n" " extern %s;\n", cpp_decl.c_str()); fprintf(file, "#else\n" " extern %s;\n", c_decl.c_str()); fprintf(file, "#endif // %s function declaraion\n", fname.c_str()); } } if (useExternC) { fprintf(file, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\n} /* end " "extern C */\n#endif // __cplusplus\n"); } } static bool lIsExported(const Symbol *sym) { const FunctionType *ft = CastType<FunctionType>(sym->type); Assert(ft); return ft->IsExported(); } static bool lIsExternC(const Symbol *sym) { const FunctionType *ft = CastType<FunctionType>(sym->type); Assert(ft); return ft->IsExternC(); } static void lUnescapeStringInPlace(std::string &str) { // There are many more escape sequences, but since this is a path, // we can get away with only supporting the basic ones (i.e. no // octal, hexadecimal or unicode values). for (std::string::iterator it = str.begin(); it != str.end(); ++it) { size_t pos = it - str.begin(); std::string::iterator next = it + 1; if (*it == '\\' && next != str.end()) { switch (*next) { #define UNESCAPE_SEQ(c, esc) \ case c: \ *it = esc; \ str.erase(next); \ it = str.begin() + pos; \ break UNESCAPE_SEQ('\'', '\''); UNESCAPE_SEQ('?', '?'); UNESCAPE_SEQ('\\', '\\'); UNESCAPE_SEQ('a', '\a'); UNESCAPE_SEQ('b', '\b'); UNESCAPE_SEQ('f', '\f'); UNESCAPE_SEQ('n', '\n'); UNESCAPE_SEQ('r', '\r'); UNESCAPE_SEQ('t', '\t'); UNESCAPE_SEQ('v', '\v'); #undef UNESCAPE_SEQ } } } } std::string Module::Output::DepsTargetName(const char *srcFile) const { if (!depsTarget.empty()) { return depsTarget; } if (!out.empty()) { return out; } if (!IsStdin(srcFile)) { std::string targetName = srcFile; size_t dot = targetName.find_last_of('.'); if (dot != std::string::npos) { targetName.erase(dot, std::string::npos); } return targetName + ".o"; } return "a.out"; } /** * This function creates a dependency file listing all headers and source files that * the current module depends on. The output can be in one of two formats: * 1. A Makefile rule (when makeRuleDeps is true) with the target depending on all files * 2. A flat list of all dependencies (when makeRuleDeps is false) * * The output can be written to a specified file or to stdout. * * @note In the case of dispatcher module generation, the customOutput * parameter should be used instead of the class member output, as the * dispatcher requires different output settings. */ bool Module::writeDeps(Output &CO) { bool generateMakeRule = CO.flags.isMakeRuleDeps(); std::string targetName = CO.DepsTargetName(srcFile); reportInvalidSuffixWarning(CO.deps, OutputType::Deps); if (g->debugPrint) { // We may be passed nullptr for stdout output. printf("\nWriting dependencies to file %s\n", CO.deps.c_str()); } FILE *file = !CO.deps.empty() ? fopen(CO.deps.c_str(), "w") : stdout; if (!file) { perror("fopen"); return false; } if (generateMakeRule) { fprintf(file, "%s:", targetName.c_str()); // Rules always emit source first. if (srcFile && !IsStdin(srcFile)) { fprintf(file, " %s", srcFile); } std::string unescaped; for (std::set<std::string>::const_iterator it = registeredDependencies.begin(); it != registeredDependencies.end(); ++it) { unescaped = *it; // As this is preprocessor output, paths come escaped. lUnescapeStringInPlace(unescaped); if (srcFile && !IsStdin(srcFile) && 0 == strcmp(srcFile, unescaped.c_str())) { // If source has been passed, it's already emitted. continue; } fprintf(file, " \\\n"); fprintf(file, " %s", unescaped.c_str()); } fprintf(file, "\n"); } else { for (std::set<std::string>::const_iterator it = registeredDependencies.begin(); it != registeredDependencies.end(); ++it) { fprintf(file, "%s\n", it->c_str()); } } fclose(file); return true; } std::string emitOffloadParamStruct(const std::string ¶mStructName, const Symbol *sym, const FunctionType *fct) { std::stringstream out; out << "struct " << paramStructName << " {" << std::endl; for (int i = 0; i < fct->GetNumParameters(); i++) { const Type *orgParamType = fct->GetParameterType(i); if (orgParamType->IsPointerType() || orgParamType->IsArrayType()) { /* we're passing pointers separately -- no pointers in that struct... */ continue; } // const reference parameters can be passed as copies. const Type *paramType = nullptr; if (orgParamType->IsReferenceType()) { if (!orgParamType->IsConstType()) { Error(sym->pos, "When emitting offload-stubs, \"export\"ed functions cannot have non-const " "reference-type parameters.\n"); } const ReferenceType *refType = static_cast<const ReferenceType *>(orgParamType); paramType = refType->GetReferenceTarget()->GetAsNonConstType(); } else { paramType = orgParamType->GetAsNonConstType(); } std::string paramName = fct->GetParameterName(i); std::string tmpArgDecl = paramType->GetDeclaration(paramName, DeclarationSyntax::CPP); out << " " << tmpArgDecl << ";" << std::endl; } out << "};" << std::endl; return out.str(); } bool Module::writeDevStub() { FILE *file = fopen(output.devStub.c_str(), "w"); reportInvalidSuffixWarning(output.devStub, OutputType::DevStub); if (!file) { perror("fopen"); return false; } fprintf(file, "//\n// %s\n// (device stubs automatically generated by the ispc compiler.)\n", output.devStub.c_str()); fprintf(file, "// DO NOT EDIT THIS FILE.\n//\n\n"); fprintf(file, "#include \"ispc/dev/offload.h\"\n\n"); fprintf(file, "#include <stdint.h>\n\n"); // Collect single linear arrays of the *exported* functions (we'll // treat those as "__kernel"s in IVL -- "extern" functions will only // be used for dev-dev function calls; only "export" functions will // get exported to the host std::vector<Symbol *> exportedFuncs; m->symbolTable->GetMatchingFunctions(lIsExported, &exportedFuncs); // Get all of the struct, vector, and enumerant types used as function // parameters. These vectors may have repeats. std::vector<const StructType *> exportedStructTypes; std::vector<const EnumType *> exportedEnumTypes; std::vector<const VectorType *> exportedVectorTypes; lGetExportedParamTypes(exportedFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); // And print them lEmitVectorTypedefs(exportedVectorTypes, file); lEmitEnumDecls(exportedEnumTypes, file); lEmitStructDecls(exportedStructTypes, file); fprintf(file, "#ifdef __cplusplus\n"); fprintf(file, "namespace ispc {\n"); fprintf(file, "#endif // __cplusplus\n"); fprintf(file, "\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// Functions exported from ispc code\n"); fprintf(file, "// (so the dev stub knows what to call)\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); lPrintFunctionDeclarations(file, exportedFuncs, true); fprintf(file, "#ifdef __cplusplus\n"); fprintf(file, "}/* end namespace */\n"); fprintf(file, "#endif // __cplusplus\n"); fprintf(file, "\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// actual dev stubs\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// note(iw): due to some linking issues offload stubs *only* work under C++\n"); fprintf(file, "extern \"C\" {\n\n"); for (unsigned int i = 0; i < exportedFuncs.size(); ++i) { const Symbol *sym = exportedFuncs[i]; Assert(sym); const FunctionType *fct = CastType<FunctionType>(sym->type); Assert(fct); if (!fct->GetReturnType()->IsVoidType()) { // Error(sym->pos,"When emitting offload-stubs, \"export\"ed functions cannot have non-void return // types.\n"); Warning(sym->pos, "When emitting offload-stubs, ignoring \"export\"ed function with non-void return types.\n"); continue; } // ------------------------------------------------------- // first, emit a struct that holds the parameters // ------------------------------------------------------- std::string paramStructName = std::string("__ispc_dev_stub_") + sym->name; std::string paramStruct = emitOffloadParamStruct(paramStructName, sym, fct); fprintf(file, "%s\n", paramStruct.c_str()); // ------------------------------------------------------- // then, emit a fct stub that unpacks the parameters and pointers // ------------------------------------------------------- fprintf(file, "void __ispc_dev_stub_%s(\n" " uint32_t in_BufferCount,\n" " void** in_ppBufferPointers,\n" " uint64_t* in_pBufferLengths,\n" " void* in_pMiscData,\n" " uint16_t in_MiscDataLength,\n" " void* in_pReturnValue,\n" " uint16_t in_ReturnValueLength)\n", sym->name.c_str()); fprintf(file, "{\n"); fprintf(file, " struct %s args;\n memcpy(&args,in_pMiscData,sizeof(args));\n", paramStructName.c_str()); std::stringstream funcall; funcall << "ispc::" << sym->name << "("; for (int i = 0; i < fct->GetNumParameters(); i++) { // get param type and make it non-const, so we can write while unpacking const Type *paramType = nullptr; const Type *orgParamType = fct->GetParameterType(i); if (orgParamType->IsReferenceType()) { if (!orgParamType->IsConstType()) { Error(sym->pos, "When emitting offload-stubs, \"export\"ed functions cannot have non-const " "reference-type parameters.\n"); } const ReferenceType *refType = static_cast<const ReferenceType *>(orgParamType); paramType = refType->GetReferenceTarget()->GetAsNonConstType(); } else { paramType = orgParamType->GetAsNonConstType(); } std::string paramName = fct->GetParameterName(i); if (i) { funcall << ", "; } std::string tmpArgName = std::string("_") + paramName; if (paramType->IsPointerType() || paramType->IsArrayType()) { std::string tmpArgDecl = paramType->GetDeclaration(tmpArgName, DeclarationSyntax::CPP); fprintf(file, " %s;\n", tmpArgDecl.c_str()); fprintf(file, " (void *&)%s = ispc_dev_translate_pointer(*in_ppBufferPointers++);\n", tmpArgName.c_str()); funcall << tmpArgName; } else { funcall << "args." << paramName; } } funcall << ");"; fprintf(file, " %s\n", funcall.str().c_str()); fprintf(file, "}\n\n"); } // end extern "C" fprintf(file, "}/* end extern C */\n"); fclose(file); return true; } bool Module::writeHostStub() { FILE *file = fopen(output.hostStub.c_str(), "w"); reportInvalidSuffixWarning(output.hostStub, OutputType::HostStub); if (!file) { perror("fopen"); return false; } fprintf(file, "//\n// %s\n// (device stubs automatically generated by the ispc compiler.)\n", output.hostStub.c_str()); fprintf(file, "// DO NOT EDIT THIS FILE.\n//\n\n"); fprintf(file, "#include \"ispc/host/offload.h\"\n\n"); fprintf( file, "// note(iw): Host stubs do not get extern C linkage -- dev-side already uses that for the same symbols.\n\n"); // fprintf(file,"#ifdef __cplusplus\nextern \"C\" {\n#endif // __cplusplus\n"); fprintf(file, "#ifdef __cplusplus\nnamespace ispc {\n#endif // __cplusplus\n\n"); // Collect single linear arrays of the *exported* functions (we'll // treat those as "__kernel"s in IVL -- "extern" functions will only // be used for dev-dev function calls; only "export" functions will // get exported to the host std::vector<Symbol *> exportedFuncs; m->symbolTable->GetMatchingFunctions(lIsExported, &exportedFuncs); // Get all of the struct, vector, and enumerant types used as function // parameters. These vectors may have repeats. std::vector<const StructType *> exportedStructTypes; std::vector<const EnumType *> exportedEnumTypes; std::vector<const VectorType *> exportedVectorTypes; lGetExportedParamTypes(exportedFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); // And print them lEmitVectorTypedefs(exportedVectorTypes, file); lEmitEnumDecls(exportedEnumTypes, file); lEmitStructDecls(exportedStructTypes, file); fprintf(file, "\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(file, "// host-side stubs for dev-side ISPC fucntion(s)\n"); fprintf(file, "///////////////////////////////////////////////////////////////////////////\n"); for (unsigned int i = 0; i < exportedFuncs.size(); ++i) { const Symbol *sym = exportedFuncs[i]; Assert(sym); const FunctionType *fct = CastType<FunctionType>(sym->type); Assert(fct); if (!fct->GetReturnType()->IsVoidType()) { Warning(sym->pos, "When emitting offload-stubs, ignoring \"export\"ed function with non-void return types.\n"); continue; } // ------------------------------------------------------- // first, emit a struct that holds the parameters // ------------------------------------------------------- std::string paramStructName = std::string("__ispc_dev_stub_") + sym->name; std::string paramStruct = emitOffloadParamStruct(paramStructName, sym, fct); fprintf(file, "%s\n", paramStruct.c_str()); // ------------------------------------------------------- // then, emit a fct stub that unpacks the parameters and pointers // ------------------------------------------------------- std::string decl = fct->GetDeclaration(sym->name, DeclarationSyntax::CPP); fprintf(file, "extern %s {\n", decl.c_str()); int numPointers = 0; fprintf(file, " %s __args;\n", paramStructName.c_str()); // ------------------------------------------------------------------ // write args, and save pointers for later // ------------------------------------------------------------------ std::stringstream pointerArgs; for (int i = 0; i < fct->GetNumParameters(); i++) { const Type *orgParamType = fct->GetParameterType(i); std::string paramName = fct->GetParameterName(i); if (orgParamType->IsPointerType() || orgParamType->IsArrayType()) { /* we're passing pointers separately -- no pointers in that struct... */ if (numPointers) { pointerArgs << ","; } pointerArgs << "(void*)" << paramName; numPointers++; continue; } fprintf(file, " __args.%s = %s;\n", paramName.c_str(), paramName.c_str()); } // ------------------------------------------------------------------ // writer pointer list // ------------------------------------------------------------------ if (numPointers == 0) { pointerArgs << "NULL"; } fprintf(file, " void *ptr_args[] = { %s };\n", pointerArgs.str().c_str()); // ------------------------------------------------------------------ // ... and call the kernel with those args // ------------------------------------------------------------------ fprintf(file, " static ispc_kernel_handle_t kernel_handle = NULL;\n"); fprintf(file, " if (!kernel_handle) kernel_handle = ispc_host_get_kernel_handle(\"__ispc_dev_stub_%s\");\n", sym->name.c_str()); fprintf(file, " assert(kernel_handle);\n"); fprintf(file, " ispc_host_call_kernel(kernel_handle,\n" " &__args, sizeof(__args),\n" " ptr_args,%i);\n", numPointers); fprintf(file, "}\n\n"); } // end extern "C" fprintf(file, "#ifdef __cplusplus\n"); fprintf(file, "}/* namespace */\n"); fprintf(file, "#endif // __cplusplus\n"); // fprintf(file, "#ifdef __cplusplus\n"); // fprintf(file, "}/* end extern C */\n"); // fprintf(file, "#endif // __cplusplus\n"); fclose(file); return true; } void Module::writeHeader(FILE *f) { // Create a nice guard string from the filename, turning any // non-number/letter characters into underbars std::string guard = "ISPC_"; const char *p = output.header.c_str(); while (*p) { if (isdigit(*p)) { guard += *p; } else if (isalpha(*p)) { guard += toupper(*p); } else { guard += "_"; } ++p; } if (g->noPragmaOnce) { fprintf(f, "#ifndef %s\n#define %s\n\n", guard.c_str(), guard.c_str()); } else { fprintf(f, "#pragma once\n"); } fprintf(f, "#include <stdint.h>\n\n"); fprintf(f, "#if !defined(__cplusplus)\n" "#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)\n" "#include <stdbool.h>\n" "#else\n" "typedef int bool;\n" "#endif\n" "#endif\n\n"); if (g->emitInstrumentation) { fprintf(f, "#define ISPC_INSTRUMENTATION 1\n"); fprintf(f, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\nextern \"C\" " "{\n#endif // __cplusplus\n"); fprintf(f, " void ISPCInstrument(const char *fn, const char *note, int line, uint64_t mask);\n"); fprintf(f, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\n} /* end " "extern C */\n#endif // __cplusplus\n"); } // end namespace fprintf(f, "\n"); fprintf(f, "\n#ifdef __cplusplus\nnamespace ispc { /* namespace */\n#endif // __cplusplus\n"); // Collect single linear arrays of the exported and extern "C" // functions std::vector<Symbol *> exportedFuncs, externCFuncs; m->symbolTable->GetMatchingFunctions(lIsExported, &exportedFuncs); m->symbolTable->GetMatchingFunctions(lIsExternC, &externCFuncs); // Get all of the struct, vector, and enumerant types used as function // parameters. These vectors may have repeats. std::vector<const StructType *> exportedStructTypes; std::vector<const EnumType *> exportedEnumTypes; std::vector<const VectorType *> exportedVectorTypes; lGetExportedParamTypes(exportedFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); lGetExportedParamTypes(externCFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); // Go through the explicitly exported types for (int i = 0; i < (int)exportedTypes.size(); ++i) { if (const StructType *st = CastType<StructType>(exportedTypes[i].first)) { exportedStructTypes.push_back(CastType<StructType>(st->GetAsUniformType())); } else if (const EnumType *et = CastType<EnumType>(exportedTypes[i].first)) { exportedEnumTypes.push_back(CastType<EnumType>(et->GetAsUniformType())); } else if (const VectorType *vt = CastType<VectorType>(exportedTypes[i].first)) { exportedVectorTypes.push_back(CastType<VectorType>(vt->GetAsUniformType())); } else { FATAL("Unexpected type in export list"); } } // And print them lEmitVectorTypedefs(exportedVectorTypes, f); lEmitEnumDecls(exportedEnumTypes, f); lEmitStructDecls(exportedStructTypes, f); // emit function declarations for exported stuff... if (exportedFuncs.size() > 0) { fprintf(f, "\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Functions exported from ispc code\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n"); lPrintFunctionDeclarations(f, exportedFuncs); } // end namespace fprintf(f, "\n"); fprintf(f, "\n#ifdef __cplusplus\n} /* namespace */\n#endif // __cplusplus\n"); // end guard if (g->noPragmaOnce) { fprintf(f, "\n#endif // %s\n", guard.c_str()); } } bool Module::writeHeader() { FILE *f = fopen(output.header.c_str(), "w"); reportInvalidSuffixWarning(output.header, OutputType::Header); if (!f) { perror("fopen"); return false; } fprintf(f, "//\n// %s\n// (Header automatically generated by the ispc compiler.)\n", output.header.c_str()); fprintf(f, "// DO NOT EDIT THIS FILE.\n//\n\n"); writeHeader(f); fclose(f); return true; } bool Module::DispatchHeaderInfo::initialize(std::string headerFileName) { EmitUnifs = true; EmitFuncs = true; EmitFrontMatter = true; // This is toggled later. EmitBackMatter = false; Emit4 = true; Emit8 = true; Emit16 = true; header = std::move(headerFileName); fn = header.c_str(); if (!header.empty()) { file = fopen(header.c_str(), "w"); if (!file) { perror("fopen"); return false; } } return true; } void Module::DispatchHeaderInfo::closeFile() { if (file != nullptr) { fclose(file); file = nullptr; } } bool Module::writeDispatchHeader(DispatchHeaderInfo *DHI) { FILE *f = DHI->file; reportInvalidSuffixWarning(DHI->fn, OutputType::Header); if (DHI->EmitFrontMatter) { fprintf(f, "//\n// %s\n// (Header automatically generated by the ispc compiler.)\n", DHI->fn); fprintf(f, "// DO NOT EDIT THIS FILE.\n//\n\n"); } // Create a nice guard string from the filename, turning any // non-number/letter characters into underbars std::string guard = "ISPC_"; const char *p = DHI->fn; while (*p) { if (isdigit(*p)) { guard += *p; } else if (isalpha(*p)) { guard += toupper(*p); } else { guard += "_"; } ++p; } if (DHI->EmitFrontMatter) { if (g->noPragmaOnce) { fprintf(f, "#ifndef %s\n#define %s\n\n", guard.c_str(), guard.c_str()); } else { fprintf(f, "#pragma once\n"); } fprintf(f, "#include <stdint.h>\n\n"); if (g->emitInstrumentation) { fprintf(f, "#define ISPC_INSTRUMENTATION 1\n"); fprintf(f, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\nextern " "\"C\" {\n#endif // __cplusplus\n"); fprintf(f, " void ISPCInstrument(const char *fn, const char *note, int line, uint64_t mask);\n"); fprintf(f, "#if defined(__cplusplus) && (! defined(__ISPC_NO_EXTERN_C) || !__ISPC_NO_EXTERN_C )\n} /* end " "extern C */\n#endif // __cplusplus\n"); } // end namespace fprintf(f, "\n"); fprintf(f, "\n#ifdef __cplusplus\nnamespace ispc { /* namespace */\n#endif // __cplusplus\n\n"); DHI->EmitFrontMatter = false; } // Collect single linear arrays of the exported and extern "C" // functions std::vector<Symbol *> exportedFuncs, externCFuncs; m->symbolTable->GetMatchingFunctions(lIsExported, &exportedFuncs); m->symbolTable->GetMatchingFunctions(lIsExternC, &externCFuncs); int programCount = g->target->getVectorWidth(); if ((DHI->Emit4 && (programCount == 4)) || (DHI->Emit8 && (programCount == 8)) || (DHI->Emit16 && (programCount == 16))) { // Get all of the struct, vector, and enumerant types used as function // parameters. These vectors may have repeats. std::vector<const StructType *> exportedStructTypes; std::vector<const EnumType *> exportedEnumTypes; std::vector<const VectorType *> exportedVectorTypes; lGetExportedParamTypes(exportedFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); lGetExportedParamTypes(externCFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); // TODO!: Why there are two almost identical piece of code like this? // Go through the explicitly exported types for (int i = 0; i < (int)exportedTypes.size(); ++i) { if (const StructType *st = CastType<StructType>(exportedTypes[i].first)) { exportedStructTypes.push_back(CastType<StructType>(st->GetAsUniformType())); } else if (const EnumType *et = CastType<EnumType>(exportedTypes[i].first)) { exportedEnumTypes.push_back(CastType<EnumType>(et->GetAsUniformType())); } else if (const VectorType *vt = CastType<VectorType>(exportedTypes[i].first)) { exportedVectorTypes.push_back(CastType<VectorType>(vt->GetAsUniformType())); } else { FATAL("Unexpected type in export list"); } } // And print them if (DHI->EmitUnifs) { lEmitVectorTypedefs(exportedVectorTypes, f); lEmitEnumDecls(exportedEnumTypes, f); } lEmitStructDecls(exportedStructTypes, f, DHI->EmitUnifs); // Update flags DHI->EmitUnifs = false; if (programCount == 4) { DHI->Emit4 = false; } else if (programCount == 8) { DHI->Emit8 = false; } else if (programCount == 16) { DHI->Emit16 = false; } } if (DHI->EmitFuncs) { // emit function declarations for exported stuff... if (exportedFuncs.size() > 0) { fprintf(f, "\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Functions exported from ispc code\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n"); lPrintFunctionDeclarations(f, exportedFuncs, 1, true); fprintf(f, "\n"); } DHI->EmitFuncs = false; } if (DHI->EmitBackMatter) { // end namespace fprintf(f, "\n"); fprintf(f, "\n#ifdef __cplusplus\n} /* namespace */\n#endif // __cplusplus\n"); // end guard if (g->noPragmaOnce) { fprintf(f, "\n#endif // %s\n", guard.c_str()); } DHI->EmitBackMatter = false; } return true; } std::string lGetNanobindType(const Type *type) { if (type->IsAtomicType()) { return type->GetDeclaration("", DeclarationSyntax::CPP); } else if (type->IsPointerType()) { // Both TYPE a[] and TYPE *uniform are represented as pointer types at // this point, except structures. We suppose that they are passed from // Python side as numpy arrays. if (const StructType *ST = CastType<StructType>(type->GetBaseType())) { return ST->GetCStructName() + "*"; } else { return "nb::ndarray<" + lGetNanobindType(type->GetBaseType()) + ">"; } } else if (type->IsReferenceType()) { return type->GetDeclaration("", DeclarationSyntax::CPP); } else if (const StructType *ST = CastType<StructType>(type)) { return ST->GetCStructName(); } else if (const EnumType *ET = CastType<EnumType>(type)) { return ET->GetEnumName(); } else { Error(type->GetSourcePos(), "Unsupported type for nanobind wrapper"); return ""; } } std::string lGetParameters(const FunctionType *ftype) { std::string params; for (int i = 0; i < ftype->GetNumParameters(); i++) { if (i > 0) { params += ", "; } const Type *paramType = ftype->GetParameterType(i); const std::string paramName = ftype->GetParameterName(i); params += lGetNanobindType(paramType) + " " + paramName; } return params; } std::string lGetCallArguments(const FunctionType *ftype) { std::string callArgs; for (int i = 0; i < ftype->GetNumParameters(); i++) { if (i > 0) { callArgs += ", "; } const std::string paramName = ftype->GetParameterName(i); callArgs += paramName; const Type *PT = ftype->GetParameterType(i); if (PT->IsPointerType() && !CastType<StructType>(PT->GetBaseType())) { callArgs += ".data()"; } } return callArgs; } void lEmitNanobindWrapper(FILE *f, const Symbol *sym) { std::string fname = sym->name; const FunctionType *ftype = CastType<FunctionType>(sym->type); if (!ftype) { Error(sym->pos, "Symbol %s is not a function type", fname.c_str()); return; } const Type *returnType = ftype->GetReturnType(); std::string nbWrapper = lGetNanobindType(returnType) + " " + fname + "(" + lGetParameters(ftype) + ")"; std::string callArgs = lGetCallArguments(ftype); fprintf(f, "%s {\n", nbWrapper.c_str()); if (!returnType->IsVoidType()) { fprintf(f, " return "); } fprintf(f, " ispc::%s(%s);\n", fname.c_str(), callArgs.c_str()); fprintf(f, "}\n"); } void lEmitNanobindStruct(FILE *f, const StructType *stType) { std::string stName = stType->GetCStructName(); fprintf(f, " nb::class_<%s>(m, \"%s\")\n", stName.c_str(), stName.c_str()); fprintf(f, " .def(nb::init())\n"); for (int i = 0; i < stType->GetElementCount(); i++) { const Type *elemType = stType->GetElementType(i); const std::string elemName = stType->GetElementName(i); if (elemType->IsArrayType() || elemType->IsVaryingAtomic()) { // We need to bind array as numpy arrays fprintf(f, " .def_prop_rw(\"%s\",\n" " make_array_getter(&%s::%s),\n" " make_array_setter(&%s::%s))\n", elemName.c_str(), stName.c_str(), elemName.c_str(), stName.c_str(), elemName.c_str()); } else { fprintf(f, " .def_rw(\"%s\", &%s::%s)\n", elemName.c_str(), stName.c_str(), elemName.c_str()); } } fprintf(f, " ;\n"); } void lEmitNanobindEnum(FILE *f, const EnumType *etType) { std::string etName = etType->GetEnumName(); fprintf(f, " nb::enum_<%s>(m, \"%s\")\n", etName.c_str(), etName.c_str()); for (int i = 0; i < etType->GetEnumeratorCount(); i++) { const std::string val = etType->GetEnumerator(i)->name; fprintf(f, " .value(\"%s\", %s::%s)\n", val.c_str(), etName.c_str(), val.c_str()); } fprintf(f, " ;\n"); } void lEmitArrayGettersSetters(FILE *f) { fprintf( f, "#include <algorithm>\n" "#include <array>\n" "#include <numeric>\n" "// Type deduction helpers - use recursive template specialization\n" "template<typename T>\n" "struct array_traits;\n" "\n" "// Base case for 1D arrays\n" "template<typename ElementType, size_t Dim>\n" "struct array_traits<ElementType[Dim]> {\n" " using element_type = ElementType;\n" " static constexpr size_t ndim = 1;\n" " static constexpr std::array<size_t, 1> dimensions = {Dim};\n" " static constexpr size_t total_size = Dim;\n" "\n" " static auto make_shape_tuple() {\n" " return nb::make_tuple(Dim);\n" " }\n" "};\n" "\n" "// Specialization for 2D arrays\n" "template<typename ElementType, size_t Dim1, size_t Dim2>\n" "struct array_traits<ElementType[Dim1][Dim2]> {\n" " using element_type = ElementType;\n" " static constexpr size_t ndim = 2;\n" " static constexpr std::array<size_t, 2> dimensions = {Dim1, Dim2};\n" " static constexpr size_t total_size = Dim1 * Dim2;\n" "\n" " static auto make_shape_tuple() {\n" " return nb::make_tuple(Dim1, Dim2);\n" " }\n" "};\n" "\n" "// Specialization for 3D arrays (if needed)\n" "template<typename ElementType, size_t Dim1, size_t Dim2, size_t Dim3>\n" "struct array_traits<ElementType[Dim1][Dim2][Dim3]> {\n" " using element_type = ElementType;\n" " static constexpr size_t ndim = 3;\n" " static constexpr std::array<size_t, 3> dimensions = {Dim1, Dim2, Dim3};\n" " static constexpr size_t total_size = Dim1 * Dim2 * Dim3;\n" "\n" " static auto make_shape_tuple() {\n" " return nb::make_tuple(Dim1, Dim2, Dim3);\n" " }\n" "};\n" "\n" "// Helper function to get numpy dtype string from C++ type\n" "template<typename T>\n" "constexpr const char* get_numpy_dtype() {\n" " if constexpr (std::is_same_v<T, int8_t>) {\n" " return \"int8\";\n" " } else if constexpr (std::is_same_v<T, uint8_t>) {\n" " return \"uint8\";\n" " } else if constexpr (std::is_same_v<T, int16_t>) {\n" " return \"int16\";\n" " } else if constexpr (std::is_same_v<T, uint16_t>) {\n" " return \"uint16\";\n" " } else if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, int>) {\n" " return \"int32\";\n" " } else if constexpr (std::is_same_v<T, uint32_t>) {\n" " return \"uint32\";\n" " } else if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, long long>) {\n" " return \"int64\";\n" " } else if constexpr (std::is_same_v<T, uint64_t>) {\n" " return \"uint64\";\n" " } else if constexpr (std::is_same_v<T, float>) {\n" " return \"float32\";\n" " } else if constexpr (std::is_same_v<T, double>) {\n" " return \"float64\";\n" " } else if constexpr (std::is_same_v<T, long double>) {\n" " return \"longdouble\";\n" " } else {\n" " static_assert(sizeof(T) == 0, \"Unsupported type for numpy dtype conversion\");\n" " }\n" "}\n" "\n" "// Simple copy-based approach\n" "template<typename ClassType, typename ArrayType>\n" "auto make_array_getter(ArrayType ClassType::*member_ptr) {\n" " using traits = array_traits<ArrayType>;\n" " using element_type = typename traits::element_type;\n" "\n" " return [member_ptr](const ClassType& self) -> nb::object {\n" " const auto& arr = self.*member_ptr;\n" "\n" " // Import numpy\n" " nb::module_ np = nb::module_::import_(\"numpy\");\n" "\n" " // Get the appropriate dtype string\n" " const char* dtype_str = get_numpy_dtype<element_type>();\n" "\n" " // Create a Python list structure based on dimensions\n" " if constexpr (traits::ndim == 1) {\n" " nb::list list;\n" " for (size_t i = 0; i < traits::dimensions[0]; ++i) {\n" " list.append(arr[i]);\n" " }\n" " return np.attr(\"array\")(list, nb::arg(\"dtype\") = dtype_str);\n" " } else if constexpr (traits::ndim == 2) {\n" " nb::list outer_list;\n" " for (size_t i = 0; i < traits::dimensions[0]; ++i) {\n" " nb::list inner_list;\n" " for (size_t j = 0; j < traits::dimensions[1]; ++j) {\n" " inner_list.append(arr[i][j]);\n" " }\n" " outer_list.append(inner_list);\n" " }\n" " return np.attr(\"array\")(outer_list, nb::arg(\"dtype\") = dtype_str);\n" " } else if constexpr (traits::ndim == 3) {\n" " nb::list outer_list;\n" " for (size_t i = 0; i < traits::dimensions[0]; ++i) {\n" " nb::list middle_list;\n" " for (size_t j = 0; j < traits::dimensions[1]; ++j) {\n" " nb::list inner_list;\n" " for (size_t k = 0; k < traits::dimensions[2]; ++k) {\n" " inner_list.append(arr[i][j][k]);\n" " }\n" " middle_list.append(inner_list);\n" " }\n" " outer_list.append(middle_list);\n" " }\n" " return np.attr(\"array\")(outer_list, nb::arg(\"dtype\") = dtype_str);\n" " } else {\n" " throw std::runtime_error(\"Unsupported array dimension for getter\");\n" " }\n" " };\n" "}\n" "\n" "template<typename ClassType, typename ArrayType>\n" "auto make_array_setter(ArrayType ClassType::*member_ptr) {\n" " using traits = array_traits<ArrayType>;\n" " using element_type = typename traits::element_type;\n" "\n" " return [member_ptr](ClassType& self, nb::ndarray<nb::numpy, const element_type> arr) {\n" " // Check dimensions\n" " if (arr.ndim() != traits::ndim) {\n" " throw std::runtime_error(\"Array must have \" + std::to_string(traits::ndim) + \" dimensions\");\n" " }\n" "\n" " // Check each dimension size\n" " for (size_t i = 0; i < traits::ndim; ++i) {\n" " if (arr.shape(i) != traits::dimensions[i]) {\n" " throw std::runtime_error(\"Dimension \" + std::to_string(i) +\n" " \" must have size \" + std::to_string(traits::dimensions[i]) +\n" " \", got \" + std::to_string(arr.shape(i)));\n" " }\n" " }\n" "\n" " // Copy data - handle different dimensions\n" " auto& target_arr = self.*member_ptr;\n" " if constexpr (traits::ndim == 1) {\n" " std::copy(arr.data(), arr.data() + traits::total_size, &target_arr[0]);\n" " } else if constexpr (traits::ndim == 2) {\n" " std::copy(arr.data(), arr.data() + traits::total_size, &target_arr[0][0]);\n" " } else if constexpr (traits::ndim == 3) {\n" " std::copy(arr.data(), arr.data() + traits::total_size, &target_arr[0][0][0]);\n" " } else {\n" " throw std::runtime_error(\"Unsupported array dimension for setter\");\n" " }\n" " };\n" "}\n"); } bool lStructsWithArrays(const std::vector<const StructType *> &exportedStructTypes) { for (const auto &stType : exportedStructTypes) { for (int i = 0; i < stType->GetElementCount(); i++) { const Type *elemType = stType->GetElementType(i); if (elemType->IsArrayType() || elemType->IsVaryingAtomic()) { return true; } } } return false; } bool Module::writeNanobindWrapper() { FILE *f = fopen(output.nbWrap.c_str(), "w"); if (!f) { perror("fopen"); return false; } reportInvalidSuffixWarning(output.nbWrap, OutputType::NanobindWrapper); // remove pathname and extension from filename std::string filename = llvm::sys::path::stem(output.nbWrap).str(); fprintf(f, "//\n// %s\n// (Nanobind wrapper automatically generated by the ispc compiler.)\n" "// DO NOT EDIT THIS FILE.\n//\n\n", filename.c_str()); // Emit ISPC header to generate self-contained wrapper cpp file that // doesn't depend on any other ISPC byproducts. fprintf(f, "\n/////////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// ISPC header\n"); fprintf(f, "/////////////////////////////////////////////////////////////////////////////\n\n"); // We need to emit the header file with pragma once disabled, so that it // won't raise compile warnings of #pragma once is being used in main file. // Just temporarily disable it, and then restore it. int oldPragmaOnce = g->noPragmaOnce; g->noPragmaOnce = true; writeHeader(f); g->noPragmaOnce = oldPragmaOnce; fprintf(f, "\n/////////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Nanobind wrapper body\n"); fprintf(f, "/////////////////////////////////////////////////////////////////////////////\n\n"); fprintf(f, "\n#include <nanobind/nanobind.h>\n" "#include <nanobind/ndarray.h>\n\n"); fprintf(f, "namespace nb = nanobind;\n\n"); std::vector<Symbol *> exportedFuncs; m->symbolTable->GetMatchingFunctions(lIsExported, &exportedFuncs); std::vector<const StructType *> exportedStructTypes; std::vector<const EnumType *> exportedEnumTypes; std::vector<const VectorType *> exportedVectorTypes; lGetExportedParamTypes(exportedFuncs, &exportedStructTypes, &exportedEnumTypes, &exportedVectorTypes); if (lStructsWithArrays(exportedStructTypes)) { lEmitArrayGettersSetters(f); } // We do this to avoid emitting ispc:: every time we need to use struct/enum types. fprintf(f, "\n////////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Bring exported ISPC structs and enums into the current scope\n"); fprintf(f, "////////////////////////////////////////////////////////////////////////////\n\n"); for (const auto &stType : exportedStructTypes) { fprintf(f, "using ispc::%s;\n", stType->GetCStructName().c_str()); } for (const auto &etType : exportedEnumTypes) { fprintf(f, "using ispc::%s;\n", etType->GetEnumName().c_str()); } for (const auto &vType : exportedVectorTypes) { Error(vType->GetSourcePos(), "Nanobind wrapper generation does not support vector types.\n"); } if (exportedFuncs.size() > 0) { fprintf(f, "\n///////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Wrappers for functions exported from ispc code\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n\n"); } for (const auto &func : exportedFuncs) { lEmitNanobindWrapper(f, func); fprintf(f, "\n"); } fprintf(f, "\n///////////////////////////////////////////////////////////////////////////\n"); fprintf(f, "// Python module definition\n"); fprintf(f, "///////////////////////////////////////////////////////////////////////////\n\n"); fprintf(f, "NB_MODULE(%s, m) {\n", filename.c_str()); for (const auto &stType : exportedStructTypes) { lEmitNanobindStruct(f, stType); fprintf(f, "\n"); } for (const auto &etType : exportedEnumTypes) { lEmitNanobindEnum(f, etType); fprintf(f, "\n"); } fprintf(f, "\n"); fprintf(f, " // Register wrappers\n"); for (const auto &func : exportedFuncs) { std::string fname = func->name; fprintf(f, " m.def(\"%s\", &%s);\n", fname.c_str(), fname.c_str()); } fprintf(f, "}\n"); fclose(f); return true; }