this repo has no description

Add CALL_FUNCTION_TYPE_INIT

Bypass `_type_dunder_call` in cases where types do not define a
`__new__` and are not a metaclass. Cache the `__init__` and call it
directly.

Add tests.

authored by bernsteinbear.com and committed by

Max Bernstein aa922d62 34314a86

+174 -7
+2 -1
runtime/bytecode.h
··· 192 192 V(UNUSED_BYTECODE_172, 172, doInvalidBytecode) \ 193 193 V(UNUSED_BYTECODE_173, 173, doInvalidBytecode) \ 194 194 V(UNUSED_BYTECODE_174, 174, doInvalidBytecode) \ 195 - V(UNUSED_BYTECODE_175, 175, doInvalidBytecode) \ 195 + V(CALL_FUNCTION_TYPE_INIT, 175, doCallFunctionTypeInit) \ 196 196 V(CALL_FUNCTION_TYPE_NEW, 176, doCallFunctionTypeNew) \ 197 197 V(CALL_FUNCTION_ANAMORPHIC, 177, doCallFunctionAnamorphic) \ 198 198 V(COMPARE_NE_STR, 178, doCompareNeStr) \ ··· 378 378 case COMPARE_OP_MONOMORPHIC: 379 379 case COMPARE_OP_POLYMORPHIC: 380 380 case COMPARE_OP_ANAMORPHIC: 381 + case CALL_FUNCTION_TYPE_INIT: 381 382 case CALL_FUNCTION_TYPE_NEW: 382 383 case FOR_ITER_MONOMORPHIC: 383 384 case FOR_ITER_POLYMORPHIC:
+3 -5
runtime/ic.cpp
··· 161 161 word id = static_cast<word>(type.instanceLayoutId()); 162 162 caches.atPut(index + kIcEntryKeyOffset, SmallInt::fromWord(id)); 163 163 caches.atPut(index + kIcEntryValueOffset, *constructor); 164 - MutableBytes bytecode(&scope, dependent.rewrittenBytecode()); 165 - word pc = thread->currentFrame()->virtualPC() - kCodeUnitSize; 166 - DCHECK(bytecode.byteAt(pc) == CALL_FUNCTION_ANAMORPHIC, 167 - "current opcode must be CALL_FUNCTION_ANAMORPHIC"); 168 - bytecode.byteAtPut(pc, CALL_FUNCTION_TYPE_NEW); 169 164 if (!type.isBuiltin()) { 170 165 icInsertConstructorDependencies(thread, static_cast<LayoutId>(id), 171 166 dependent); ··· 859 854 case BINARY_SUBSCR_MONOMORPHIC: 860 855 case BINARY_SUBSCR_POLYMORPHIC: 861 856 return attr_name == runtime_->symbols()->at(ID(__getitem__)); 857 + case CALL_FUNCTION_TYPE_INIT: 858 + return attr_name == runtime_->symbols()->at(ID(__new__)) || 859 + attr_name == runtime_->symbols()->at(ID(__init__)); 862 860 case CALL_FUNCTION_TYPE_NEW: 863 861 return attr_name == runtime_->symbols()->at(ID(__new__)) || 864 862 attr_name == runtime_->symbols()->at(ID(__init__));
+1
runtime/ic.h
··· 331 331 case BINARY_SUBSCR_ANAMORPHIC: 332 332 case BINARY_SUBSCR_MONOMORPHIC: 333 333 case BINARY_SUBSCR_POLYMORPHIC: 334 + case CALL_FUNCTION_TYPE_INIT: 334 335 case CALL_FUNCTION_TYPE_NEW: 335 336 case FOR_ITER_MONOMORPHIC: 336 337 case FOR_ITER_POLYMORPHIC:
+97 -1
runtime/interpreter-test.cpp
··· 782 782 HandleScope scope(thread_); 783 783 ASSERT_FALSE(runFromCStr(runtime_, R"( 784 784 class C: 785 + def __new__(cls): 786 + return object.__new__(cls) 785 787 def __init__(self): 786 788 pass 787 789 def foo(fn): ··· 801 803 802 804 // Invalidate cache 803 805 Object new_init(&scope, mainModuleAt(runtime_, "new_init")); 804 - typeAtPutById(thread_, type, ID(__new__), new_init); 806 + typeAtPutById(thread_, type, ID(__init__), new_init); 807 + 808 + // Cache miss 809 + expected = Interpreter::call1(thread_, function, type); 810 + EXPECT_FALSE(expected.isError()); 811 + EXPECT_EQ(expected.layoutId(), type.instanceLayoutId()); 812 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION)); 813 + } 814 + 815 + TEST_F(InterpreterTest, CallFunctionAnamorphicRewritesToCallFunctionTypeInit) { 816 + HandleScope scope(thread_); 817 + ASSERT_FALSE(runFromCStr(runtime_, R"( 818 + class C: 819 + def __init__(self): 820 + pass 821 + def foo(fn): 822 + return fn() 823 + def non_type(): 824 + return 5 825 + )") 826 + .isError()); 827 + Function function(&scope, mainModuleAt(runtime_, "foo")); 828 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC)); 829 + 830 + Type type(&scope, mainModuleAt(runtime_, "C")); 831 + Object expected(&scope, Interpreter::call1(thread_, function, type)); 832 + EXPECT_FALSE(expected.isError()); 833 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_INIT)); 834 + EXPECT_EQ(expected.layoutId(), type.instanceLayoutId()); 835 + 836 + Object non_type(&scope, mainModuleAt(runtime_, "non_type")); 837 + expected = Interpreter::call1(thread_, function, non_type); 838 + EXPECT_FALSE(expected.isError()); 839 + EXPECT_TRUE(isIntEqualsWord(*expected, 5)); 840 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION)); 841 + } 842 + 843 + TEST_F(InterpreterTest, 844 + CallFunctionTypeInitWithNewDunderInitRewritesToCallFunction) { 845 + HandleScope scope(thread_); 846 + ASSERT_FALSE(runFromCStr(runtime_, R"( 847 + class C: 848 + def __init__(self): 849 + pass 850 + def foo(fn): 851 + return fn() 852 + def new_init(self): 853 + pass 854 + )") 855 + .isError()); 856 + Function function(&scope, mainModuleAt(runtime_, "foo")); 857 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC)); 858 + 859 + Type type(&scope, mainModuleAt(runtime_, "C")); 860 + Object expected(&scope, Interpreter::call1(thread_, function, type)); 861 + EXPECT_FALSE(expected.isError()); 862 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_INIT)); 863 + EXPECT_EQ(expected.layoutId(), type.instanceLayoutId()); 864 + 865 + // Invalidate cache 866 + Object new_init(&scope, mainModuleAt(runtime_, "new_init")); 867 + typeAtPutById(thread_, type, ID(__init__), new_init); 868 + 869 + // Cache miss 870 + expected = Interpreter::call1(thread_, function, type); 871 + EXPECT_FALSE(expected.isError()); 872 + EXPECT_EQ(expected.layoutId(), type.instanceLayoutId()); 873 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION)); 874 + } 875 + 876 + TEST_F(InterpreterTest, 877 + CallFunctionTypeInitWithNewDunderNewRewritesToCallFunction) { 878 + HandleScope scope(thread_); 879 + ASSERT_FALSE(runFromCStr(runtime_, R"( 880 + class C: 881 + def __init__(self): 882 + pass 883 + def foo(fn): 884 + return fn() 885 + def new_new(self): 886 + pass 887 + )") 888 + .isError()); 889 + Function function(&scope, mainModuleAt(runtime_, "foo")); 890 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_ANAMORPHIC)); 891 + 892 + Type type(&scope, mainModuleAt(runtime_, "C")); 893 + Object expected(&scope, Interpreter::call1(thread_, function, type)); 894 + EXPECT_FALSE(expected.isError()); 895 + EXPECT_TRUE(containsBytecode(function, CALL_FUNCTION_TYPE_INIT)); 896 + EXPECT_EQ(expected.layoutId(), type.instanceLayoutId()); 897 + 898 + // Invalidate cache 899 + Object new_new(&scope, mainModuleAt(runtime_, "new_new")); 900 + typeAtPutById(thread_, type, ID(__new__), new_new); 805 901 806 902 // Cache miss 807 903 expected = Interpreter::call1(thread_, function, type);
+70
runtime/interpreter.cpp
··· 4580 4580 } 4581 4581 MutableTuple caches(&scope, frame->caches()); 4582 4582 Object ctor(&scope, receiver.ctor()); 4583 + bool set_ctor = false; 4583 4584 if (arg == 1) { 4584 4585 Runtime* runtime = thread->runtime(); 4585 4586 switch (receiver.instanceLayoutId()) { ··· 4587 4588 ctor = runtime->lookupNameInModule(thread, ID(_builtins), 4588 4589 ID(_str_ctor_obj)); 4589 4590 DCHECK(!ctor.isError(), "cannot find _str_ctor_obj"); 4591 + set_ctor = true; 4590 4592 break; 4591 4593 case LayoutId::kType: 4592 4594 ctor = 4593 4595 runtime->lookupNameInModule(thread, ID(_builtins), ID(_type_ctor)); 4594 4596 DCHECK(!ctor.isError(), "cannot find _type_ctor"); 4595 4597 break; 4598 + set_ctor = true; 4596 4599 case LayoutId::kInt: 4597 4600 ctor = runtime->lookupNameInModule(thread, ID(_builtins), 4598 4601 ID(_int_ctor_obj)); 4599 4602 DCHECK(!ctor.isError(), "cannot find _int_ctor_obj"); 4603 + set_ctor = true; 4600 4604 break; 4601 4605 default: 4602 4606 break; 4603 4607 } 4604 4608 } 4609 + if (!set_ctor) { 4610 + // TODO(emacs): Split out objectInit from objectNew and split opcode into 4611 + // slots vs no slots 4612 + // TODO(emacs): Only cache if not abstract (and do not include that in 4613 + // objectInit) 4614 + // TODO(emacs): Also split by tuple overflow/no tuple overflow 4615 + // TODO(emacs): Also cache instanceSize so we can allocate without the 4616 + // layout object 4617 + bool use_object_dunder_new = 4618 + receiver.isType() && receiver.hasFlag(Type::Flag::kHasObjectDunderNew); 4619 + if (use_object_dunder_new) { 4620 + // Metaclass is "type" so we do not need to check for __init__ being a 4621 + // datadescriptor and we can look it up directly on the type. 4622 + ctor = typeLookupInMroById(thread, *receiver, ID(__init__)); 4623 + DCHECK(!ctor.isError(), "self must have __init__"); 4624 + rewriteCurrentBytecode(frame, CALL_FUNCTION_TYPE_INIT); 4625 + icUpdateCallFunctionTypeNew(thread, caches, cache, receiver, ctor, 4626 + dependent); 4627 + return doCallFunctionTypeInit(thread, arg); 4628 + } 4629 + } 4630 + rewriteCurrentBytecode(frame, CALL_FUNCTION_TYPE_NEW); 4605 4631 icUpdateCallFunctionTypeNew(thread, caches, cache, receiver, ctor, dependent); 4606 4632 return doCallFunctionTypeNew(thread, arg); 4607 4633 } ··· 4636 4662 thread->stackSetAt(callable_idx, *ctor); 4637 4663 thread->stackInsertAt(callable_idx, *receiver); 4638 4664 return tailcallFunction(thread, arg + 1, *ctor); 4665 + } 4666 + 4667 + HANDLER_INLINE Continue Interpreter::doCallFunctionTypeInit(Thread* thread, 4668 + word arg) { 4669 + HandleScope scope(thread); 4670 + Frame* frame = thread->currentFrame(); 4671 + word callable_idx = arg; 4672 + Object receiver(&scope, thread->stackPeek(callable_idx)); 4673 + if (!receiver.isType()) { 4674 + EVENT_CACHE(CALL_FUNCTION_TYPE_INIT); 4675 + rewriteCurrentBytecode(frame, CALL_FUNCTION); 4676 + return doCallFunction(thread, arg); 4677 + } 4678 + MutableTuple caches(&scope, frame->caches()); 4679 + word cache = currentCacheIndex(frame); 4680 + bool is_found; 4681 + Object init(&scope, icLookupMonomorphic( 4682 + *caches, cache, 4683 + Type::cast(*receiver).instanceLayoutId(), &is_found)); 4684 + if (!is_found) { 4685 + EVENT_CACHE(CALL_FUNCTION_TYPE_INIT); 4686 + rewriteCurrentBytecode(frame, CALL_FUNCTION); 4687 + return doCallFunction(thread, arg); 4688 + } 4689 + Type type(&scope, *receiver); 4690 + Object instance(&scope, objectNew(thread, type)); 4691 + DCHECK(init.isFunction(), "cached is expected to be a function"); 4692 + thread->stackSetAt(callable_idx, *init); 4693 + thread->stackInsertAt(callable_idx, *instance); 4694 + Object result(&scope, callFunction(thread, arg + 1, *init)); 4695 + // TODO(emacs): When we have real call/ret working in the assembly 4696 + // interpreter, add an asm implementation of this. Right now it does not make 4697 + // much sense to do that because the only way to call an entryAsm is to 4698 + // tailcall it... but we need to do the None check and return the instance. 4699 + if (!result.isNoneType()) { 4700 + if (!result.isErrorException()) { 4701 + Object type_name(&scope, type.name()); 4702 + thread->raiseWithFmt(LayoutId::kTypeError, 4703 + "%S.__init__ returned non None", &type_name); 4704 + } 4705 + return Continue::UNWIND; 4706 + } 4707 + thread->stackPush(*instance); 4708 + return Continue::NEXT; 4639 4709 } 4640 4710 4641 4711 HANDLER_INLINE Continue Interpreter::doMakeFunction(Thread* thread, word arg) {
+1
runtime/interpreter.h
··· 387 387 static Continue doCallMethod(Thread* thread, word arg); 388 388 static Continue doCallFunctionAnamorphic(Thread* thread, word arg); 389 389 static Continue doCallFunctionTypeNew(Thread* thread, word arg); 390 + static Continue doCallFunctionTypeInit(Thread* thread, word arg); 390 391 static Continue doCompareInAnamorphic(Thread* thread, word arg); 391 392 static Continue doCompareInStr(Thread* thread, word arg); 392 393 static Continue doCompareInTuple(Thread* thread, word arg);