提交 6ae0085b 编写于 作者: S Shreedhar Hardikar

Move ElogWrapper to GpCodegenUtils.

* Derive GpCodegenUtils from CodegenUtils for GPDB specific utilities.
* Move ElogWrapper functions to GpCodegenUtils
* Implement GetOrRegisterExternalFunction
上级 048b492f
......@@ -128,6 +128,7 @@ include_directories(${CLANG_INCLUDE_DIRS})
add_library(gpcodegen SHARED
utils/clang_compiler.cc
utils/codegen_utils.cc
utils/gp_codegen_utils.cc
${codegen_tmpfile_sources})
if(APPLE)
set(WL_START_GROUP "")
......
......@@ -20,7 +20,7 @@ extern "C" {
#include "codegen/utils/clang_compiler.h"
#include "codegen/utils/utility.h"
#include "codegen/utils/instance_method_wrappers.h"
#include "codegen/utils/codegen_utils.h"
#include "codegen/utils/gp_codegen_utils.h"
#include "codegen/codegen_interface.h"
#include "codegen/codegen_manager.h"
......@@ -44,7 +44,7 @@ using gpcodegen::CodegenManager;
CodegenManager::CodegenManager(const std::string& module_name) {
module_name_ = module_name;
codegen_utils_.reset(new gpcodegen::CodegenUtils(module_name));
codegen_utils_.reset(new gpcodegen::GpCodegenUtils(module_name));
}
bool CodegenManager::EnrollCodeGenerator(
......@@ -74,9 +74,9 @@ unsigned int CodegenManager::PrepareGeneratedFunctions() {
}
// Call CodegenUtils to compile entire module
// Call GpCodegenUtils to compile entire module
bool compilation_status = codegen_utils_->PrepareForExecution(
gpcodegen::CodegenUtils::OptimizationLevel::kDefault, true);
gpcodegen::GpCodegenUtils::OptimizationLevel::kDefault, true);
if (!compilation_status) {
return success_count;
......@@ -84,7 +84,7 @@ unsigned int CodegenManager::PrepareGeneratedFunctions() {
// On successful compilation, go through all generator and swap
// the pointer so compiled function get called
gpcodegen::CodegenUtils* codegen_utils = codegen_utils_.get();
gpcodegen::GpCodegenUtils* codegen_utils = codegen_utils_.get();
for (std::unique_ptr<CodegenInterface>& generator :
enrolled_code_generators_) {
success_count += generator->SetToGenerated(codegen_utils);
......
......@@ -15,7 +15,7 @@
#include "codegen/exec_variable_list_codegen.h"
#include "codegen/exec_qual_codegen.h"
#include "codegen/utils/codegen_utils.h"
#include "codegen/utils/gp_codegen_utils.h"
using gpcodegen::CodegenManager;
using gpcodegen::BaseCodegen;
......@@ -32,7 +32,7 @@ extern bool init_codegen; // defined from guc
// Perform global set-up tasks for code generation. Returns 0 on
// success, nonzero on error.
unsigned int InitCodegen() {
return gpcodegen::CodegenUtils::InitializeGlobal();
return gpcodegen::GpCodegenUtils::InitializeGlobal();
}
void* CodeGeneratorManagerCreate(const char* module_name) {
......
......@@ -17,7 +17,7 @@
#include "codegen/utils/clang_compiler.h"
#include "codegen/utils/utility.h"
#include "codegen/utils/instance_method_wrappers.h"
#include "codegen/utils/codegen_utils.h"
#include "codegen/utils/gp_codegen_utils.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
......@@ -44,68 +44,6 @@ using gpcodegen::ExecQualCodegen;
constexpr char ExecQualCodegen::kExecQualPrefix[];
class ElogWrapper {
public:
ElogWrapper(gpcodegen::CodegenUtils* codegen_utils) :
codegen_utils_(codegen_utils) {
SetupElog();
}
~ElogWrapper() {
TearDownElog();
}
template<typename... V>
void CreateElog(
llvm::Value* llvm_elevel,
llvm::Value* llvm_fmt,
V ... args ) {
assert(NULL != llvm_elevel);
assert(NULL != llvm_fmt);
codegen_utils_->ir_builder()->CreateCall(
llvm_elog_start_, {
codegen_utils_->GetConstant(""), // Filename
codegen_utils_->GetConstant(0), // line number
codegen_utils_->GetConstant("") // function name
});
codegen_utils_->ir_builder()->CreateCall(
llvm_elog_finish_, {
llvm_elevel,
llvm_fmt,
args...
});
}
template<typename... V>
void CreateElog(
int elevel,
const char* fmt,
V ... args ) {
CreateElog(codegen_utils_->GetConstant(elevel),
codegen_utils_->GetConstant(fmt),
args...);
}
private:
llvm::Function* llvm_elog_start_;
llvm::Function* llvm_elog_finish_;
gpcodegen::CodegenUtils* codegen_utils_;
void SetupElog(){
assert(codegen_utils_ != nullptr);
llvm_elog_start_ = codegen_utils_->RegisterExternalFunction(elog_start);
assert(llvm_elog_start_ != nullptr);
llvm_elog_finish_ = codegen_utils_->RegisterExternalFunction(elog_finish);
assert(llvm_elog_finish_ != nullptr);
}
void TearDownElog(){
llvm_elog_start_ = nullptr;
llvm_elog_finish_ = nullptr;
}
};
ExecQualCodegen::ExecQualCodegen
(
ExecQualFn regular_func_ptr,
......@@ -117,12 +55,10 @@ ExecQualCodegen::ExecQualCodegen
bool ExecQualCodegen::GenerateExecQual(
gpcodegen::CodegenUtils* codegen_utils) {
gpcodegen::GpCodegenUtils* codegen_utils) {
assert(NULL != codegen_utils);
ElogWrapper elogwrapper(codegen_utils);
llvm::Function* exec_qual_func = codegen_utils->
CreateFunction<ExecQualFn>(
GetUniqueFuncName());
......@@ -135,7 +71,7 @@ bool ExecQualCodegen::GenerateExecQual(
irb->SetInsertPoint(entry_block);
elogwrapper.CreateElog(DEBUG1, "Falling back to regular ExecQual.");
codegen_utils->CreateElog(DEBUG1, "Falling back to regular ExecQual.");
codegen_utils->CreateFallback<ExecQualFn>(
codegen_utils->RegisterExternalFunction(GetRegularFuncPointer()),
......@@ -144,7 +80,7 @@ bool ExecQualCodegen::GenerateExecQual(
}
bool ExecQualCodegen::GenerateCodeInternal(CodegenUtils* codegen_utils) {
bool ExecQualCodegen::GenerateCodeInternal(GpCodegenUtils* codegen_utils) {
bool isGenerated = GenerateExecQual(codegen_utils);
if (isGenerated) {
......
......@@ -18,7 +18,7 @@
#include "codegen/utils/clang_compiler.h"
#include "codegen/utils/utility.h"
#include "codegen/utils/instance_method_wrappers.h"
#include "codegen/utils/codegen_utils.h"
#include "codegen/utils/gp_codegen_utils.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
......@@ -48,67 +48,6 @@ using gpcodegen::ExecVariableListCodegen;
constexpr char ExecVariableListCodegen::kExecVariableListPrefix[];
class ElogWrapper {
public:
explicit ElogWrapper(gpcodegen::CodegenUtils* codegen_utils) :
codegen_utils_(codegen_utils) {
SetupElog();
}
~ElogWrapper() {
TearDownElog();
}
template<typename... V>
void CreateElog(
llvm::Value* llvm_elevel,
llvm::Value* llvm_fmt,
V ... args ) {
assert(NULL != llvm_elevel);
assert(NULL != llvm_fmt);
codegen_utils_->ir_builder()->CreateCall(
llvm_elog_start_, {
codegen_utils_->GetConstant(""), // Filename
codegen_utils_->GetConstant(0), // line number
codegen_utils_->GetConstant("") // function name
});
codegen_utils_->ir_builder()->CreateCall(
llvm_elog_finish_, {
llvm_elevel,
llvm_fmt,
args...
});
}
template<typename... V>
void CreateElog(
int elevel,
const char* fmt,
V ... args ) {
CreateElog(codegen_utils_->GetConstant(elevel),
codegen_utils_->GetConstant(fmt),
args...);
}
private:
llvm::Function* llvm_elog_start_;
llvm::Function* llvm_elog_finish_;
gpcodegen::CodegenUtils* codegen_utils_;
void SetupElog() {
assert(codegen_utils_ != nullptr);
llvm_elog_start_ = codegen_utils_->RegisterExternalFunction(elog_start);
assert(llvm_elog_start_ != nullptr);
llvm_elog_finish_ = codegen_utils_->RegisterExternalFunction(elog_finish);
assert(llvm_elog_finish_ != nullptr);
}
void TearDownElog(){
llvm_elog_start_ = nullptr;
llvm_elog_finish_ = nullptr;
}
};
ExecVariableListCodegen::ExecVariableListCodegen
(
ExecVariableListFn regular_func_ptr,
......@@ -124,11 +63,10 @@ ExecVariableListCodegen::ExecVariableListCodegen
bool ExecVariableListCodegen::GenerateExecVariableList(
gpcodegen::CodegenUtils* codegen_utils) {
gpcodegen::GpCodegenUtils* codegen_utils) {
assert(NULL != codegen_utils);
ElogWrapper elogwrapper(codegen_utils);
static_assert(sizeof(Datum) == sizeof(int64),
"sizeof(Datum) doesn't match sizeof(int64)");
......@@ -643,7 +581,7 @@ bool ExecVariableListCodegen::GenerateExecVariableList(
llvm_error->addIncoming(codegen_utils->GetConstant(2),
heap_tuple_check_block);
elogwrapper.CreateElog(
codegen_utils->CreateElog(
DEBUG1,
"Falling back to regular ExecVariableList, reason = %d",
llvm_error);
......@@ -656,7 +594,7 @@ bool ExecVariableListCodegen::GenerateExecVariableList(
bool ExecVariableListCodegen::GenerateCodeInternal(
CodegenUtils* codegen_utils) {
GpCodegenUtils* codegen_utils) {
bool isGenerated = GenerateExecVariableList(codegen_utils);
if (isGenerated) {
......
......@@ -18,7 +18,7 @@ extern "C" {
#include <string>
#include <vector>
#include "codegen/utils/codegen_utils.h"
#include "codegen/utils/gp_codegen_utils.h"
#include "codegen/codegen_interface.h"
#include "llvm/IR/Function.h"
......@@ -50,7 +50,7 @@ class BaseCodegen: public CodegenInterface {
SetToRegular(regular_func_ptr_, ptr_to_chosen_func_ptr_);
}
bool GenerateCode(gpcodegen::CodegenUtils* codegen_utils) final {
bool GenerateCode(gpcodegen::GpCodegenUtils* codegen_utils) final {
bool valid_generated_functions = true;
valid_generated_functions &= GenerateCodeInternal(codegen_utils);
......@@ -87,7 +87,7 @@ class BaseCodegen: public CodegenInterface {
return true;
}
bool SetToGenerated(gpcodegen::CodegenUtils* codegen_utils) final {
bool SetToGenerated(gpcodegen::GpCodegenUtils* codegen_utils) final {
if (false == IsGenerated()) {
assert(*ptr_to_chosen_func_ptr_ == regular_func_ptr_);
return false;
......@@ -180,7 +180,7 @@ class BaseCodegen: public CodegenInterface {
* @param codegen_utils Utility to ease the code generation process.
* @return true on successful generation.
**/
virtual bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) = 0;
virtual bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) = 0;
/**
* @brief Create llvm Function for given type and store the function pointer
......@@ -196,7 +196,7 @@ class BaseCodegen: public CodegenInterface {
* @return llvm::Function pointer
**/
template <typename FunctionType>
llvm::Function* CreateFunction(gpcodegen::CodegenUtils* codegen_utils,
llvm::Function* CreateFunction(gpcodegen::GpCodegenUtils* codegen_utils,
const std::string& function_name) {
assert(nullptr != codegen_utils);
llvm::Function* function = codegen_utils->CreateFunction<FunctionType>(
......
......@@ -23,7 +23,7 @@ namespace gpcodegen {
*/
// Forward declaration
class CodegenUtils;
class GpCodegenUtils;
/**
* @brief Interface for all code generators.
......@@ -39,7 +39,7 @@ class CodegenInterface {
* @param codegen_utils Utility to ease the code generation process.
* @return true on successful generation.
**/
virtual bool GenerateCode(gpcodegen::CodegenUtils* codegen_utils) = 0;
virtual bool GenerateCode(gpcodegen::GpCodegenUtils* codegen_utils) = 0;
/**
* @brief Sets up the caller to use the corresponding regular version of the
......@@ -58,7 +58,7 @@ class CodegenInterface {
* the compiled module.
* @return true on successfully setting to generated functions
**/
virtual bool SetToGenerated(gpcodegen::CodegenUtils* codegen_utils) = 0;
virtual bool SetToGenerated(gpcodegen::GpCodegenUtils* codegen_utils) = 0;
/**
* @brief Resets the state of the generator, including reverting back to
......
......@@ -6,7 +6,7 @@
// codegen_manager.h
//
// @doc:
// Object that manage all CodegenInterface and CodegenUtils
// Object that manage all CodegenInterface and GpCodegenUtils
//
//---------------------------------------------------------------------------
......@@ -25,8 +25,8 @@ namespace gpcodegen {
* @{
*/
// Forward declaration of CodegenUtils to manage llvm module
class CodegenUtils;
// Forward declaration of GpCodegenUtils to manage llvm module
class GpCodegenUtils;
// Forward declaration of a CodegenInterface that will be managed by manager
class CodegenInterface;
......@@ -52,7 +52,7 @@ class CodegenManager {
* @note Manager manages the memory of enrolled generator.
*
* @param funcLifespan Life span of the enrolling generator. Based on life span,
* corresponding CodegenUtils will be used for code generation
* corresponding GpCodegenUtils will be used for code generation
* @param generator Generator that needs to be enrolled with manager.
* @return true on successful enrollment.
**/
......@@ -100,8 +100,8 @@ class CodegenManager {
}
private:
// CodegenUtils provides a facade to LLVM subsystem.
std::unique_ptr<gpcodegen::CodegenUtils> codegen_utils_;
// GpCodegenUtils provides a facade to LLVM subsystem.
std::unique_ptr<gpcodegen::GpCodegenUtils> codegen_utils_;
std::string module_name_;
......
......@@ -51,7 +51,7 @@ class ExecQualCodegen: public BaseCodegen<ExecQualFn> {
*
* @note Currently, it simply falls back to regular ExecQual.
*/
bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) final;
bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) final;
private:
PlanState *planstate_;
......@@ -65,7 +65,7 @@ class ExecQualCodegen: public BaseCodegen<ExecQualFn> {
* @param codegen_utils Utility to ease the code generation process.
* @return true on successful generation.
**/
bool GenerateExecQual(gpcodegen::CodegenUtils* codegen_utils);
bool GenerateExecQual(gpcodegen::GpCodegenUtils* codegen_utils);
};
/** @} */
......
......@@ -76,7 +76,7 @@ class ExecVariableListCodegen: public BaseCodegen<ExecVariableListFn> {
* If at execution time, we see any of the above types of attributes, we fall backs to
* the regular function.
*/
bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) final;
bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) final;
private:
ProjectionInfo* proj_info_;
......@@ -91,7 +91,7 @@ class ExecVariableListCodegen: public BaseCodegen<ExecVariableListFn> {
* @param codegen_utils Utility to ease the code generation process.
* @return true on successful generation.
**/
bool GenerateExecVariableList(gpcodegen::CodegenUtils* codegen_utils);
bool GenerateExecVariableList(gpcodegen::GpCodegenUtils* codegen_utils);
};
/** @} */
......
......@@ -80,7 +80,7 @@ class CodegenUtils {
**/
explicit CodegenUtils(llvm::StringRef module_name);
~CodegenUtils() {
virtual ~CodegenUtils() {
}
/**
......@@ -328,6 +328,60 @@ class CodegenUtils {
true);
}
/*
* @brief Register an external function if previously unregistered. Otherwise
* return a pointer to the previously registered llvm::Function
*
* @warning This method returns a pointer to an llvm::Function object. The
* caller should NOT attempt to add BasicBlocks to the function, as
* that would cause conflicts when mapping the function to its
* external implementation during PrepareForExecution().
*
* @tparam ReturnType The return type of the external_function. This does not
* need to be specified if external_function is not overloaded (it
* will be inferred automatically).
* @tparam argument_types The types of the arguments to external_function.
* These do not need to be specified if external_function is not
* overloaded (they will be inferred automatically).
* @param external_function A function pointer to install for use in this
* CodegenUtils.
* @param name An optional name to refer to the external function by. If
* non-empty, this CodegenUtils will record additional information
* so that the registered function will also be callable by its name
* in C++ source code compiled by ClangCompiler (see
* ClangCompiler::GenerateExternalFunctionDeclarations()).
* @param is_var_arg Whether the function has trailing variable arguments list
* @return A callable LLVM function.
*/
template <typename ReturnType, typename... ArgumentTypes>
llvm::Function* GetOrRegisterExternalFunction(
ReturnType (*external_function)(ArgumentTypes...),
const std::string& name = "",
const bool is_var_arg = false) {
auto it = std::find_if(
external_functions_.begin(),
external_functions_.end(),
[external_function] (decltype(external_functions_)::value_type val) -> bool {
return val.second == reinterpret_cast<std::uint64_t>(external_function);
});
if (it == external_functions_.end()) {
// If not found
return RegisterExternalFunction(external_function, name, is_var_arg);
} else {
return module()->getFunction(it->first);
}
}
template <typename ReturnType, typename... ArgumentTypes>
llvm::Function* GetOrRegisterExternalFunction(
ReturnType (*external_function)(ArgumentTypes..., ...),
const std::string& name = "") {
return GetOrRegisterExternalFunction(
reinterpret_cast<ReturnType (*)(ArgumentTypes...)>(external_function),
name,
true);
}
/**
* @brief Optimize the code in the module managed by this CodegenUtils before
......
//---------------------------------------------------------------------------
// Greenplum Database
// Copyright 2016 Pivotal Software, Inc.
//
// @filename:
// gp_codegen_utils.h
//
// @doc:
// Object that extends the functionality of CodegenUtils by adding GPDB
// specific functionality and utilities to aid in the runtime code generation
// for a LLVM module
//
// @test:
//
//
//---------------------------------------------------------------------------
#ifndef GPCODEGEN_GP_CODEGEN_UTILS_H_ // NOLINT(build/header_guard)
#define GPCODEGEN_GP_CODEGEN_UTILS_H_
#include "codegen/utils/codegen_utils.h"
extern "C" {
#include "utils/elog.h"
}
namespace gpcodegen {
class GpCodegenUtils : public CodegenUtils {
public:
/**
* @brief Constructor.
*
* @param module_name A human-readable name for the module that this
* CodegenUtils will manage.
**/
explicit GpCodegenUtils(llvm::StringRef module_name)
: CodegenUtils(module_name) {
}
~GpCodegenUtils() {
}
/*
* @brief Create LLVM instructions to call elog_start() and elog_finish().
*
* @warning This method does not create instructions for any sort of exception
* handling. In the case that elog throws an error, the code with jump
* straight out of the compiled module back to the last location in GPDB
* that setjump was called.
*
* @param llvm_elevel llvm::Value pointer to an integer representing the error level
* @param llvm_fmt llvm::Value pointer to the format string
* @tparam args llvm::Value pointers to arguments to elog()
*/
template<typename... V>
void CreateElog(
llvm::Value* llvm_elevel,
llvm::Value* llvm_fmt,
const V ... args ) {
assert(NULL != llvm_elevel);
assert(NULL != llvm_fmt);
llvm::Function* llvm_elog_start =
GetOrRegisterExternalFunction(elog_start);
llvm::Function* llvm_elog_finish =
GetOrRegisterExternalFunction(elog_finish);
ir_builder()->CreateCall(
llvm_elog_start, {
GetConstant(""), // Filename
GetConstant(0), // line number
GetConstant("") // function name
});
ir_builder()->CreateCall(
llvm_elog_finish, {
llvm_elevel,
llvm_fmt,
args...
});
}
/*
* @brief Create LLVM instructions to call elog_start() and elog_finish().
* A convenient alternative that automatically converts an integer elevel and
* format string to LLVM constants.
*
* @warning This method does not create instructions for any sort of exception
* handling. In the case that elog throws an error, the code with jump
* straight out of the compiled module back to the last location in GPDB
* that setjump was called.
*
* @param elevel Integer representing the error level
* @param fmt Format string
* @tparam args llvm::Value pointers to arguments to elog()
*/
template<typename... V>
void CreateElog(
int elevel,
const char* fmt,
const V ... args ) {
CreateElog(GetConstant(elevel), GetConstant(fmt), args...);
}
};
} // namespace gpcodegen
#endif // GPCODEGEN_GP_CODEGEN_UTILS_H
// EOF
......@@ -87,7 +87,7 @@ class SumCodeGenerator : public BaseCodegen<SumFunc> {
virtual ~SumCodeGenerator() = default;
protected:
bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) final {
bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) final {
llvm::Function* add2_func
= CreateFunction<SumFunc>(codegen_utils, GetUniqueFuncName());
llvm::BasicBlock* add2_body = codegen_utils->CreateBasicBlock("body",
......@@ -117,7 +117,7 @@ class FailingCodeGenerator : public BaseCodegen<SumFunc> {
virtual ~FailingCodeGenerator() = default;
protected:
bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) final {
bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) final {
return false;
}
......@@ -139,7 +139,7 @@ class UncompilableCodeGenerator : public BaseCodegen<UncompilableFunc> {
virtual ~UncompilableCodeGenerator() = default;
protected:
bool GenerateCodeInternal(gpcodegen::CodegenUtils* codegen_utils) final {
bool GenerateCodeInternal(gpcodegen::GpCodegenUtils* codegen_utils) final {
llvm::Function* dummy_func
= CreateFunction<UncompilableFunc>(codegen_utils,
GetUniqueFuncName());
......
......@@ -2736,6 +2736,28 @@ TEST_F(CodegenUtilsTest, CppClassObjectTest) {
EXPECT_EQ(-12.75, (*accumulate_test_fn_compiled)(-22.75));
}
// Test GetOrRegisterExternalFunction to return the right llvm::Function if
// previously registered or else register it anew
TEST_F(CodegenUtilsTest, GetOrRegisterExternalFunctionTest) {
// Test previous unregistered function
EXPECT_EQ(nullptr, codegen_utils_->module()->getFunction("floor"));
llvm::Function* floor_func = codegen_utils_->GetOrRegisterExternalFunction(floor, "floor");
EXPECT_EQ(floor_func, codegen_utils_->module()->getFunction("floor"));
// Test previous registered Non vararg function
llvm::Function* expected_fabs_func = codegen_utils_->RegisterExternalFunction(ceil);
llvm::Function* fabs_func = codegen_utils_->GetOrRegisterExternalFunction(ceil);
EXPECT_EQ(expected_fabs_func, fabs_func);
// Test previously registered vararg function
llvm::Function* expected_vprintf_func = codegen_utils_->RegisterExternalFunction(vprintf);
llvm::Function* vprintf_func = codegen_utils_->GetOrRegisterExternalFunction(vprintf);
EXPECT_EQ(expected_vprintf_func, vprintf_func);
}
#ifdef GPCODEGEN_DEBUG
......
//---------------------------------------------------------------------------
// Greenplum Database
// Copyright 2016 Pivotal Software, Inc.
//
// @filename:
// gp_codegen_utils.cc
//
// @doc:
// Object that extends the functionality of CodegenUtils by adding GPDB
// specific functionality and utilities to aid in the runtime code generation
// for a LLVM module
//
// @test:
// Unittests in tests/code_generator_unittest.cc
//
//
//---------------------------------------------------------------------------
#include "codegen/utils/gp_codegen_utils.h"
namespace gpcodegen {
} // namespace gpcodegen
// EOF
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册