Commit 7f50476b authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[builtins] Introduce CallProxy builtin based on CSA

- Add more conformance tests for proxy call and calling undetectable
- This improves the performance of calling a proxy by ~5x

Bug: v8:6558, v8:6557
Change-Id: I5fe78d7ca703cfe86a2a14e39f0b6d88bb8c8e03
Reviewed-on: https://chromium-review.googlesource.com/570023Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Maya Lekova <mslekova@google.com>
Cr-Commit-Position: refs/heads/master@{#46673}
parent 38769995
...@@ -2442,17 +2442,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2442,17 +2442,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ tst(r4, Operand(1 << Map::kIsCallable)); __ tst(r4, Operand(1 << Map::kIsCallable));
__ b(eq, &non_callable); __ b(eq, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ cmp(r5, Operand(JS_PROXY_TYPE)); __ cmp(r5, Operand(JS_PROXY_TYPE));
__ b(ne, &non_function); __ b(ne, &non_function);
// 1. Runtime fallback for Proxy [[Call]]. __ mov(r5, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ Push(r1); __ ldr(r5, MemOperand(r5));
// Increase the arguments size to include the pushed function and the __ add(pc, r5, Operand(Code::kHeaderSize - kHeapObjectTag));
// existing receiver on the stack.
__ add(r0, r0, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2535,17 +2535,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2535,17 +2535,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ Ldrb(x4, FieldMemOperand(x4, Map::kBitFieldOffset)); __ Ldrb(x4, FieldMemOperand(x4, Map::kBitFieldOffset));
__ TestAndBranchIfAllClear(x4, 1 << Map::kIsCallable, &non_callable); __ TestAndBranchIfAllClear(x4, 1 << Map::kIsCallable, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ Cmp(x5, JS_PROXY_TYPE); __ Cmp(x5, JS_PROXY_TYPE);
__ B(ne, &non_function); __ B(ne, &non_function);
// 1. Runtime fallback for Proxy [[Call]]. __ Mov(x5, ExternalReference(Builtins::kCallProxy, masm->isolate()));
__ Push(x1); __ Ldr(x5, MemOperand(x5));
// Increase the arguments size to include the pushed function and the __ Add(x6, x5, Code::kHeaderSize - kHeapObjectTag);
// existing receiver on the stack. __ Br(x6);
__ Add(x0, x0, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -68,6 +68,9 @@ namespace internal { ...@@ -68,6 +68,9 @@ namespace internal {
ASM(Call_ReceiverIsNullOrUndefined) \ ASM(Call_ReceiverIsNullOrUndefined) \
ASM(Call_ReceiverIsNotNullOrUndefined) \ ASM(Call_ReceiverIsNotNullOrUndefined) \
ASM(Call_ReceiverIsAny) \ ASM(Call_ReceiverIsAny) \
\
/* ES6 section 9.5.12[[Call]] ( thisArgument, argumentsList ) */ \
TFC(CallProxy, CallTrampoline, 1) \
ASM(CallVarargs) \ ASM(CallVarargs) \
TFC(CallWithSpread, CallWithSpread, 1) \ TFC(CallWithSpread, CallWithSpread, 1) \
TFC(CallWithArrayLike, CallWithArrayLike, 1) \ TFC(CallWithArrayLike, CallWithArrayLike, 1) \
......
...@@ -51,8 +51,7 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler { ...@@ -51,8 +51,7 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
Node* nativeContext = LoadNativeContext(context); Node* nativeContext = LoadNativeContext(context);
GotoIf(IsCallable(target), &callable_target); Branch(IsCallable(target), &callable_target, &none_target);
Goto(&none_target);
BIND(&callable_target); BIND(&callable_target);
{ {
...@@ -87,6 +86,32 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler { ...@@ -87,6 +86,32 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
return proxy; return proxy;
} }
Node* AllocateJSArrayForCodeStubArguments(Node* context,
CodeStubArguments& args, Node* argc,
ParameterMode mode) {
Node* array = nullptr;
Node* elements = nullptr;
Node* native_context = LoadNativeContext(context);
Node* array_map = LoadJSArrayElementsMap(PACKED_ELEMENTS, native_context);
Node* argc_smi = ParameterToTagged(argc, mode);
std::tie(array, elements) = AllocateUninitializedJSArrayWithElements(
PACKED_ELEMENTS, array_map, argc_smi, nullptr, argc, INTPTR_PARAMETERS);
StoreMapNoWriteBarrier(elements, Heap::kFixedArrayMapRootIndex);
StoreObjectFieldNoWriteBarrier(elements, FixedArrayBase::kLengthOffset,
argc_smi);
VARIABLE(index, MachineType::PointerRepresentation());
index.Bind(IntPtrConstant(FixedArrayBase::kHeaderSize - kHeapObjectTag));
VariableList list({&index}, zone());
args.ForEach(list, [this, elements, &index](Node* arg) {
StoreNoWriteBarrier(MachineRepresentation::kTagged, elements,
index.value(), arg);
Increment(index, kPointerSize);
});
return array;
}
}; };
// ES6 section 26.2.1.1 Proxy ( target, handler ) for the [[Construct]] case. // ES6 section 26.2.1.1 Proxy ( target, handler ) for the [[Construct]] case.
...@@ -123,5 +148,68 @@ TF_BUILTIN(ProxyConstructor_ConstructStub, ProxiesCodeStubAssembler) { ...@@ -123,5 +148,68 @@ TF_BUILTIN(ProxyConstructor_ConstructStub, ProxiesCodeStubAssembler) {
ThrowTypeError(context, MessageTemplate::kProxyHandlerOrTargetRevoked); ThrowTypeError(context, MessageTemplate::kProxyHandlerOrTargetRevoked);
} }
TF_BUILTIN(CallProxy, ProxiesCodeStubAssembler) {
Node* argc = Parameter(Descriptor::kActualArgumentsCount);
Node* argc_ptr = ChangeInt32ToIntPtr(argc);
Node* proxy = Parameter(Descriptor::kFunction);
Node* context = Parameter(Descriptor::kContext);
CSA_ASSERT(this, IsJSProxy(proxy));
CSA_ASSERT(this, IsCallable(proxy));
Label throw_proxy_handler_revoked(this, Label::kDeferred),
trap_undefined(this), trap_defined(this, Label::kDeferred);
// 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset);
// 2. If handler is null, throw a TypeError exception.
CSA_ASSERT(this, Word32Or(IsJSReceiver(handler), IsNull(handler)));
GotoIf(IsNull(handler), &throw_proxy_handler_revoked);
// 3. Assert: Type(handler) is Object.
CSA_ASSERT(this, IsJSReceiver(handler));
// 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset);
// 5. Let trap be ? GetMethod(handler, "apply").
Handle<Name> trap_name = factory()->apply_string();
Node* trap = GetProperty(context, handler, trap_name);
// 6. If trap is undefined, then
GotoIf(IsUndefined(trap), &trap_undefined);
Branch(IsNull(trap), &trap_undefined, &trap_defined);
BIND(&trap_defined);
{
CodeStubArguments args(this, argc_ptr);
Node* receiver = args.GetReceiver();
// 7. Let argArray be CreateArrayFromList(argumentsList).
Node* array = AllocateJSArrayForCodeStubArguments(context, args, argc_ptr,
INTPTR_PARAMETERS);
// 8. Return Call(trap, handler, «target, thisArgument, argArray»).
Node* result = CallJS(CodeFactory::Call(isolate()), context, trap, handler,
target, receiver, array);
args.PopAndReturn(result);
}
BIND(&trap_undefined);
{
// 6.a. Return Call(target, thisArgument, argumentsList).
TailCallStub(CodeFactory::Call(isolate()), context, target, argc);
}
BIND(&throw_proxy_handler_revoked);
{
CallRuntime(Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kProxyRevoked),
StringConstant("apply"));
Unreachable();
}
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -2612,24 +2612,19 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2612,24 +2612,19 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ j(equal, masm->isolate()->builtins()->CallBoundFunction(), __ j(equal, masm->isolate()->builtins()->CallBoundFunction(),
RelocInfo::CODE_TARGET); RelocInfo::CODE_TARGET);
// Check if target has a [[Call]] internal method. // Check if target is a proxy and call CallProxy external builtin
__ test_b(FieldOperand(ecx, Map::kBitFieldOffset), __ test_b(FieldOperand(ecx, Map::kBitFieldOffset),
Immediate(1 << Map::kIsCallable)); Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable); __ j(zero, &non_callable);
// Call CallProxy external builtin
__ CmpInstanceType(ecx, JS_PROXY_TYPE); __ CmpInstanceType(ecx, JS_PROXY_TYPE);
__ j(not_equal, &non_function); __ j(not_equal, &non_function);
// 1. Runtime fallback for Proxy [[Call]]. __ mov(ecx, Operand::StaticVariable(
__ PopReturnAddressTo(ecx); ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ Push(edi); __ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
__ PushReturnAddressFrom(ecx); __ jmp(ecx);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ add(eax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2427,16 +2427,11 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2427,16 +2427,11 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ And(t1, t1, Operand(1 << Map::kIsCallable)); __ And(t1, t1, Operand(1 << Map::kIsCallable));
__ Branch(&non_callable, eq, t1, Operand(zero_reg)); __ Branch(&non_callable, eq, t1, Operand(zero_reg));
// Check if target is a proxy and call CallProxy external builtin
__ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE)); __ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE));
__ li(t2, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
// 1. Runtime fallback for Proxy [[Call]]. __ lw(t2, MemOperand(t2));
__ Push(a1); __ Jump(t2, Operand(Code::kHeaderSize - kHeapObjectTag));
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ Addu(a0, a0, 2);
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2452,15 +2452,10 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2452,15 +2452,10 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
__ Branch(&non_callable, eq, t1, Operand(zero_reg)); __ Branch(&non_callable, eq, t1, Operand(zero_reg));
__ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE)); __ Branch(&non_function, ne, t2, Operand(JS_PROXY_TYPE));
__ li(t2, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
// 1. Runtime fallback for Proxy [[Call]]. __ Ld(t2, MemOperand(t2));
__ Push(a1); __ Daddu(t2, t2, Operand(Code::kHeaderSize - kHeapObjectTag));
// Increase the arguments size to include the pushed function and the __ Jump(t2);
// existing receiver on the stack.
__ Daddu(a0, a0, 2);
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2633,22 +2633,12 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode, ...@@ -2633,22 +2633,12 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode,
__ TestBit(r7, Map::kIsCallable, r0); __ TestBit(r7, Map::kIsCallable, r0);
__ beq(&non_callable, cr0); __ beq(&non_callable, cr0);
// Check if target is a proxy and call CallProxy external builtin
__ cmpi(r8, Operand(JS_PROXY_TYPE)); __ cmpi(r8, Operand(JS_PROXY_TYPE));
__ bne(&non_function); __ mov(r8, Operand(ExternalReference(Builtins::kCallProxy, masm->isolate())));
__ LoadP(r8, MemOperand(r8));
// 0. Prepare for tail call if necessary. __ addi(r8, r8, Code::kHeaderSize - kHeapObjectTag);
if (tail_call_mode == TailCallMode::kAllow) { __ JumpToJSEntry(r8);
PrepareForTailCall(masm, r3, r6, r7, r8);
}
// 1. Runtime fallback for Proxy [[Call]].
__ Push(r4);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ addi(r3, r3, Operand(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2712,19 +2712,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) { ...@@ -2712,19 +2712,13 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode) {
Immediate(1 << Map::kIsCallable)); Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable); __ j(zero, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ CmpInstanceType(rcx, JS_PROXY_TYPE); __ CmpInstanceType(rcx, JS_PROXY_TYPE);
__ j(not_equal, &non_function); __ j(not_equal, &non_function);
// 1. Runtime fallback for Proxy [[Call]]. __ Load(rcx, ExternalReference(Builtins::kCallProxy, masm->isolate()));
__ PopReturnAddressTo(kScratchRegister); __ leap(rcx, FieldOperand(rcx, Code::kHeaderSize));
__ Push(rdi); __ jmp(rcx);
__ PushReturnAddressFrom(kScratchRegister);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ addp(rax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -2568,24 +2568,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode, ...@@ -2568,24 +2568,14 @@ void Builtins::Generate_Call(MacroAssembler* masm, ConvertReceiverMode mode,
Immediate(1 << Map::kIsCallable)); Immediate(1 << Map::kIsCallable));
__ j(zero, &non_callable); __ j(zero, &non_callable);
// Check if target is a proxy and call CallProxy external builtin
__ CmpInstanceType(ecx, JS_PROXY_TYPE); __ CmpInstanceType(ecx, JS_PROXY_TYPE);
__ j(not_equal, &non_function); __ j(not_equal, &non_function);
// 0. Prepare for tail call if necessary. __ mov(ecx, Operand::StaticVariable(
if (tail_call_mode == TailCallMode::kAllow) { ExternalReference(Builtins::kCallProxy, masm->isolate())));
PrepareForTailCall(masm, eax, ebx, ecx, edx); __ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
} __ jmp(ecx);
// 1. Runtime fallback for Proxy [[Call]].
__ PopReturnAddressTo(ecx);
__ Push(edi);
__ PushReturnAddressFrom(ecx);
// Increase the arguments size to include the pushed function and the
// existing receiver on the stack.
__ add(eax, Immediate(2));
// Tail-call to the runtime.
__ JumpToExternalReference(
ExternalReference(Runtime::kJSProxyCall, masm->isolate()));
// 2. Call to something else, which might have a [[Call]] internal method (if // 2. Call to something else, which might have a [[Call]] internal method (if
// not we raise an exception). // not we raise an exception).
......
...@@ -370,9 +370,14 @@ void ExternalReferenceTable::AddBuiltins(Isolate* isolate) { ...@@ -370,9 +370,14 @@ void ExternalReferenceTable::AddBuiltins(Isolate* isolate) {
const char* name; const char* name;
}; };
static const BuiltinEntry builtins[] = { static const BuiltinEntry builtins[] = {
#define BUILTIN_LIST_EXTERNAL_REFS(DEF) \
BUILTIN_LIST_C(DEF) \
BUILTIN_LIST_A(DEF) \
DEF(CallProxy)
#define DEF_ENTRY(Name, ...) {Builtins::k##Name, "Builtin_" #Name}, #define DEF_ENTRY(Name, ...) {Builtins::k##Name, "Builtin_" #Name},
BUILTIN_LIST_C(DEF_ENTRY) BUILTIN_LIST_A(DEF_ENTRY) BUILTIN_LIST_EXTERNAL_REFS(DEF_ENTRY)
#undef DEF_ENTRY #undef DEF_ENTRY
#undef BUILTIN_LIST_EXTERNAL_REFS
}; };
for (unsigned i = 0; i < arraysize(builtins); ++i) { for (unsigned i = 0; i < arraysize(builtins); ++i) {
Add(isolate->builtins()->builtin_address(builtins[i].id), builtins[i].name); Add(isolate->builtins()->builtin_address(builtins[i].id), builtins[i].name);
......
...@@ -86,6 +86,133 @@ ...@@ -86,6 +86,133 @@
assertTrue(called_handler); assertTrue(called_handler);
})(); })();
(function testCallProxyTrapArrayArg() {
var called_target = false;
var called_handler = false;
var target = function(a, b) {
called_target = true;
assertArrayEquals([1, 2], a);
assertEquals(3, b);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy([1,2], 3);
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testCallProxyTrapObjectArg() {
var called_target = false;
var called_handler = false;
var target = function(o) {
called_target = true;
assertEquals({a: 1, b: 2}, o);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy({a: 1, b: 2});
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testCallProxyTrapGeneratorArg() {
function* gen() {
yield 1;
yield 2;
yield 3;
}
var called_target = false;
var called_handler = false;
var target = function(g) {
called_target = true;
assertArrayEquals([1,2,3], [...g]);
}
var handler = {
apply: function(target, this_arg, args) {
target.apply(this_arg, args);
called_handler = true;
}
}
var proxy = new Proxy(target, handler);
assertFalse(called_target);
assertFalse(called_handler);
proxy(gen());
assertTrue(called_target);
assertTrue(called_handler);
})();
(function testProxyTrapContext() {
var _target, _args, _handler, _context;
var target = function(a, b) { return a + b; };
var handler = {
apply: function(t, c, args) {
_handler = this;
_target = t;
_context = c;
_args = args;
}
};
var proxy = new Proxy(target, handler);
var context = {};
proxy.call(context, 1, 2);
assertEquals(_handler, handler);
assertEquals(_target, target);
assertEquals(_context, context);
assertEquals(_args.length, 2);
assertEquals(_args[0], 1);
assertEquals(_args[1], 2);
})();
(function testCallProxyNonCallableTrap() {
var called_target = false;
var target = function() {
called_target = true;
};
var handler = {
apply: 'non callable trap'
};
var proxy = new Proxy(target, handler);
assertThrows(function(){ proxy() }, TypeError);
assertFalse(called_target);
})();
(function testCallProxyNullTrap() {
var _args;
var target = function(a, b) {
_args = [a, b];
return a + b;
};
var handler = {
apply: null
};
var proxy = new Proxy(target, handler);
var result = proxy(1, 2);
assertEquals(result, 3);
assertEquals(_args.length, 2);
assertEquals(_args[0], 1);
assertEquals(_args[1], 2);
})();
(function testCallProxyNonCallableTarget() { (function testCallProxyNonCallableTarget() {
var values = [NaN, 1.5, 100, /RegExp/, "string", {}, [], Symbol(), var values = [NaN, 1.5, 100, /RegExp/, "string", {}, [], Symbol(),
......
...@@ -72,16 +72,22 @@ function testFors() { ...@@ -72,16 +72,22 @@ function testFors() {
assertEquals(1, j); assertEquals(1, j);
} }
function testCall() {
obj();
}
for (var j = 0; j < 5; j++) { for (var j = 0; j < 5; j++) {
testCompares(); testCompares();
testIfs(); testIfs();
testWhiles(); testWhiles();
testFors(); testFors();
testCall();
if (j == 3) { if (j == 3) {
%OptimizeFunctionOnNextCall(testCompares); %OptimizeFunctionOnNextCall(testCompares);
%OptimizeFunctionOnNextCall(testIfs); %OptimizeFunctionOnNextCall(testIfs);
%OptimizeFunctionOnNextCall(testWhiles); %OptimizeFunctionOnNextCall(testWhiles);
%OptimizeFunctionOnNextCall(testFors); %OptimizeFunctionOnNextCall(testFors);
%OptimizeFunctionOnNextCall(testCall);
} }
} }
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