* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#ifndef GE_COMPILER_GRAPH_FUSION_PASS_PYTHON_PASS_FALLBACK_CODEGEN_HELPER_H_
#define GE_COMPILER_GRAPH_FUSION_PASS_PYTHON_PASS_FALLBACK_CODEGEN_HELPER_H_
#include <dlfcn.h>
#include <string>
#include "common/python_runtime/python_artifact_utils.h"
#include "framework/common/debug/ge_log.h"
namespace ge {
namespace fusion {
namespace python_pass_fallback_codegen {
using ProbePythonRuntimeFn = bool (*)(const char *python_command, ::ge::python_artifact::PythonRuntimeKey &runtime_key);
constexpr const char *kPyGILStateEnsureSymbol = "PyGILState_Ensure";
constexpr const char *kPyGILStateReleaseSymbol = "PyGILState_Release";
constexpr const char *kPyImportAddModuleSymbol = "PyImport_AddModule";
constexpr const char *kPyModuleGetDictSymbol = "PyModule_GetDict";
constexpr const char *kPyRunStringSymbol = "PyRun_String";
constexpr const char *kPyUnicodeAsUTF8Symbol = "PyUnicode_AsUTF8";
constexpr const char *kPyDecRefSymbol = "Py_DecRef";
constexpr const char *kPyErrOccurredSymbol = "PyErr_Occurred";
constexpr const char *kPyErrFetchSymbol = "PyErr_Fetch";
constexpr const char *kPyObjectStrSymbol = "PyObject_Str";
constexpr int kPyEvalInput = 258;
constexpr const char *kSubProcessFallbackRootPrefix = "__GE_PYTHON_PASS_FALLBACK_ROOT__=";
constexpr const char *kSubProcessFallbackScript =
" -c \"import sys, traceback\n"
"try:\n"
" from ge.passes.runtime import run_fallback_codegen\n"
" print('__GE_PYTHON_PASS_FALLBACK_ROOT__=' + str(run_fallback_codegen().root))\n"
"except Exception:\n"
" traceback.print_exc(file=sys.stdout)\n"
" sys.exit(1)\" 2>/dev/null";
constexpr const char *kInProcessFallbackExpression =
"str(__import__('ge.passes.runtime', fromlist=['run_fallback_codegen']).run_fallback_codegen().root)";
struct InProcessPythonApi {
using PyObjectPtr = void *;
using PyGILStateEnsureFn = int (*)();
using PyGILStateReleaseFn = void (*)(int);
using PyImportAddModuleFn = PyObjectPtr (*)(const char *);
using PyModuleGetDictFn = PyObjectPtr (*)(PyObjectPtr);
using PyRunStringFn = PyObjectPtr (*)(const char *, int, PyObjectPtr, PyObjectPtr);
using PyUnicodeAsUTF8Fn = const char *(*)(PyObjectPtr);
using PyDecRefFn = void (*)(PyObjectPtr);
using PyErrOccurredFn = int (*)();
using PyErrFetchFn = void (*)(PyObjectPtr *, PyObjectPtr *, PyObjectPtr *);
using PyObjectStrFn = PyObjectPtr (*)(PyObjectPtr);
PyGILStateEnsureFn gil_ensure{nullptr};
PyGILStateReleaseFn gil_release{nullptr};
PyImportAddModuleFn import_add_module{nullptr};
PyModuleGetDictFn module_get_dict{nullptr};
PyRunStringFn run_string{nullptr};
PyUnicodeAsUTF8Fn unicode_as_utf8{nullptr};
PyDecRefFn dec_ref{nullptr};
PyErrOccurredFn err_occurred{nullptr};
PyErrFetchFn err_fetch{nullptr};
PyObjectStrFn object_str{nullptr};
bool Resolve() {
gil_ensure = reinterpret_cast<PyGILStateEnsureFn>(dlsym(RTLD_DEFAULT, kPyGILStateEnsureSymbol));
gil_release = reinterpret_cast<PyGILStateReleaseFn>(dlsym(RTLD_DEFAULT, kPyGILStateReleaseSymbol));
import_add_module = reinterpret_cast<PyImportAddModuleFn>(dlsym(RTLD_DEFAULT, kPyImportAddModuleSymbol));
module_get_dict = reinterpret_cast<PyModuleGetDictFn>(dlsym(RTLD_DEFAULT, kPyModuleGetDictSymbol));
run_string = reinterpret_cast<PyRunStringFn>(dlsym(RTLD_DEFAULT, kPyRunStringSymbol));
unicode_as_utf8 = reinterpret_cast<PyUnicodeAsUTF8Fn>(dlsym(RTLD_DEFAULT, kPyUnicodeAsUTF8Symbol));
dec_ref = reinterpret_cast<PyDecRefFn>(dlsym(RTLD_DEFAULT, kPyDecRefSymbol));
err_occurred = reinterpret_cast<PyErrOccurredFn>(dlsym(RTLD_DEFAULT, kPyErrOccurredSymbol));
err_fetch = reinterpret_cast<PyErrFetchFn>(dlsym(RTLD_DEFAULT, kPyErrFetchSymbol));
object_str = reinterpret_cast<PyObjectStrFn>(dlsym(RTLD_DEFAULT, kPyObjectStrSymbol));
return (gil_ensure != nullptr) && (gil_release != nullptr) && (import_add_module != nullptr) &&
(module_get_dict != nullptr) && (run_string != nullptr) && (unicode_as_utf8 != nullptr) &&
(dec_ref != nullptr) && (err_occurred != nullptr) && (err_fetch != nullptr) && (object_str != nullptr);
}
std::string FormatActivePythonError() const {
if ((err_occurred == nullptr) || (err_occurred() == 0)) {
return "";
}
PyObjectPtr type = nullptr;
PyObjectPtr value = nullptr;
PyObjectPtr traceback = nullptr;
err_fetch(&type, &value, &traceback);
std::string message;
if (value != nullptr) {
PyObjectPtr value_str = object_str(value);
if (value_str != nullptr) {
const char *utf8 = unicode_as_utf8(value_str);
if ((utf8 != nullptr) && (utf8[0] != '\0')) {
message = utf8;
}
dec_ref(value_str);
}
dec_ref(value);
}
if (type != nullptr) {
dec_ref(type);
}
if (traceback != nullptr) {
dec_ref(traceback);
}
return message;
}
};
struct PyGilGuard {
explicit PyGilGuard(InProcessPythonApi &api) : api_(api), state_(api.gil_ensure()) {}
~PyGilGuard() {
api_.gil_release(state_);
}
PyGilGuard(const PyGilGuard &) = delete;
PyGilGuard &operator=(const PyGilGuard &) = delete;
InProcessPythonApi &api_;
int state_;
};
struct FallbackCodegenDependencies {
using ReadCommandOutputFn = bool (*)(const std::string &command, std::string &output);
ReadCommandOutputFn read_command_output{nullptr};
ProbePythonRuntimeFn probe_runtime{nullptr};
};
inline std::string FetchLineByPrefix(const std::string &content, const std::string &prefix) {
if (prefix.empty()) {
return "";
}
size_t line_start = 0U;
while (line_start <= content.size()) {
const auto line_end = content.find('\n', line_start);
size_t value_end = (line_end == std::string::npos) ? content.size() : line_end;
if ((value_end >= (line_start + prefix.size())) && (content.compare(line_start, prefix.size(), prefix) == 0)) {
if ((value_end > (line_start + prefix.size())) && (content[value_end - 1U] == '\r')) {
--value_end;
}
return content.substr(line_start + prefix.size(), value_end - line_start - prefix.size());
}
if (line_end == std::string::npos) {
break;
}
line_start = line_end + 1U;
}
return "";
}
inline std::string ResolveCompatiblePythonCommand(const ::ge::python_artifact::PythonRuntimeKey &expected_key,
ProbePythonRuntimeFn probe) {
if (probe == nullptr) {
return "";
}
for (const char *candidate : {"python3", "python"}) {
::ge::python_artifact::PythonRuntimeKey probed_key;
if (!probe(candidate, probed_key)) {
continue;
}
if (!::ge::python_artifact::IsRuntimeKeyCompatible(expected_key, probed_key)) {
continue;
}
return probed_key.python_command;
}
return "";
}
inline bool IsFallbackCodegenDependenciesValid(const FallbackCodegenDependencies &deps) {
return (deps.read_command_output != nullptr) && (deps.probe_runtime != nullptr);
}
inline bool RunEvalExpressionInProcess(const char *expression, std::string &result_utf8) {
result_utf8.clear();
InProcessPythonApi py_api;
if (!py_api.Resolve()) {
GELOGE(FAILED, "In-process python pass fallback codegen failed, required libpython symbols are not resolvable.");
return false;
}
const PyGilGuard gil_guard(py_api);
InProcessPythonApi::PyObjectPtr main_module = py_api.import_add_module("__main__");
InProcessPythonApi::PyObjectPtr globals = (main_module != nullptr) ? py_api.module_get_dict(main_module) : nullptr;
InProcessPythonApi::PyObjectPtr result =
(globals != nullptr) ? py_api.run_string(expression, kPyEvalInput, globals, globals) : nullptr;
if (result == nullptr) {
const auto error_message = py_api.FormatActivePythonError();
if (error_message.empty()) {
GELOGE(FAILED, "In-process python pass fallback codegen failed.");
} else {
GELOGE(FAILED, "In-process python pass fallback codegen failed: %s", error_message.c_str());
}
return false;
}
const char *utf8 = py_api.unicode_as_utf8(result);
if ((utf8 == nullptr) || (utf8[0] == '\0')) {
GELOGE(FAILED, "In-process python pass fallback codegen failed: result is empty.");
py_api.dec_ref(result);
return false;
}
result_utf8 = utf8;
py_api.dec_ref(result);
return true;
}
inline bool RunFallbackCodegenViaSubprocess(const ::ge::python_artifact::PythonRuntimeKey &runtime_key,
const FallbackCodegenDependencies &deps, std::string &gen_artifact_root) {
if ((deps.read_command_output == nullptr) || (deps.probe_runtime == nullptr)) {
return false;
}
std::string python_command = runtime_key.python_command;
if (python_command.empty()) {
python_command = ResolveCompatiblePythonCommand(runtime_key, deps.probe_runtime);
}
if (python_command.empty()) {
GELOGE(FAILED, "Python pass fallback codegen failed, no python command for runtime key[%s].",
runtime_key.ToString().c_str());
return false;
}
std::string output;
if (!deps.read_command_output(std::string(python_command) + kSubProcessFallbackScript, output)) {
GELOGE(FAILED, "Subprocess python pass fallback codegen failed, command[%s], output[%s].", python_command.c_str(),
output.c_str());
return false;
}
gen_artifact_root = FetchLineByPrefix(output, kSubProcessFallbackRootPrefix);
if (gen_artifact_root.empty()) {
GELOGE(FAILED,
"Subprocess python pass fallback codegen failed, command[%s], "
"missing artifact root marker in output[%s].",
python_command.c_str(), output.c_str());
return false;
}
GELOGI("Subprocess python pass fallback codegen success, gen artifact root[%s].", gen_artifact_root.c_str());
return true;
}
inline bool RunFallbackCodegenInProcess(std::string &gen_artifact_root) {
if (!RunEvalExpressionInProcess(kInProcessFallbackExpression, gen_artifact_root)) {
return false;
}
GELOGI("In-process python pass fallback codegen success, gen artifact root[%s].", gen_artifact_root.c_str());
return true;
}
inline bool RunFallbackCodegen(const ::ge::python_artifact::PythonRuntimeKey &runtime_key,
const FallbackCodegenDependencies &deps, std::string &gen_artifact_root) {
gen_artifact_root.clear();
if (runtime_key.python_tag.empty() || !IsFallbackCodegenDependenciesValid(deps)) {
return false;
}
if (runtime_key.has_python_symbols && runtime_key.is_initialized) {
return RunFallbackCodegenInProcess(gen_artifact_root);
}
return RunFallbackCodegenViaSubprocess(runtime_key, deps, gen_artifact_root);
}
}
}
}
#endif