Commit 0814d218 authored by Marius Wachtler's avatar Marius Wachtler Committed by GitHub

Merge pull request #1338 from undingen/fix_pointer_reuse_problems

Fix pointer reuse problems
parents 0abee106 64961ca3
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "asm_writing/assembler.h" #include "asm_writing/assembler.h"
#include "asm_writing/mc_writer.h" #include "asm_writing/mc_writer.h"
#include "codegen/patchpoints.h" #include "codegen/patchpoints.h"
#include "codegen/type_recording.h"
#include "codegen/unwinding.h" #include "codegen/unwinding.h"
#include "core/common.h" #include "core/common.h"
#include "core/options.h" #include "core/options.h"
...@@ -339,14 +340,12 @@ static llvm::DenseMap<AST*, ICInfo*> ics_by_ast_node; ...@@ -339,14 +340,12 @@ static llvm::DenseMap<AST*, ICInfo*> ics_by_ast_node;
ICInfo::ICInfo(void* start_addr, void* slowpath_rtn_addr, void* continue_addr, StackInfo stack_info, int size, ICInfo::ICInfo(void* start_addr, void* slowpath_rtn_addr, void* continue_addr, StackInfo stack_info, int size,
llvm::CallingConv::ID calling_conv, LiveOutSet _live_outs, assembler::GenericRegister return_register, llvm::CallingConv::ID calling_conv, LiveOutSet _live_outs, assembler::GenericRegister return_register,
TypeRecorder* type_recorder, std::vector<Location> ic_global_decref_locations, std::vector<Location> ic_global_decref_locations, assembler::RegisterSet allocatable_registers)
assembler::RegisterSet allocatable_registers)
: next_slot_to_try(0), : next_slot_to_try(0),
stack_info(stack_info), stack_info(stack_info),
calling_conv(calling_conv), calling_conv(calling_conv),
live_outs(std::move(_live_outs)), live_outs(std::move(_live_outs)),
return_register(return_register), return_register(return_register),
type_recorder(type_recorder),
retry_in(0), retry_in(0),
retry_backoff(1), retry_backoff(1),
times_rewritten(0), times_rewritten(0),
...@@ -416,7 +415,7 @@ std::unique_ptr<ICInfo> registerCompiledPatchpoint(uint8_t* start_addr, uint8_t* ...@@ -416,7 +415,7 @@ std::unique_ptr<ICInfo> registerCompiledPatchpoint(uint8_t* start_addr, uint8_t*
ICInfo* icinfo ICInfo* icinfo
= new ICInfo(start_addr, slowpath_rtn_addr, continue_addr, stack_info, ic->size, ic->getCallingConvention(), = new ICInfo(start_addr, slowpath_rtn_addr, continue_addr, stack_info, ic->size, ic->getCallingConvention(),
std::move(live_outs), return_register, ic->type_recorder, decref_info, ic->allocatable_regs); std::move(live_outs), return_register, decref_info, ic->allocatable_regs);
assert(!ics_by_return_addr.count(slowpath_rtn_addr)); assert(!ics_by_return_addr.count(slowpath_rtn_addr));
ics_by_return_addr[slowpath_rtn_addr] = icinfo; ics_by_return_addr[slowpath_rtn_addr] = icinfo;
...@@ -487,9 +486,10 @@ ICInfo* ICInfo::getICInfoForNode(AST* node) { ...@@ -487,9 +486,10 @@ ICInfo* ICInfo::getICInfoForNode(AST* node) {
return it->second; return it->second;
return NULL; return NULL;
} }
void ICInfo::associateNodeWithICInfo(AST* node) { void ICInfo::associateNodeWithICInfo(AST* node, std::unique_ptr<TypeRecorder> type_recorder) {
assert(!this->node); assert(!this->node);
this->node = node; this->node = node;
this->type_recorder = std::move(type_recorder);
ics_by_ast_node[node] = this; ics_by_ast_node[node] = this;
} }
void ICInfo::appendDecrefInfosTo(std::vector<DecrefInfo>& dest_decref_infos) { void ICInfo::appendDecrefInfosTo(std::vector<DecrefInfo>& dest_decref_infos) {
......
...@@ -93,7 +93,7 @@ private: ...@@ -93,7 +93,7 @@ private:
const llvm::CallingConv::ID calling_conv; const llvm::CallingConv::ID calling_conv;
LiveOutSet live_outs; LiveOutSet live_outs;
const assembler::GenericRegister return_register; const assembler::GenericRegister return_register;
TypeRecorder* const type_recorder; std::unique_ptr<TypeRecorder> type_recorder;
int retry_in, retry_backoff; int retry_in, retry_backoff;
int times_rewritten; int times_rewritten;
assembler::RegisterSet allocatable_registers; assembler::RegisterSet allocatable_registers;
...@@ -113,7 +113,7 @@ private: ...@@ -113,7 +113,7 @@ private:
public: public:
ICInfo(void* start_addr, void* slowpath_rtn_addr, void* continue_addr, StackInfo stack_info, int size, ICInfo(void* start_addr, void* slowpath_rtn_addr, void* continue_addr, StackInfo stack_info, int size,
llvm::CallingConv::ID calling_conv, LiveOutSet live_outs, assembler::GenericRegister return_register, llvm::CallingConv::ID calling_conv, LiveOutSet live_outs, assembler::GenericRegister return_register,
TypeRecorder* type_recorder, std::vector<Location> ic_global_decref_locations, std::vector<Location> ic_global_decref_locations,
assembler::RegisterSet allocatable_registers = assembler::RegisterSet::stdAllocatable()); assembler::RegisterSet allocatable_registers = assembler::RegisterSet::stdAllocatable());
~ICInfo(); ~ICInfo();
void* const start_addr, *const slowpath_rtn_addr, *const continue_addr; void* const start_addr, *const slowpath_rtn_addr, *const continue_addr;
...@@ -123,6 +123,7 @@ public: ...@@ -123,6 +123,7 @@ public:
llvm::CallingConv::ID getCallingConvention() { return calling_conv; } llvm::CallingConv::ID getCallingConvention() { return calling_conv; }
const LiveOutSet& getLiveOuts() { return live_outs; } const LiveOutSet& getLiveOuts() { return live_outs; }
TypeRecorder* getTypeRecorder() { return type_recorder.get(); }
std::unique_ptr<ICSlotRewrite> startRewrite(const char* debug_name); std::unique_ptr<ICSlotRewrite> startRewrite(const char* debug_name);
void invalidate(ICSlotInfo* entry); void invalidate(ICSlotInfo* entry);
...@@ -145,7 +146,7 @@ public: ...@@ -145,7 +146,7 @@ public:
friend class ICSlotRewrite; friend class ICSlotRewrite;
static ICInfo* getICInfoForNode(AST* node); static ICInfo* getICInfoForNode(AST* node);
void associateNodeWithICInfo(AST* node); void associateNodeWithICInfo(AST* node, std::unique_ptr<TypeRecorder> type_recorder);
void appendDecrefInfosTo(std::vector<DecrefInfo>& dest_decref_infos); void appendDecrefInfosTo(std::vector<DecrefInfo>& dest_decref_infos);
}; };
...@@ -179,7 +180,7 @@ public: ...@@ -179,7 +180,7 @@ public:
ICInfo* getICInfo() { return ic_entry->ic; } ICInfo* getICInfo() { return ic_entry->ic; }
int getSlotSize() { return ic_entry->size; } int getSlotSize() { return ic_entry->size; }
uint8_t* getSlotStart() { return ic_entry->start_addr; } uint8_t* getSlotStart() { return ic_entry->start_addr; }
TypeRecorder* getTypeRecorder() { return getICInfo()->type_recorder; } TypeRecorder* getTypeRecorder() { return getICInfo()->getTypeRecorder(); }
assembler::GenericRegister returnRegister() { return getICInfo()->return_register; } assembler::GenericRegister returnRegister() { return getICInfo()->return_register; }
int getScratchSize() { return getICInfo()->stack_info.scratch_size; } int getScratchSize() { return getICInfo()->stack_info.scratch_size; }
int getScratchRspOffset() { int getScratchRspOffset() {
......
...@@ -179,6 +179,7 @@ public: ...@@ -179,6 +179,7 @@ public:
RewriterVar* cmp(AST_TYPE::AST_TYPE cmp_type, RewriterVar* other, Location loc = Location::any()); RewriterVar* cmp(AST_TYPE::AST_TYPE cmp_type, RewriterVar* other, Location loc = Location::any());
RewriterVar* toBool(Location loc = Location::any()); RewriterVar* toBool(Location loc = Location::any());
RefType getType() const { return reftype; }
RewriterVar* setType(RefType type); RewriterVar* setType(RefType type);
RewriterVar* setNullable(bool nullable) { RewriterVar* setNullable(bool nullable) {
this->nullable = nullable; this->nullable = nullable;
......
This diff is collapsed.
...@@ -251,6 +251,7 @@ private: ...@@ -251,6 +251,7 @@ private:
StackInfo stack_info; StackInfo stack_info;
AST* node; AST* node;
std::vector<Location> decref_infos; std::vector<Location> decref_infos;
std::unique_ptr<TypeRecorder> type_recorder;
}; };
llvm::SmallVector<PPInfo, 8> pp_infos; llvm::SmallVector<PPInfo, 8> pp_infos;
...@@ -349,13 +350,13 @@ private: ...@@ -349,13 +350,13 @@ private:
RewriterVar* emitCallWithAllocatedArgs(void* func_addr, const llvm::ArrayRef<RewriterVar*> args, RewriterVar* emitCallWithAllocatedArgs(void* func_addr, const llvm::ArrayRef<RewriterVar*> args,
const llvm::ArrayRef<RewriterVar*> additional_uses); const llvm::ArrayRef<RewriterVar*> additional_uses);
std::pair<RewriterVar*, RewriterAction*> emitPPCall(void* func_addr, llvm::ArrayRef<RewriterVar*> args, std::pair<RewriterVar*, RewriterAction*> emitPPCall(void* func_addr, llvm::ArrayRef<RewriterVar*> args,
unsigned short pp_size, AST* ast_node = NULL, unsigned short pp_size, bool should_record_type = false,
TypeRecorder* type_recorder = NULL, AST* ast_node = NULL,
llvm::ArrayRef<RewriterVar*> additional_uses = {}); llvm::ArrayRef<RewriterVar*> additional_uses = {});
static void assertNameDefinedHelper(const char* id); static void assertNameDefinedHelper(const char* id);
static Box* callattrHelper(Box* obj, BoxedString* attr, CallattrFlags flags, TypeRecorder* type_recorder, static Box* callattrHelper(Box* obj, BoxedString* attr, CallattrFlags flags, Box** args,
Box** args, std::vector<BoxedString*>* keyword_names); std::vector<BoxedString*>* keyword_names);
static Box* createDictHelper(uint64_t num, Box** keys, Box** values); static Box* createDictHelper(uint64_t num, Box** keys, Box** values);
static Box* createListHelper(uint64_t num, Box** data); static Box* createListHelper(uint64_t num, Box** data);
static Box* createSetHelper(uint64_t num, Box** data); static Box* createSetHelper(uint64_t num, Box** data);
...@@ -364,15 +365,14 @@ private: ...@@ -364,15 +365,14 @@ private:
static BORROWED(Box*) hasnextHelper(Box* b); static BORROWED(Box*) hasnextHelper(Box* b);
static BORROWED(Box*) nonzeroHelper(Box* b); static BORROWED(Box*) nonzeroHelper(Box* b);
static BORROWED(Box*) notHelper(Box* b); static BORROWED(Box*) notHelper(Box* b);
static Box* runtimeCallHelper(Box* obj, ArgPassSpec argspec, TypeRecorder* type_recorder, Box** args, static Box* runtimeCallHelper(Box* obj, ArgPassSpec argspec, Box** args, std::vector<BoxedString*>* keyword_names);
std::vector<BoxedString*>* keyword_names);
void _emitGetLocal(RewriterVar* val_var, const char* name); void _emitGetLocal(RewriterVar* val_var, const char* name);
void _emitJump(CFGBlock* b, RewriterVar* block_next, ExitInfo& exit_info); void _emitJump(CFGBlock* b, RewriterVar* block_next, ExitInfo& exit_info);
void _emitOSRPoint(); void _emitOSRPoint();
void _emitPPCall(RewriterVar* result, void* func_addr, llvm::ArrayRef<RewriterVar*> args, unsigned short pp_size, void _emitPPCall(RewriterVar* result, void* func_addr, llvm::ArrayRef<RewriterVar*> args, unsigned short pp_size,
AST* ast_node, llvm::ArrayRef<RewriterVar*> vars_to_bump); AST* ast_node, llvm::ArrayRef<RewriterVar*> vars_to_bump);
void _emitRecordType(RewriterVar* type_recorder_var, RewriterVar* obj_cls_var); void _emitRecordType(RewriterVar* obj_cls_var);
void _emitReturn(RewriterVar* v); void _emitReturn(RewriterVar* v);
void _emitSideExit(STOLEN(RewriterVar*) var, RewriterVar* val_constant, CFGBlock* next_block, void _emitSideExit(STOLEN(RewriterVar*) var, RewriterVar* val_constant, CFGBlock* next_block,
RewriterVar* false_path); RewriterVar* false_path);
......
...@@ -246,7 +246,7 @@ public: ...@@ -246,7 +246,7 @@ public:
bool do_patchpoint = ENABLE_ICSETATTRS; bool do_patchpoint = ENABLE_ICSETATTRS;
llvm::Instruction* inst; llvm::Instruction* inst;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createSetattrIC(info.getTypeRecorder(), info.getBJitICInfo()); auto pp = createSetattrIC(info.getBJitICInfo());
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -269,7 +269,7 @@ public: ...@@ -269,7 +269,7 @@ public:
bool do_patchpoint = false; bool do_patchpoint = false;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createDelattrIC(info.getTypeRecorder()); auto pp = createDelattrIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -316,7 +316,7 @@ public: ...@@ -316,7 +316,7 @@ public:
bool do_patchpoint = ENABLE_ICGENERICS; bool do_patchpoint = ENABLE_ICGENERICS;
llvm::Value* rtn; llvm::Value* rtn;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGenericIC(info.getTypeRecorder(), true, 256); auto pp = createGenericIC(true, 256);
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -350,7 +350,7 @@ public: ...@@ -350,7 +350,7 @@ public:
: emitter.setType(getNullPtr(g.llvm_value_type_ptr), RefType::BORROWED); : emitter.setType(getNullPtr(g.llvm_value_type_ptr), RefType::BORROWED);
llvm::Value* r = NULL; llvm::Value* r = NULL;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGetitemIC(info.getTypeRecorder(), info.getBJitICInfo()); auto pp = createGetitemIC(info.getBJitICInfo());
llvm::Instruction* uncasted = emitter.createIC( llvm::Instruction* uncasted = emitter.createIC(
std::move(pp), std::move(pp),
(void*)(target_exception_style == CAPI ? pyston::apply_slice : pyston::applySlice), (void*)(target_exception_style == CAPI ? pyston::apply_slice : pyston::applySlice),
...@@ -377,7 +377,7 @@ public: ...@@ -377,7 +377,7 @@ public:
llvm::Value* rtn; llvm::Value* rtn;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGetitemIC(info.getTypeRecorder(), info.getBJitICInfo()); auto pp = createGetitemIC(info.getBJitICInfo());
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -428,7 +428,7 @@ public: ...@@ -428,7 +428,7 @@ public:
// var has __iter__() // var has __iter__()
emitter.setCurrentBasicBlock(bb_has_iter); emitter.setCurrentBasicBlock(bb_has_iter);
auto pp = createGenericIC(info.getTypeRecorder(), true, 128); auto pp = createGenericIC(true, 128);
llvm::Instruction* uncasted = emitter.createIC(std::move(pp), (void*)pyston::createBoxedIterWrapperIfNeeded, llvm::Instruction* uncasted = emitter.createIC(std::move(pp), (void*)pyston::createBoxedIterWrapperIfNeeded,
{ converted_iter_call->getValue() }, info.unw_info); { converted_iter_call->getValue() }, info.unw_info);
llvm::Value* value_has_iter = createAfter<llvm::IntToPtrInst>(uncasted, uncasted, g.llvm_value_type_ptr, ""); llvm::Value* value_has_iter = createAfter<llvm::IntToPtrInst>(uncasted, uncasted, g.llvm_value_type_ptr, "");
...@@ -440,7 +440,7 @@ public: ...@@ -440,7 +440,7 @@ public:
// TODO: we could create a patchpoint if this turns out to be hot // TODO: we could create a patchpoint if this turns out to be hot
emitter.setCurrentBasicBlock(bb_no_iter); emitter.setCurrentBasicBlock(bb_no_iter);
auto pp2 = createGenericIC(info.getTypeRecorder(), true, 128); auto pp2 = createGenericIC(true, 128);
llvm::Instruction* value_no_iter llvm::Instruction* value_no_iter
= emitter.createIC(std::move(pp2), (void*)getiterHelper, { var->getValue() }, info.unw_info); = emitter.createIC(std::move(pp2), (void*)getiterHelper, { var->getValue() }, info.unw_info);
value_no_iter = createAfter<llvm::IntToPtrInst>(value_no_iter, value_no_iter, g.llvm_value_type_ptr, ""); value_no_iter = createAfter<llvm::IntToPtrInst>(value_no_iter, value_no_iter, g.llvm_value_type_ptr, "");
...@@ -483,7 +483,7 @@ public: ...@@ -483,7 +483,7 @@ public:
} }
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createBinexpIC(info.getTypeRecorder(), info.getBJitICInfo()); auto pp = createBinexpIC(info.getBJitICInfo());
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -571,7 +571,7 @@ CompilerVariable* UnknownType::getattr(IREmitter& emitter, const OpInfo& info, C ...@@ -571,7 +571,7 @@ CompilerVariable* UnknownType::getattr(IREmitter& emitter, const OpInfo& info, C
bool do_patchpoint = ENABLE_ICGETATTRS; bool do_patchpoint = ENABLE_ICGETATTRS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGetattrIC(info.getTypeRecorder(), info.getBJitICInfo()); auto pp = createGetattrIC(info.getBJitICInfo());
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -674,7 +674,7 @@ _call(IREmitter& emitter, const OpInfo& info, llvm::Value* func, ExceptionStyle ...@@ -674,7 +674,7 @@ _call(IREmitter& emitter, const OpInfo& info, llvm::Value* func, ExceptionStyle
if (do_patchpoint) { if (do_patchpoint) {
assert(func_addr); assert(func_addr);
auto pp = createCallsiteIC(info.getTypeRecorder(), args.size(), info.getBJitICInfo()); auto pp = createCallsiteIC(args.size(), info.getBJitICInfo());
llvm::Instruction* uncasted = emitter.createIC(std::move(pp), func_addr, llvm_args, info.unw_info, llvm::Instruction* uncasted = emitter.createIC(std::move(pp), func_addr, llvm_args, info.unw_info,
target_exception_style, getNullPtr(g.llvm_value_type_ptr)); target_exception_style, getNullPtr(g.llvm_value_type_ptr));
...@@ -782,7 +782,7 @@ ConcreteCompilerVariable* UnknownType::nonzero(IREmitter& emitter, const OpInfo& ...@@ -782,7 +782,7 @@ ConcreteCompilerVariable* UnknownType::nonzero(IREmitter& emitter, const OpInfo&
bool do_patchpoint = ENABLE_ICNONZEROS; bool do_patchpoint = ENABLE_ICNONZEROS;
llvm::Value* rtn_val; llvm::Value* rtn_val;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createNonzeroIC(info.getTypeRecorder()); auto pp = createNonzeroIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
...@@ -802,7 +802,7 @@ ConcreteCompilerVariable* UnknownType::unaryop(IREmitter& emitter, const OpInfo& ...@@ -802,7 +802,7 @@ ConcreteCompilerVariable* UnknownType::unaryop(IREmitter& emitter, const OpInfo&
llvm::Value* rtn = NULL; llvm::Value* rtn = NULL;
bool do_patchpoint = ENABLE_ICGENERICS; bool do_patchpoint = ENABLE_ICGENERICS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGenericIC(info.getTypeRecorder(), true, 256); auto pp = createGenericIC(true, 256);
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(converted->getValue()); llvm_args.push_back(converted->getValue());
...@@ -824,7 +824,7 @@ ConcreteCompilerVariable* UnknownType::hasnext(IREmitter& emitter, const OpInfo& ...@@ -824,7 +824,7 @@ ConcreteCompilerVariable* UnknownType::hasnext(IREmitter& emitter, const OpInfo&
do_patchpoint = false; // we are currently using runtime ics for this do_patchpoint = false; // we are currently using runtime ics for this
llvm::Value* rtn_val; llvm::Value* rtn_val;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createHasnextIC(info.getTypeRecorder()); auto pp = createHasnextIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(var->getValue()); llvm_args.push_back(var->getValue());
......
...@@ -163,20 +163,17 @@ llvm::Value* handlePotentiallyUndefined(ConcreteCompilerVariable* is_defined_var ...@@ -163,20 +163,17 @@ llvm::Value* handlePotentiallyUndefined(ConcreteCompilerVariable* is_defined_var
std::function<llvm::Value*(IREmitter&)> when_defined, std::function<llvm::Value*(IREmitter&)> when_defined,
std::function<llvm::Value*(IREmitter&)> when_undefined); std::function<llvm::Value*(IREmitter&)> when_undefined);
class TypeRecorder;
class OpInfo { class OpInfo {
private: private:
const EffortLevel effort; const EffortLevel effort;
TypeRecorder* const type_recorder;
ICInfo* bjit_ic_info; ICInfo* bjit_ic_info;
public: public:
const UnwindInfo unw_info; const UnwindInfo unw_info;
OpInfo(EffortLevel effort, TypeRecorder* type_recorder, const UnwindInfo& unw_info, ICInfo* bjit_ic_info) OpInfo(EffortLevel effort, const UnwindInfo& unw_info, ICInfo* bjit_ic_info)
: effort(effort), type_recorder(type_recorder), bjit_ic_info(bjit_ic_info), unw_info(unw_info) {} : effort(effort), bjit_ic_info(bjit_ic_info), unw_info(unw_info) {}
TypeRecorder* getTypeRecorder() const { return type_recorder; }
ICInfo* getBJitICInfo() const { return bjit_ic_info; } ICInfo* getBJitICInfo() const { return bjit_ic_info; }
ExceptionStyle preferredExceptionStyle() const { return unw_info.preferredExceptionStyle(); } ExceptionStyle preferredExceptionStyle() const { return unw_info.preferredExceptionStyle(); }
......
...@@ -758,24 +758,15 @@ IREmitter* createIREmitter(IRGenState* irstate, llvm::BasicBlock*& curblock, IRG ...@@ -758,24 +758,15 @@ IREmitter* createIREmitter(IRGenState* irstate, llvm::BasicBlock*& curblock, IRG
return new IREmitterImpl(irstate, curblock, irgenerator); return new IREmitterImpl(irstate, curblock, irgenerator);
} }
static std::unordered_map<AST_expr*, std::vector<BoxedString*>*> made_keyword_storage;
std::vector<BoxedString*>* getKeywordNameStorage(AST_Call* node) { std::vector<BoxedString*>* getKeywordNameStorage(AST_Call* node) {
auto it = made_keyword_storage.find(node); if (!node->keywords_names) {
if (it != made_keyword_storage.end()) node->keywords_names = llvm::make_unique<std::vector<BoxedString*>>();
return it->second; for (auto kw : node->keywords) {
node->keywords_names->push_back(kw->arg.getBox());
auto rtn = new std::vector<BoxedString*>(); }
made_keyword_storage.insert(it, std::make_pair(node, rtn));
// Only add the keywords to the array the first time, since
// the later times we will hit the cache which will have the
// keyword names already populated:
if (!rtn->size()) {
for (auto kw : node->keywords)
rtn->push_back(kw->arg.getBox());
} }
return rtn; return node->keywords_names.get();
} }
const std::string CREATED_CLOSURE_NAME = "#created_closure"; const std::string CREATED_CLOSURE_NAME = "#created_closure";
...@@ -843,25 +834,10 @@ private: ...@@ -843,25 +834,10 @@ private:
OpInfo getOpInfoForNode(AST* ast, const UnwindInfo& unw_info) { OpInfo getOpInfoForNode(AST* ast, const UnwindInfo& unw_info) {
assert(ast); assert(ast);
EffortLevel effort = irstate->getEffortLevel(); return OpInfo(irstate->getEffortLevel(), unw_info, ICInfo::getICInfoForNode(ast));
// This is the only place we create type recorders for the llvm tier;
// if we are ok with never doing that there's a bunch of code that could
// be removed.
bool record_types = false;
TypeRecorder* type_recorder;
if (record_types) {
type_recorder = getTypeRecorderForNode(ast);
} else {
type_recorder = NULL;
}
return OpInfo(irstate->getEffortLevel(), type_recorder, unw_info, ICInfo::getICInfoForNode(ast));
} }
OpInfo getEmptyOpInfo(const UnwindInfo& unw_info) { OpInfo getEmptyOpInfo(const UnwindInfo& unw_info) { return OpInfo(irstate->getEffortLevel(), unw_info, NULL); }
return OpInfo(irstate->getEffortLevel(), NULL, unw_info, NULL);
}
void createExprTypeGuard(llvm::Value* check_val, AST* node, llvm::Value* node_value, AST_stmt* current_statement) { void createExprTypeGuard(llvm::Value* check_val, AST* node, llvm::Value* node_value, AST_stmt* current_statement) {
assert(check_val->getType() == g.i1); assert(check_val->getType() == g.i1);
...@@ -1320,7 +1296,7 @@ private: ...@@ -1320,7 +1296,7 @@ private:
bool do_patchpoint = ENABLE_ICGETGLOBALS; bool do_patchpoint = ENABLE_ICGETGLOBALS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createGetGlobalIC(getOpInfoForNode(node, unw_info).getTypeRecorder()); auto pp = createGetGlobalIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(irstate->getGlobals()); llvm_args.push_back(irstate->getGlobals());
...@@ -2012,7 +1988,7 @@ private: ...@@ -2012,7 +1988,7 @@ private:
bool do_patchpoint = ENABLE_ICSETITEMS; bool do_patchpoint = ENABLE_ICSETITEMS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createSetitemIC(getEmptyOpInfo(unw_info).getTypeRecorder()); auto pp = createSetitemIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(target); llvm_args.push_back(target);
...@@ -2043,7 +2019,7 @@ private: ...@@ -2043,7 +2019,7 @@ private:
// patchpoints if it couldn't. // patchpoints if it couldn't.
bool do_patchpoint = ENABLE_ICSETITEMS; bool do_patchpoint = ENABLE_ICSETITEMS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createSetitemIC(getEmptyOpInfo(unw_info).getTypeRecorder()); auto pp = createSetitemIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(converted_target->getValue()); llvm_args.push_back(converted_target->getValue());
...@@ -2173,7 +2149,7 @@ private: ...@@ -2173,7 +2149,7 @@ private:
bool do_patchpoint = ENABLE_ICDELITEMS; bool do_patchpoint = ENABLE_ICDELITEMS;
if (do_patchpoint) { if (do_patchpoint) {
auto pp = createDelitemIC(getEmptyOpInfo(unw_info).getTypeRecorder()); auto pp = createDelitemIC();
std::vector<llvm::Value*> llvm_args; std::vector<llvm::Value*> llvm_args;
llvm_args.push_back(converted_target->getValue()); llvm_args.push_back(converted_target->getValue());
......
...@@ -32,67 +32,6 @@ ...@@ -32,67 +32,6 @@
namespace pyston { namespace pyston {
/*
static std::string getStringName(std::string strvalue) {
std::ostringstream name_os;
name_os << "str";
name_os << g.global_string_table.size();
name_os << '_';
for (int i = 0; i < strvalue.size(); i++) {
if (isalnum(strvalue[i]))
name_os << strvalue[i];
}
return name_os.str();
}
// Gets a reference (creating if necessary) to a global string constant with the given value.
// The return type will be a char array.
static llvm::Constant* getStringConstant(const std::string &str) {
if (g.global_string_table.find(str) != g.global_string_table.end()) {
llvm::GlobalVariable *gv = g.global_string_table[str];
llvm::Constant *rtn = g.cur_module->getOrInsertGlobal(gv->getName(), gv->getType()->getElementType());
return rtn;
}
int len = str.size();
std::vector<llvm::Constant*> chars;
for (int i = 0; i < len; i++) {
chars.push_back(llvm::ConstantInt::get(g.i8, str[i]));
}
llvm::ArrayType *at = llvm::ArrayType::get(g.i8, len);
llvm::Constant *val = llvm::ConstantArray::get(at, llvm::ArrayRef<llvm::Constant*>(chars));
llvm::GlobalVariable *gv = new llvm::GlobalVariable(*g.cur_module, at, true, llvm::GlobalValue::ExternalLinkage,
val, getStringName(str));
g.global_string_table[str] = gv;
return gv;
}
*/
std::unordered_map<std::string, const char*> strings;
/*
// Returns a llvm::Constant char* to a global string constant
llvm::Constant* getStringConstantPtr(llvm::StringRef str) {
const char* c;
if (strings.count(str)) {
c = strings[str];
} else {
char* buf = (char*)malloc(str.size() + 1);
memcpy(buf, str.c_str(), str.size());
buf[str.size()] = '\0';
strings[str] = buf;
c = buf;
}
return embedRelocatablePtr(c, g.i8->getPointerTo());
}
// Returns a llvm::Constant char* to a global string constant
llvm::Constant* getStringConstantPtr(llvm::StringRef str) {
return getStringConstantPtr(std::string(str, strlen(str) + 1));
}
*/
// Sometimes we want to embed pointers into the emitted code, usually to link the emitted code // Sometimes we want to embed pointers into the emitted code, usually to link the emitted code
// to some associated compiler-level data structure. // to some associated compiler-level data structure.
// It's slightly easier to emit them as integers (there are primitive integer constants but not pointer constants), // It's slightly easier to emit them as integers (there are primitive integer constants but not pointer constants),
......
...@@ -45,10 +45,8 @@ int ICSetupInfo::totalSize() const { ...@@ -45,10 +45,8 @@ int ICSetupInfo::totalSize() const {
static std::vector<std::pair<std::unique_ptr<PatchpointInfo>, void* /* addr of func to call */>> new_patchpoints; static std::vector<std::pair<std::unique_ptr<PatchpointInfo>, void* /* addr of func to call */>> new_patchpoints;
std::unique_ptr<ICSetupInfo> ICSetupInfo::initialize(bool has_return_value, int size, ICType type, std::unique_ptr<ICSetupInfo> ICSetupInfo::initialize(bool has_return_value, int size, ICType type,
TypeRecorder* type_recorder,
assembler::RegisterSet allocatable_regs) { assembler::RegisterSet allocatable_regs) {
auto rtn auto rtn = std::unique_ptr<ICSetupInfo>(new ICSetupInfo(type, size, has_return_value, allocatable_regs));
= std::unique_ptr<ICSetupInfo>(new ICSetupInfo(type, size, has_return_value, type_recorder, allocatable_regs));
// We use size == CALL_ONLY_SIZE to imply that the call isn't patchable // We use size == CALL_ONLY_SIZE to imply that the call isn't patchable
...@@ -382,57 +380,56 @@ int slotSize(ICInfo* bjit_ic_info, int default_size) { ...@@ -382,57 +380,56 @@ int slotSize(ICInfo* bjit_ic_info, int default_size) {
return suggested_size; return suggested_size;
} }
std::unique_ptr<ICSetupInfo> createGenericIC(TypeRecorder* type_recorder, bool has_return_value, int size) { std::unique_ptr<ICSetupInfo> createGenericIC(bool has_return_value, int size) {
return ICSetupInfo::initialize(has_return_value, size, ICSetupInfo::Generic, type_recorder); return ICSetupInfo::initialize(has_return_value, size, ICSetupInfo::Generic);
} }
std::unique_ptr<ICSetupInfo> createGetattrIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info) { std::unique_ptr<ICSetupInfo> createGetattrIC(ICInfo* bjit_ic_info) {
return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 1024), ICSetupInfo::Getattr, type_recorder); return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 1024), ICSetupInfo::Getattr);
} }
std::unique_ptr<ICSetupInfo> createGetitemIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info) { std::unique_ptr<ICSetupInfo> createGetitemIC(ICInfo* bjit_ic_info) {
return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 512), ICSetupInfo::Getitem, type_recorder); return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 512), ICSetupInfo::Getitem);
} }
std::unique_ptr<ICSetupInfo> createSetitemIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createSetitemIC() {
return ICSetupInfo::initialize(true, 512, ICSetupInfo::Setitem, type_recorder); return ICSetupInfo::initialize(true, 512, ICSetupInfo::Setitem);
} }
std::unique_ptr<ICSetupInfo> createDelitemIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createDelitemIC() {
return ICSetupInfo::initialize(false, 512, ICSetupInfo::Delitem, type_recorder); return ICSetupInfo::initialize(false, 512, ICSetupInfo::Delitem);
} }
std::unique_ptr<ICSetupInfo> createSetattrIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info) { std::unique_ptr<ICSetupInfo> createSetattrIC(ICInfo* bjit_ic_info) {
return ICSetupInfo::initialize(false, slotSize(bjit_ic_info, 1024), ICSetupInfo::Setattr, type_recorder); return ICSetupInfo::initialize(false, slotSize(bjit_ic_info, 1024), ICSetupInfo::Setattr);
} }
std::unique_ptr<ICSetupInfo> createDelattrIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createDelattrIC() {
return ICSetupInfo::initialize(false, 144, ICSetupInfo::Delattr, type_recorder); return ICSetupInfo::initialize(false, 144, ICSetupInfo::Delattr);
} }
std::unique_ptr<ICSetupInfo> createCallsiteIC(TypeRecorder* type_recorder, int num_args, ICInfo* bjit_ic_info) { std::unique_ptr<ICSetupInfo> createCallsiteIC(int num_args, ICInfo* bjit_ic_info) {
return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 4 * (640 + 48 * num_args)), ICSetupInfo::Callsite, return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 4 * (640 + 48 * num_args)), ICSetupInfo::Callsite);
type_recorder);
} }
std::unique_ptr<ICSetupInfo> createGetGlobalIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createGetGlobalIC() {
return ICSetupInfo::initialize(true, 128, ICSetupInfo::GetGlobal, type_recorder); return ICSetupInfo::initialize(true, 128, ICSetupInfo::GetGlobal);
} }
std::unique_ptr<ICSetupInfo> createBinexpIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info) { std::unique_ptr<ICSetupInfo> createBinexpIC(ICInfo* bjit_ic_info) {
return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 2048), ICSetupInfo::Binexp, type_recorder); return ICSetupInfo::initialize(true, slotSize(bjit_ic_info, 2048), ICSetupInfo::Binexp);
} }
std::unique_ptr<ICSetupInfo> createNonzeroIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createNonzeroIC() {
return ICSetupInfo::initialize(true, 1024, ICSetupInfo::Nonzero, type_recorder); return ICSetupInfo::initialize(true, 1024, ICSetupInfo::Nonzero);
} }
std::unique_ptr<ICSetupInfo> createHasnextIC(TypeRecorder* type_recorder) { std::unique_ptr<ICSetupInfo> createHasnextIC() {
return ICSetupInfo::initialize(true, 128, ICSetupInfo::Hasnext, type_recorder); return ICSetupInfo::initialize(true, 128, ICSetupInfo::Hasnext);
} }
std::unique_ptr<ICSetupInfo> createDeoptIC() { std::unique_ptr<ICSetupInfo> createDeoptIC() {
return ICSetupInfo::initialize(true, 0, ICSetupInfo::Deopt, NULL); return ICSetupInfo::initialize(true, 0, ICSetupInfo::Deopt);
} }
} // namespace pyston } // namespace pyston
...@@ -65,19 +65,13 @@ public: ...@@ -65,19 +65,13 @@ public:
}; };
private: private:
ICSetupInfo(ICType type, int size, bool has_return_value, TypeRecorder* type_recorder, ICSetupInfo(ICType type, int size, bool has_return_value, assembler::RegisterSet allocatable_regs)
assembler::RegisterSet allocatable_regs) : type(type), size(size), has_return_value(has_return_value), allocatable_regs(allocatable_regs) {}
: type(type),
size(size),
has_return_value(has_return_value),
type_recorder(type_recorder),
allocatable_regs(allocatable_regs) {}
public: public:
const ICType type; const ICType type;
const int size; const int size;
const bool has_return_value; const bool has_return_value;
TypeRecorder* const type_recorder;
assembler::RegisterSet allocatable_regs; assembler::RegisterSet allocatable_regs;
int totalSize() const; int totalSize() const;
...@@ -97,9 +91,9 @@ public: ...@@ -97,9 +91,9 @@ public:
return llvm::CallingConv::C; return llvm::CallingConv::C;
} }
static std::unique_ptr<ICSetupInfo> static std::unique_ptr<ICSetupInfo> initialize(bool has_return_value, int size, ICType type,
initialize(bool has_return_value, int size, ICType type, TypeRecorder* type_recorder, assembler::RegisterSet allocatable_regs
assembler::RegisterSet allocatable_regs = assembler::RegisterSet::stdAllocatable()); = assembler::RegisterSet::stdAllocatable());
}; };
struct PatchpointInfo { struct PatchpointInfo {
...@@ -178,18 +172,18 @@ public: ...@@ -178,18 +172,18 @@ public:
}; };
class ICInfo; class ICInfo;
std::unique_ptr<ICSetupInfo> createGenericIC(TypeRecorder* type_recorder, bool has_return_value, int size); std::unique_ptr<ICSetupInfo> createGenericIC(bool has_return_value, int size);
std::unique_ptr<ICSetupInfo> createCallsiteIC(TypeRecorder* type_recorder, int num_args, ICInfo* bjit_ic_info); std::unique_ptr<ICSetupInfo> createCallsiteIC(int num_args, ICInfo* bjit_ic_info);
std::unique_ptr<ICSetupInfo> createGetGlobalIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createGetGlobalIC();
std::unique_ptr<ICSetupInfo> createGetattrIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info); std::unique_ptr<ICSetupInfo> createGetattrIC(ICInfo* bjit_ic_info);
std::unique_ptr<ICSetupInfo> createSetattrIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info); std::unique_ptr<ICSetupInfo> createSetattrIC(ICInfo* bjit_ic_info);
std::unique_ptr<ICSetupInfo> createDelattrIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createDelattrIC();
std::unique_ptr<ICSetupInfo> createGetitemIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info); std::unique_ptr<ICSetupInfo> createGetitemIC(ICInfo* bjit_ic_info);
std::unique_ptr<ICSetupInfo> createSetitemIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createSetitemIC();
std::unique_ptr<ICSetupInfo> createDelitemIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createDelitemIC();
std::unique_ptr<ICSetupInfo> createBinexpIC(TypeRecorder* type_recorder, ICInfo* bjit_ic_info); std::unique_ptr<ICSetupInfo> createBinexpIC(ICInfo* bjit_ic_info);
std::unique_ptr<ICSetupInfo> createNonzeroIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createNonzeroIC();
std::unique_ptr<ICSetupInfo> createHasnextIC(TypeRecorder* type_recorder); std::unique_ptr<ICSetupInfo> createHasnextIC();
std::unique_ptr<ICSetupInfo> createDeoptIC(); std::unique_ptr<ICSetupInfo> createDeoptIC();
} // namespace pyston } // namespace pyston
......
...@@ -14,21 +14,12 @@ ...@@ -14,21 +14,12 @@
#include "codegen/type_recording.h" #include "codegen/type_recording.h"
#include <unordered_map> #include "asm_writing/icinfo.h"
#include "core/options.h" #include "core/options.h"
#include "core/types.h" #include "core/types.h"
namespace pyston { namespace pyston {
static std::unordered_map<AST*, std::unique_ptr<TypeRecorder>> type_recorders;
TypeRecorder* getTypeRecorderForNode(AST* node) {
std::unique_ptr<TypeRecorder>& r = type_recorders[node];
if (r == NULL)
r = llvm::make_unique<TypeRecorder>();
return r.get();
}
Box* recordType(TypeRecorder* self, Box* obj) { Box* recordType(TypeRecorder* self, Box* obj) {
// The baseline JIT directly generates machine code for this function inside JitFragmentWriter::_emitRecordType. // The baseline JIT directly generates machine code for this function inside JitFragmentWriter::_emitRecordType.
// When changing this function one has to also change the bjit code. // When changing this function one has to also change the bjit code.
...@@ -52,11 +43,11 @@ Box* recordType(TypeRecorder* self, Box* obj) { ...@@ -52,11 +43,11 @@ Box* recordType(TypeRecorder* self, Box* obj) {
} }
BoxedClass* predictClassFor(AST* node) { BoxedClass* predictClassFor(AST* node) {
auto it = type_recorders.find(node); ICInfo* ic = ICInfo::getICInfoForNode(node);
if (it == type_recorders.end()) if (!ic || !ic->getTypeRecorder())
return NULL; return NULL;
return it->second->predict(); return ic->getTypeRecorder()->predict();
} }
BoxedClass* TypeRecorder::predict() { BoxedClass* TypeRecorder::predict() {
......
...@@ -44,8 +44,6 @@ public: ...@@ -44,8 +44,6 @@ public:
friend Box* recordType(TypeRecorder*, Box*); friend Box* recordType(TypeRecorder*, Box*);
}; };
TypeRecorder* getTypeRecorderForNode(AST* node);
BoxedClass* predictClassFor(AST* node); BoxedClass* predictClassFor(AST* node);
} }
......
...@@ -353,6 +353,9 @@ public: ...@@ -353,6 +353,9 @@ public:
std::vector<AST_expr*> args; std::vector<AST_expr*> args;
std::vector<AST_keyword*> keywords; std::vector<AST_keyword*> keywords;
// used during execution stores all keyword names
std::unique_ptr<std::vector<BoxedString*>> keywords_names;
virtual void accept(ASTVisitor* v); virtual void accept(ASTVisitor* v);
virtual void* accept_expr(ExprVisitor* v); virtual void* accept_expr(ExprVisitor* v);
......
...@@ -249,8 +249,7 @@ RuntimeIC::RuntimeIC(void* func_addr, int total_size) { ...@@ -249,8 +249,7 @@ RuntimeIC::RuntimeIC(void* func_addr, int total_size) {
// printf("Allocated runtime IC at %p\n", addr); // printf("Allocated runtime IC at %p\n", addr);
std::unique_ptr<ICSetupInfo> setup_info( std::unique_ptr<ICSetupInfo> setup_info(ICSetupInfo::initialize(true, patchable_size, ICSetupInfo::Generic));
ICSetupInfo::initialize(true, patchable_size, ICSetupInfo::Generic, NULL));
uint8_t* pp_start = (uint8_t*)addr + PROLOGUE_SIZE; uint8_t* pp_start = (uint8_t*)addr + PROLOGUE_SIZE;
uint8_t* pp_end = pp_start + patchable_size + CALL_SIZE; uint8_t* pp_end = pp_start + patchable_size + CALL_SIZE;
......
...@@ -2879,13 +2879,7 @@ template <ExceptionStyle S> Box* _getattrEntry(Box* obj, BoxedString* attr, void ...@@ -2879,13 +2879,7 @@ template <ExceptionStyle S> Box* _getattrEntry(Box* obj, BoxedString* attr, void
rewriter->getArg(0)->setType(RefType::BORROWED); rewriter->getArg(0)->setType(RefType::BORROWED);
rewriter->getArg(1)->setType(RefType::BORROWED); rewriter->getArg(1)->setType(RefType::BORROWED);
Location dest; GetattrRewriteArgs rewrite_args(rewriter.get(), rewriter->getArg(0), rewriter->getReturnDestination());
TypeRecorder* recorder = rewriter->getTypeRecorder();
if (recorder)
dest = Location::forArg(1);
else
dest = rewriter->getReturnDestination();
GetattrRewriteArgs rewrite_args(rewriter.get(), rewriter->getArg(0), dest);
val = getattrInternal<S>(obj, attr, &rewrite_args); val = getattrInternal<S>(obj, attr, &rewrite_args);
if (rewrite_args.isSuccessful()) { if (rewrite_args.isSuccessful()) {
...@@ -2914,15 +2908,8 @@ template <ExceptionStyle S> Box* _getattrEntry(Box* obj, BoxedString* attr, void ...@@ -2914,15 +2908,8 @@ template <ExceptionStyle S> Box* _getattrEntry(Box* obj, BoxedString* attr, void
} }
if (return_convention == ReturnConvention::HAS_RETURN if (return_convention == ReturnConvention::HAS_RETURN
|| (S == CAPI && return_convention == ReturnConvention::CAPI_RETURN)) { || (S == CAPI && return_convention == ReturnConvention::CAPI_RETURN))
if (recorder) {
rtn = rewriter->call(false, (void*)recordType,
rewriter->loadConst((intptr_t)recorder, Location::forArg(0)), rtn);
recordType(recorder, val);
}
rewriter->commitReturning(rtn); rewriter->commitReturning(rtn);
}
} }
} else { } else {
val = getattrInternal<S>(obj, attr); val = getattrInternal<S>(obj, attr);
......
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