Commit 8c667486 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Box traceback strings once

It had gotten to the point that a large part of the cost of throwing an
eception was allocating the std::string's to represent the function and
filename.

Most of the other strings have already been converted to being represented
as BoxedString's (ie python strings), so do that conversion here and a few
more places.
parent 21257c97
...@@ -294,7 +294,7 @@ void ASTInterpreter::startJITing(CFGBlock* block, int exit_offset) { ...@@ -294,7 +294,7 @@ void ASTInterpreter::startJITing(CFGBlock* block, int exit_offset) {
code_block = code_blocks[code_blocks.size() - 1].get(); code_block = code_blocks[code_blocks.size() - 1].get();
if (!code_block || code_block->shouldCreateNewBlock()) { if (!code_block || code_block->shouldCreateNewBlock()) {
code_blocks.push_back(std::unique_ptr<JitCodeBlock>(new JitCodeBlock(source_info->getName()))); code_blocks.push_back(std::unique_ptr<JitCodeBlock>(new JitCodeBlock(source_info->getName()->s())));
code_block = code_blocks[code_blocks.size() - 1].get(); code_block = code_blocks[code_blocks.size() - 1].get();
exit_offset = 0; exit_offset = 0;
} }
...@@ -334,7 +334,7 @@ Box* ASTInterpreter::execJITedBlock(CFGBlock* b) { ...@@ -334,7 +334,7 @@ Box* ASTInterpreter::execJITedBlock(CFGBlock* b) {
auto source = getCL()->source.get(); auto source = getCL()->source.get();
stmt->cxx_exception_count++; stmt->cxx_exception_count++;
caughtCxxException(LineInfo(stmt->lineno, stmt->col_offset, source->fn, source->getName()), &e); caughtCxxException(LineInfo(stmt->lineno, stmt->col_offset, source->getFn(), source->getName()), &e);
next_block = ((AST_Invoke*)stmt)->exc_dest; next_block = ((AST_Invoke*)stmt)->exc_dest;
last_exception = e; last_exception = e;
...@@ -791,7 +791,7 @@ Value ASTInterpreter::visit_invoke(AST_Invoke* node) { ...@@ -791,7 +791,7 @@ Value ASTInterpreter::visit_invoke(AST_Invoke* node) {
auto source = getCL()->source.get(); auto source = getCL()->source.get();
node->cxx_exception_count++; node->cxx_exception_count++;
caughtCxxException(LineInfo(node->lineno, node->col_offset, source->fn, source->getName()), &e); caughtCxxException(LineInfo(node->lineno, node->col_offset, source->getFn(), source->getName()), &e);
next_block = node->exc_dest; next_block = node->exc_dest;
last_exception = e; last_exception = e;
...@@ -925,7 +925,7 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) { ...@@ -925,7 +925,7 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) {
#endif #endif
if (0) { if (0) {
printf("%20s % 2d ", source_info->getName().data(), current_block->idx); printf("%20s % 2d ", source_info->getName()->c_str(), current_block->idx);
print_ast(node); print_ast(node);
printf("\n"); printf("\n");
} }
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "codegen/compvars.h" #include "codegen/compvars.h"
#include "core/ast.h" #include "core/ast.h"
#include "core/util.h" #include "core/util.h"
#include "runtime/types.h"
namespace pyston { namespace pyston {
...@@ -60,16 +61,18 @@ CLFunction::CLFunction(int num_args, int num_defaults, bool takes_varargs, bool ...@@ -60,16 +61,18 @@ CLFunction::CLFunction(int num_args, int num_defaults, bool takes_varargs, bool
} }
SourceInfo::SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, FutureFlags future_flags, AST* ast, SourceInfo::SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, FutureFlags future_flags, AST* ast,
std::vector<AST_stmt*> body, std::string fn) std::vector<AST_stmt*> body, BoxedString* fn)
: parent_module(m), : parent_module(m),
scoping(scoping), scoping(scoping),
scope_info(NULL), scope_info(NULL),
future_flags(future_flags), future_flags(future_flags),
ast(ast), ast(ast),
cfg(NULL), cfg(NULL),
fn(std::move(fn)),
body(std::move(body)) { body(std::move(body)) {
assert(this->fn.size()); assert(fn->size());
// TODO: we should track this reference correctly rather than making it a root
gc::registerPermanentRoot(fn, true);
this->fn = fn;
switch (ast->type) { switch (ast->type) {
case AST_TYPE::ClassDef: case AST_TYPE::ClassDef:
......
...@@ -934,18 +934,18 @@ static llvm::MDNode* setupDebugInfo(SourceInfo* source, llvm::Function* f, std:: ...@@ -934,18 +934,18 @@ static llvm::MDNode* setupDebugInfo(SourceInfo* source, llvm::Function* f, std::
llvm::DIBuilder builder(*g.cur_module); llvm::DIBuilder builder(*g.cur_module);
const std::string& fn = source->fn; BoxedString* fn = source->getFn();
std::string dir = ""; std::string dir = "";
std::string producer = "pyston; git rev " STRINGIFY(GITREV); std::string producer = "pyston; git rev " STRINGIFY(GITREV);
llvm::DIFile file = builder.createFile(fn, dir); llvm::DIFile file = builder.createFile(fn->s(), dir);
llvm::DITypeArray param_types = builder.getOrCreateTypeArray(llvm::None); llvm::DITypeArray param_types = builder.getOrCreateTypeArray(llvm::None);
llvm::DICompositeType func_type = builder.createSubroutineType(file, param_types); llvm::DICompositeType func_type = builder.createSubroutineType(file, param_types);
llvm::DISubprogram func_info = builder.createFunction(file, f->getName(), f->getName(), file, lineno, func_type, llvm::DISubprogram func_info = builder.createFunction(file, f->getName(), f->getName(), file, lineno, func_type,
false, true, lineno + 1, 0, true, f); false, true, lineno + 1, 0, true, f);
llvm::DICompileUnit compile_unit llvm::DICompileUnit compile_unit
= builder.createCompileUnit(llvm::dwarf::DW_LANG_Python, fn, dir, producer, true, "", 0); = builder.createCompileUnit(llvm::dwarf::DW_LANG_Python, fn->s(), dir, producer, true, "", 0);
builder.finalize(); builder.finalize();
return func_info; return func_info;
...@@ -967,7 +967,7 @@ static std::string getUniqueFunctionName(std::string nameprefix, EffortLevel eff ...@@ -967,7 +967,7 @@ static std::string getUniqueFunctionName(std::string nameprefix, EffortLevel eff
CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names, CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names,
const OSREntryDescriptor* entry_descriptor, EffortLevel effort, const OSREntryDescriptor* entry_descriptor, EffortLevel effort,
ExceptionStyle exception_style, FunctionSpecialization* spec, std::string nameprefix) { ExceptionStyle exception_style, FunctionSpecialization* spec, llvm::StringRef nameprefix) {
Timer _t("in doCompile"); Timer _t("in doCompile");
Timer _t2; Timer _t2;
long irgen_us = 0; long irgen_us = 0;
......
...@@ -113,7 +113,7 @@ bool isIsDefinedName(llvm::StringRef name); ...@@ -113,7 +113,7 @@ bool isIsDefinedName(llvm::StringRef name);
CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names, CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names,
const OSREntryDescriptor* entry_descriptor, EffortLevel effort, const OSREntryDescriptor* entry_descriptor, EffortLevel effort,
ExceptionStyle exception_style, FunctionSpecialization* spec, std::string nameprefix); ExceptionStyle exception_style, FunctionSpecialization* spec, llvm::StringRef nameprefix);
// A common pattern is to branch based off whether a variable is defined but only if it is // A common pattern is to branch based off whether a variable is defined but only if it is
// potentially-undefined. If it is potentially-undefined, we have to generate control-flow // potentially-undefined. If it is potentially-undefined, we have to generate control-flow
......
This diff is collapsed.
...@@ -2961,7 +2961,7 @@ CLFunction* wrapFunction(AST* node, AST_arguments* args, const std::vector<AST_s ...@@ -2961,7 +2961,7 @@ CLFunction* wrapFunction(AST* node, AST_arguments* args, const std::vector<AST_s
CLFunction*& cl = made[node]; CLFunction*& cl = made[node];
if (cl == NULL) { if (cl == NULL) {
std::unique_ptr<SourceInfo> si( std::unique_ptr<SourceInfo> si(
new SourceInfo(source->parent_module, source->scoping, source->future_flags, node, body, source->fn)); new SourceInfo(source->parent_module, source->scoping, source->future_flags, node, body, source->getFn()));
if (args) if (args)
cl = new CLFunction(args->args.size(), args->defaults.size(), args->vararg.s().size(), cl = new CLFunction(args->args.size(), args->defaults.size(), args->vararg.s().size(),
args->kwarg.s().size(), std::move(si)); args->kwarg.s().size(), std::move(si));
......
...@@ -490,7 +490,7 @@ static const LineInfo lineInfoForFrame(PythonFrameIteratorImpl* frame_it) { ...@@ -490,7 +490,7 @@ static const LineInfo lineInfoForFrame(PythonFrameIteratorImpl* frame_it) {
auto source = cl->source.get(); auto source = cl->source.get();
return LineInfo(current_stmt->lineno, current_stmt->col_offset, source->fn, source->getName()); return LineInfo(current_stmt->lineno, current_stmt->col_offset, source->getFn(), source->getName());
} }
// A class that converts a C stack trace to a Python stack trace. // A class that converts a C stack trace to a Python stack trace.
...@@ -1146,7 +1146,7 @@ std::string getCurrentPythonLine() { ...@@ -1146,7 +1146,7 @@ std::string getCurrentPythonLine() {
auto current_stmt = frame_iter->getCurrentStatement(); auto current_stmt = frame_iter->getCurrentStatement();
stream << source->fn << ":" << current_stmt->lineno; stream << source->getFn()->c_str() << ":" << current_stmt->lineno;
return stream.str(); return stream.str();
} }
return "unknown:-1"; return "unknown:-1";
......
...@@ -2733,7 +2733,7 @@ CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) { ...@@ -2733,7 +2733,7 @@ CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) {
if (b->predecessors.size() == 0) { if (b->predecessors.size() == 0) {
if (b != rtn->getStartingBlock()) { if (b != rtn->getStartingBlock()) {
rtn->print(); rtn->print();
printf("%s\n", source->getName().data()); printf("%s\n", source->getName()->c_str());
} }
ASSERT(b == rtn->getStartingBlock(), "%d", b->idx); ASSERT(b == rtn->getStartingBlock(), "%d", b->idx);
} }
......
...@@ -304,6 +304,8 @@ class ScopeInfo; ...@@ -304,6 +304,8 @@ class ScopeInfo;
class InternedStringPool; class InternedStringPool;
class LivenessAnalysis; class LivenessAnalysis;
class SourceInfo { class SourceInfo {
private:
BoxedString* fn; // equivalent of code.co_filename
public: public:
BoxedModule* parent_module; BoxedModule* parent_module;
ScopingAnalysis* scoping; ScopingAnalysis* scoping;
...@@ -312,7 +314,6 @@ public: ...@@ -312,7 +314,6 @@ public:
AST* ast; AST* ast;
CFG* cfg; CFG* cfg;
bool is_generator; bool is_generator;
std::string fn; // equivalent of code.co_filename
InternedStringPool& getInternedStrings(); InternedStringPool& getInternedStrings();
...@@ -323,13 +324,15 @@ public: ...@@ -323,13 +324,15 @@ public:
// body and we have to create one. Ideally, we'd be able to avoid the space duplication for non-lambdas. // body and we have to create one. Ideally, we'd be able to avoid the space duplication for non-lambdas.
const std::vector<AST_stmt*> body; const std::vector<AST_stmt*> body;
llvm::StringRef getName(); BoxedString* getName();
BoxedString* getFn() { return fn; }
InternedString mangleName(InternedString id); InternedString mangleName(InternedString id);
Box* getDocString(); Box* getDocString();
SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, FutureFlags future_flags, AST* ast, SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, FutureFlags future_flags, AST* ast,
std::vector<AST_stmt*> body, std::string fn); std::vector<AST_stmt*> body, BoxedString* fn);
~SourceInfo(); ~SourceInfo();
private: private:
...@@ -727,9 +730,9 @@ void raiseSyntaxErrorHelper(llvm::StringRef file, llvm::StringRef func, AST* nod ...@@ -727,9 +730,9 @@ void raiseSyntaxErrorHelper(llvm::StringRef file, llvm::StringRef func, AST* nod
struct LineInfo { struct LineInfo {
public: public:
int line, column; int line, column;
std::string file, func; BoxedString* file, *func;
LineInfo(int line, int column, llvm::StringRef file, llvm::StringRef func) LineInfo(int line, int column, BoxedString* file, BoxedString* func)
: line(line), column(column), file(file), func(func) {} : line(line), column(column), file(file), func(func) {}
}; };
......
...@@ -44,12 +44,12 @@ public: ...@@ -44,12 +44,12 @@ public:
static Box* name(Box* b, void*) { static Box* name(Box* b, void*) {
RELEASE_ASSERT(b->cls == code_cls, ""); RELEASE_ASSERT(b->cls == code_cls, "");
return boxString(static_cast<BoxedCode*>(b)->f->source->getName()); return static_cast<BoxedCode*>(b)->f->source->getName();
} }
static Box* filename(Box* b, void*) { static Box* filename(Box* b, void*) {
RELEASE_ASSERT(b->cls == code_cls, ""); RELEASE_ASSERT(b->cls == code_cls, "");
return boxString(static_cast<BoxedCode*>(b)->f->source->fn); return static_cast<BoxedCode*>(b)->f->source->getFn();
} }
static Box* firstlineno(Box* b, void*) { static Box* firstlineno(Box* b, void*) {
......
...@@ -359,8 +359,8 @@ static void print_frame(unw_cursor_t* cursor, const unw_proc_info_t* pip) { ...@@ -359,8 +359,8 @@ static void print_frame(unw_cursor_t* cursor, const unw_proc_info_t* pip) {
if (frame_type == INTERPRETED && cf && cur_stmt) { if (frame_type == INTERPRETED && cf && cur_stmt) {
auto source = cf->clfunc->source.get(); auto source = cf->clfunc->source.get();
// FIXME: dup'ed from lineInfoForFrame // FIXME: dup'ed from lineInfoForFrame
LineInfo line(cur_stmt->lineno, cur_stmt->col_offset, source->fn, source->getName()); LineInfo line(cur_stmt->lineno, cur_stmt->col_offset, source->getFn(), source->getName());
printf(" File \"%s\", line %d, in %s\n", line.file.c_str(), line.line, line.func.c_str()); printf(" File \"%s\", line %d, in %s\n", line.file->c_str(), line.line, line.func->c_str());
} }
} }
......
...@@ -37,7 +37,7 @@ void raiseExc(Box* exc_obj) { ...@@ -37,7 +37,7 @@ void raiseExc(Box* exc_obj) {
void raiseSyntaxError(const char* msg, int lineno, int col_offset, llvm::StringRef file, llvm::StringRef func) { void raiseSyntaxError(const char* msg, int lineno, int col_offset, llvm::StringRef file, llvm::StringRef func) {
Box* exc = runtimeCall(SyntaxError, ArgPassSpec(1), boxString(msg), NULL, NULL, NULL, NULL); Box* exc = runtimeCall(SyntaxError, ArgPassSpec(1), boxString(msg), NULL, NULL, NULL, NULL);
auto tb = new BoxedTraceback(LineInfo(lineno, col_offset, file, func), None); auto tb = new BoxedTraceback(LineInfo(lineno, col_offset, boxString(file), boxString(func)), None);
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
throw ExcInfo(exc->cls, exc, tb); throw ExcInfo(exc->cls, exc, tb);
} }
...@@ -243,7 +243,8 @@ extern "C" void caughtCapiException(AST_stmt* stmt, void* _source_info) { ...@@ -243,7 +243,8 @@ extern "C" void caughtCapiException(AST_stmt* stmt, void* _source_info) {
SourceInfo* source = static_cast<SourceInfo*>(_source_info); SourceInfo* source = static_cast<SourceInfo*>(_source_info);
PyThreadState* tstate = PyThreadState_GET(); PyThreadState* tstate = PyThreadState_GET();
exceptionAtLine(LineInfo(stmt->lineno, stmt->col_offset, source->fn, source->getName()), &tstate->curexc_traceback); exceptionAtLine(LineInfo(stmt->lineno, stmt->col_offset, source->getFn(), source->getName()),
&tstate->curexc_traceback);
} }
extern "C" void reraiseCapiExcAsCxx() { extern "C" void reraiseCapiExcAsCxx() {
......
...@@ -468,7 +468,7 @@ Box* generatorName(Box* _self, void* context) { ...@@ -468,7 +468,7 @@ Box* generatorName(Box* _self, void* context) {
assert(isSubclass(_self->cls, generator_cls)); assert(isSubclass(_self->cls, generator_cls));
BoxedGenerator* self = static_cast<BoxedGenerator*>(_self); BoxedGenerator* self = static_cast<BoxedGenerator*>(_self);
return boxString(self->function->f->source->getName()); return self->function->f->source->getName();
} }
void generatorDestructor(Box* b) { void generatorDestructor(Box* b) {
......
...@@ -2958,7 +2958,7 @@ static CompiledFunction* pickVersion(CLFunction* f, ExceptionStyle S, int num_ou ...@@ -2958,7 +2958,7 @@ static CompiledFunction* pickVersion(CLFunction* f, ExceptionStyle S, int num_ou
static llvm::StringRef getFunctionName(CLFunction* f) { static llvm::StringRef getFunctionName(CLFunction* f) {
if (f->source) if (f->source)
return f->source->getName(); return f->source->getName()->s();
else if (f->versions.size()) { else if (f->versions.size()) {
return "<builtin function>"; return "<builtin function>";
// std::ostringstream oss; // std::ostringstream oss;
......
...@@ -45,6 +45,9 @@ void BoxedTraceback::gcHandler(GCVisitor* v, Box* b) { ...@@ -45,6 +45,9 @@ void BoxedTraceback::gcHandler(GCVisitor* v, Box* b) {
if (self->tb_next) if (self->tb_next)
v->visit(self->tb_next); v->visit(self->tb_next);
v->visit(self->line.file);
v->visit(self->line.func);
Box::gcHandler(v, b); Box::gcHandler(v, b);
} }
...@@ -59,12 +62,12 @@ void printTraceback(Box* b) { ...@@ -59,12 +62,12 @@ void printTraceback(Box* b) {
for (; tb && tb != None; tb = static_cast<BoxedTraceback*>(tb->tb_next)) { for (; tb && tb != None; tb = static_cast<BoxedTraceback*>(tb->tb_next)) {
auto& line = tb->line; auto& line = tb->line;
fprintf(stderr, " File \"%s\", line %d, in %s:\n", line.file.c_str(), line.line, line.func.c_str()); fprintf(stderr, " File \"%s\", line %d, in %s:\n", line.file->c_str(), line.line, line.func->c_str());
if (line.line < 0) if (line.line < 0)
continue; continue;
FILE* f = fopen(line.file.c_str(), "r"); FILE* f = fopen(line.file->c_str(), "r");
if (f) { if (f) {
assert(line.line < 10000000 && "Refusing to try to seek that many lines forward"); assert(line.line < 10000000 && "Refusing to try to seek that many lines forward");
for (int i = 1; i < line.line; i++) { for (int i = 1; i < line.line; i++) {
...@@ -104,7 +107,7 @@ Box* BoxedTraceback::getLines(Box* b) { ...@@ -104,7 +107,7 @@ Box* BoxedTraceback::getLines(Box* b) {
BoxedList* lines = new BoxedList(); BoxedList* lines = new BoxedList();
for (BoxedTraceback* wtb = tb; wtb && wtb != None; wtb = static_cast<BoxedTraceback*>(wtb->tb_next)) { for (BoxedTraceback* wtb = tb; wtb && wtb != None; wtb = static_cast<BoxedTraceback*>(wtb->tb_next)) {
auto& line = wtb->line; auto& line = wtb->line;
auto l = BoxedTuple::create({ boxString(line.file), boxString(line.func), boxInt(line.line) }); auto l = BoxedTuple::create({ line.file, line.func, boxInt(line.line) });
listAppendInternal(lines, l); listAppendInternal(lines, l);
} }
tb->py_lines = lines; tb->py_lines = lines;
......
...@@ -432,7 +432,7 @@ BoxedFunction::BoxedFunction(CLFunction* f, std::initializer_list<Box*> defaults ...@@ -432,7 +432,7 @@ BoxedFunction::BoxedFunction(CLFunction* f, std::initializer_list<Box*> defaults
// some builtin functions that are BoxedFunctions but really ought to be a type that // some builtin functions that are BoxedFunctions but really ought to be a type that
// we don't have yet. // we don't have yet.
if (f->source) { if (f->source) {
this->name = static_cast<BoxedString*>(boxString(f->source->getName())); this->name = static_cast<BoxedString*>(f->source->getName());
} }
} }
......
...@@ -256,7 +256,7 @@ extern "C" void dumpEx(void* p, int levels) { ...@@ -256,7 +256,7 @@ extern "C" void dumpEx(void* p, int levels) {
CLFunction* cl = f->f; CLFunction* cl = f->f;
if (cl->source) { if (cl->source) {
printf("User-defined function '%s'\n", cl->source->getName().data()); printf("User-defined function '%s'\n", cl->source->getName()->c_str());
} else { } else {
printf("A builtin function\n"); printf("A builtin function\n");
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "codegen/parser.h" #include "codegen/parser.h"
#include "core/ast.h" #include "core/ast.h"
#include "core/cfg.h" #include "core/cfg.h"
#include "runtime/types.h"
#include "unittests.h" #include "unittests.h"
using namespace pyston; using namespace pyston;
...@@ -39,7 +40,8 @@ TEST_F(AnalysisTest, augassign) { ...@@ -39,7 +40,8 @@ TEST_F(AnalysisTest, augassign) {
FutureFlags future_flags = getFutureFlags(module->body, fn.c_str()); FutureFlags future_flags = getFutureFlags(module->body, fn.c_str());
SourceInfo* si = new SourceInfo(createModule("augassign", fn.c_str()), scoping, future_flags, func, func->body, fn); SourceInfo* si = new SourceInfo(createModule("augassign", fn.c_str()), scoping, future_flags, func,
func->body, boxString(fn));
CFG* cfg = computeCFG(si, func->body); CFG* cfg = computeCFG(si, func->body);
std::unique_ptr<LivenessAnalysis> liveness = computeLivenessInfo(cfg); std::unique_ptr<LivenessAnalysis> liveness = computeLivenessInfo(cfg);
...@@ -69,7 +71,7 @@ void doOsrTest(bool is_osr, bool i_maybe_undefined) { ...@@ -69,7 +71,7 @@ void doOsrTest(bool is_osr, bool i_maybe_undefined) {
ScopeInfo* scope_info = scoping->getScopeInfoForNode(func); ScopeInfo* scope_info = scoping->getScopeInfoForNode(func);
std::unique_ptr<SourceInfo> si(new SourceInfo(createModule("osr" + std::to_string((is_osr << 1) + i_maybe_undefined), std::unique_ptr<SourceInfo> si(new SourceInfo(createModule("osr" + std::to_string((is_osr << 1) + i_maybe_undefined),
fn.c_str()), scoping, future_flags, func, func->body, fn)); fn.c_str()), scoping, future_flags, func, func->body, boxString(fn)));
CLFunction* clfunc = new CLFunction(0, 0, false, false, std::move(si)); CLFunction* clfunc = new CLFunction(0, 0, false, false, std::move(si));
CFG* cfg = computeCFG(clfunc->source.get(), func->body); CFG* cfg = computeCFG(clfunc->source.get(), func->body);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment