From 063b7c4ebb55ced5f7153019ad9da777c04e8ed0 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Fri, 8 Nov 2013 13:10:39 +0000 Subject: [PATCH] Implement Math.sin, cos and tan using table lookup and spline interpolation. R=jkummerow@chromium.org BUG= Review URL: https://codereview.chromium.org/50563003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17594 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 46 ++++------------- src/hydrogen.cc | 52 ++++++------------- src/ia32/full-codegen-ia32.cc | 46 ++++------------- src/math.js | 72 +++++++++++++++++++++++++-- src/mips/full-codegen-mips.cc | 49 ++++-------------- src/objects-inl.h | 3 ++ src/objects.cc | 24 ++++----- src/objects.h | 10 ++-- src/runtime.cc | 43 ++++++++++++++++ src/runtime.h | 6 +-- src/x64/full-codegen-x64.cc | 46 ++++------------- test/mjsunit/sin-cos.js | 94 ++++++++++++++++++++++++++++++++++- 12 files changed, 283 insertions(+), 208 deletions(-) diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 8fb1e15585..5b6a35d12b 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -3726,42 +3726,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) { } -void FullCodeGenerator::EmitMathSin(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::SIN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(r0); -} - - -void FullCodeGenerator::EmitMathCos(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::COS, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(r0); -} - - -void FullCodeGenerator::EmitMathTan(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::TAN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(r0); -} - - void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { // Load the argument on the stack and call the stub. TranscendentalCacheStub stub(TranscendentalCache::LOG, @@ -3784,6 +3748,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) { } +void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) { + // Load the argument on the stack and call the runtime function. + ZoneList* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallRuntime(Runtime::kMath_floor, 1); + context()->Plug(r0); +} + + void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) { ZoneList* args = expr->arguments(); ASSERT(args->length() >= 2); diff --git a/src/hydrogen.cc b/src/hydrogen.cc index 19db3f1e46..dead4649a4 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -6353,6 +6353,11 @@ int HOptimizedGraphBuilder::InliningAstSize(Handle target) { Handle caller = current_info()->closure(); Handle target_shared(target->shared()); + // Always inline builtins marked for inlining. + if (target->IsBuiltin()) { + return target_shared->inline_builtin() ? 0 : kNotInlinable; + } + // Do a quick check on source code length to avoid parsing large // inlining candidates. if (target_shared->SourceSize() > @@ -6362,7 +6367,7 @@ int HOptimizedGraphBuilder::InliningAstSize(Handle target) { } // Target must be inlineable. - if (!target->IsInlineable()) { + if (!target_shared->IsInlineable()) { TraceInline(target, caller, "target not inlineable"); return kNotInlinable; } @@ -6742,9 +6747,6 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr, case kMathAbs: case kMathSqrt: case kMathLog: - case kMathSin: - case kMathCos: - case kMathTan: if (expr->arguments()->length() == 1) { HValue* argument = Pop(); Drop(1); // Receiver. @@ -6823,9 +6825,6 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall( case kMathAbs: case kMathSqrt: case kMathLog: - case kMathSin: - case kMathCos: - case kMathTan: if (argument_count == 2 && check_type == RECEIVER_MAP_CHECK) { AddCheckConstantFunction(expr->holder(), receiver, receiver_map); HValue* argument = Pop(); @@ -9145,36 +9144,6 @@ void HOptimizedGraphBuilder::GenerateMathPow(CallRuntime* call) { } -void HOptimizedGraphBuilder::GenerateMathSin(CallRuntime* call) { - ASSERT_EQ(1, call->arguments()->length()); - CHECK_ALIVE(VisitArgumentList(call->arguments())); - HCallStub* result = New(CodeStub::TranscendentalCache, 1); - result->set_transcendental_type(TranscendentalCache::SIN); - Drop(1); - return ast_context()->ReturnInstruction(result, call->id()); -} - - -void HOptimizedGraphBuilder::GenerateMathCos(CallRuntime* call) { - ASSERT_EQ(1, call->arguments()->length()); - CHECK_ALIVE(VisitArgumentList(call->arguments())); - HCallStub* result = New(CodeStub::TranscendentalCache, 1); - result->set_transcendental_type(TranscendentalCache::COS); - Drop(1); - return ast_context()->ReturnInstruction(result, call->id()); -} - - -void HOptimizedGraphBuilder::GenerateMathTan(CallRuntime* call) { - ASSERT_EQ(1, call->arguments()->length()); - CHECK_ALIVE(VisitArgumentList(call->arguments())); - HCallStub* result = New(CodeStub::TranscendentalCache, 1); - result->set_transcendental_type(TranscendentalCache::TAN); - Drop(1); - return ast_context()->ReturnInstruction(result, call->id()); -} - - void HOptimizedGraphBuilder::GenerateMathLog(CallRuntime* call) { ASSERT_EQ(1, call->arguments()->length()); CHECK_ALIVE(VisitArgumentList(call->arguments())); @@ -9194,6 +9163,15 @@ void HOptimizedGraphBuilder::GenerateMathSqrt(CallRuntime* call) { } +void HOptimizedGraphBuilder::GenerateMathFloor(CallRuntime* call) { + ASSERT(call->arguments()->length() == 1); + CHECK_ALIVE(VisitForValue(call->arguments()->at(0))); + HValue* value = Pop(); + HInstruction* result = New(value, kMathFloor); + return ast_context()->ReturnInstruction(result, call->id()); +} + + // Check whether two RegExps are equivalent void HOptimizedGraphBuilder::GenerateIsRegExpEquivalent(CallRuntime* call) { return Bailout(kInlinedRuntimeFunctionIsRegExpEquivalent); diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index b7ddeac0e4..61bf6d0dea 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -3693,42 +3693,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) { } -void FullCodeGenerator::EmitMathSin(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::SIN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(eax); -} - - -void FullCodeGenerator::EmitMathCos(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::COS, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(eax); -} - - -void FullCodeGenerator::EmitMathTan(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::TAN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(eax); -} - - void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { // Load the argument on the stack and call the stub. TranscendentalCacheStub stub(TranscendentalCache::LOG, @@ -3751,6 +3715,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) { } +void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) { + // Load the argument on the stack and call the runtime function. + ZoneList* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallRuntime(Runtime::kMath_floor, 1); + context()->Plug(eax); +} + + void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) { ZoneList* args = expr->arguments(); ASSERT(args->length() >= 2); diff --git a/src/math.js b/src/math.js index efab63a186..1f7e327582 100644 --- a/src/math.js +++ b/src/math.js @@ -79,7 +79,7 @@ function MathCeil(x) { // ECMA 262 - 15.8.2.7 function MathCos(x) { - return %_MathCos(TO_NUMBER_INLINE(x)); + return MathCosImpl(x); } // ECMA 262 - 15.8.2.8 @@ -185,7 +185,7 @@ function MathRound(x) { // ECMA 262 - 15.8.2.16 function MathSin(x) { - return %_MathSin(TO_NUMBER_INLINE(x)); + return MathSinImpl(x); } // ECMA 262 - 15.8.2.17 @@ -195,7 +195,7 @@ function MathSqrt(x) { // ECMA 262 - 15.8.2.18 function MathTan(x) { - return %_MathTan(TO_NUMBER_INLINE(x)); + return MathSinImpl(x) / MathCosImpl(x); } // Non-standard extension. @@ -204,6 +204,68 @@ function MathImul(x, y) { } +var MathSinImpl = function(x) { + InitTrigonometricFunctions(); + return MathSinImpl(x); +} + + +var MathCosImpl = function(x) { + InitTrigonometricFunctions(); + return MathCosImpl(x); +} + + +function InitTrigonometricFunctions() { + var samples = 2048; // Table size. + var pi = 3.1415926535897932; + var pi_half = pi / 2; + var inverse_pi_half = 1 / pi_half; + var two_pi = pi * 2; + var interval = pi_half / samples; + var inverse_interval = samples / pi_half; + var table_sin = new global.Float64Array(samples + 2); + var table_cos_interval = new global.Float64Array(samples + 2); + + %PopulateTrigonometricTable(table_sin, table_cos_interval, samples); + + // This implements the following algorithm. + // 1) Multiplication takes care of to-number conversion. + // 2) Reduce x to the first quadrant [0, pi/2]. + // Conveniently enough, in case of +/-Infinity, we get NaN. + // 3) Replace x by (pi/2-x) if x was in the 2nd or 4th quadrant. + // 4) Do a table lookup for the closest samples to the left and right of x. + // 5) Find the derivatives at those sampling points by table lookup: + // dsin(x)/dx = cos(x) = sin(pi/2-x) for x in [0, pi/2]. + // 6) Use cubic spline interpolation to approximate sin(x). + // 7) Negate the result if x was in the 3rd or 4th quadrant. + // 8) Get rid of -0 by adding 0. + MathSinImpl = function(x) { + var multiple = %_MathFloor(x * inverse_pi_half); + x = (multiple & 1) * pi_half + + (1 - ((multiple & 1) << 1)) * (x - multiple * pi_half); + var double_index = x * inverse_interval; + var index = double_index | 0; + var t1 = double_index - index; + var t2 = 1 - t1; + var y1 = table_sin[index]; + var y2 = table_sin[index + 1]; + var dy = y2 - y1; + return (t2 * y1 + t1 * y2 + + t1 * t2 * ((table_cos_interval[index] - dy) * t2 + + (dy - table_cos_interval[index + 1]) * t1)) * + (1 - (multiple & 2)) + 0; + }; + + MathCosImpl = function(x) { + return MathSinImpl(x + pi_half); + }; + + %SetInlineBuiltinFlag(MathSinImpl); + %SetInlineBuiltinFlag(MathCosImpl); +} + + // ------------------------------------------------------------------- function SetUpMath() { @@ -276,6 +338,10 @@ function SetUpMath() { "min", MathMin, "imul", MathImul )); + + %SetInlineBuiltinFlag(MathSin); + %SetInlineBuiltinFlag(MathCos); + %SetInlineBuiltinFlag(MathTan); } SetUpMath(); diff --git a/src/mips/full-codegen-mips.cc b/src/mips/full-codegen-mips.cc index caa7352eae..38334d50dd 100644 --- a/src/mips/full-codegen-mips.cc +++ b/src/mips/full-codegen-mips.cc @@ -3762,45 +3762,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) { } -void FullCodeGenerator::EmitMathSin(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::SIN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ mov(a0, result_register()); // Stub requires parameter in a0 and on tos. - __ CallStub(&stub); - context()->Plug(v0); -} - - -void FullCodeGenerator::EmitMathCos(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::COS, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ mov(a0, result_register()); // Stub requires parameter in a0 and on tos. - __ CallStub(&stub); - context()->Plug(v0); -} - - -void FullCodeGenerator::EmitMathTan(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::TAN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ mov(a0, result_register()); // Stub requires parameter in a0 and on tos. - __ CallStub(&stub); - context()->Plug(v0); -} - - void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { // Load the argument on the stack and call the stub. TranscendentalCacheStub stub(TranscendentalCache::LOG, @@ -3824,6 +3785,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) { } +void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) { + // Load the argument on the stack and call the runtime function. + ZoneList* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallRuntime(Runtime::kMath_floor, 1); + context()->Plug(v0); +} + + void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) { ZoneList* args = expr->arguments(); ASSERT(args->length() >= 2); diff --git a/src/objects-inl.h b/src/objects-inl.h index bef807eaf6..92f52b3d8a 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -4807,6 +4807,8 @@ bool SharedFunctionInfo::is_classic_mode() { BOOL_GETTER(SharedFunctionInfo, compiler_hints, is_extended_mode, kExtendedModeFunction) BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, native, kNative) +BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, inline_builtin, + kInlineBuiltin) BOOL_ACCESSORS(SharedFunctionInfo, compiler_hints, name_should_print_as_anonymous, kNameShouldPrintAsAnonymous) @@ -4867,6 +4869,7 @@ Code* SharedFunctionInfo::code() { void SharedFunctionInfo::set_code(Code* value, WriteBarrierMode mode) { + ASSERT(value->kind() != Code::OPTIMIZED_FUNCTION); WRITE_FIELD(this, kCodeOffset, value); CONDITIONAL_WRITE_BARRIER(value->GetHeap(), this, kCodeOffset, value, mode); } diff --git a/src/objects.cc b/src/objects.cc index 441c25e70c..703f9aaf9c 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -9747,20 +9747,6 @@ bool JSFunction::EnsureCompiled(Handle function, } -bool JSFunction::IsInlineable() { - if (IsBuiltin()) return false; - SharedFunctionInfo* shared_info = shared(); - // Check that the function has a script associated with it. - if (!shared_info->script()->IsScript()) return false; - if (shared_info->optimization_disabled()) return false; - Code* code = shared_info->code(); - if (code->kind() == Code::OPTIMIZED_FUNCTION) return true; - // If we never ran this (unlikely) then lets try to optimize it. - if (code->kind() != Code::FUNCTION) return true; - return code->optimizable(); -} - - void JSObject::OptimizeAsPrototype(Handle object) { if (object->IsGlobalObject()) return; @@ -10029,6 +10015,16 @@ Handle SharedFunctionInfo::GetSourceCode() { } +bool SharedFunctionInfo::IsInlineable() { + // Check that the function has a script associated with it. + if (!script()->IsScript()) return false; + if (optimization_disabled()) return false; + // If we never ran this (unlikely) then lets try to optimize it. + if (code()->kind() != Code::FUNCTION) return true; + return code()->optimizable(); +} + + int SharedFunctionInfo::SourceSize() { return end_position() - start_position(); } diff --git a/src/objects.h b/src/objects.h index 3105579e28..f1c6f04d26 100644 --- a/src/objects.h +++ b/src/objects.h @@ -6781,6 +6781,9 @@ class SharedFunctionInfo: public HeapObject { // global object. DECL_BOOLEAN_ACCESSORS(native) + // Indicate that this builtin needs to be inlined in crankshaft. + DECL_BOOLEAN_ACCESSORS(inline_builtin) + // Indicates that the function was created by the Function function. // Though it's anonymous, toString should treat it as if it had the name // "anonymous". We don't set the name itself so that the system does not @@ -6870,6 +6873,9 @@ class SharedFunctionInfo: public HeapObject { set_dont_optimize(reason != kNoReason); } + // Check whether or not this function is inlineable. + bool IsInlineable(); + // Source size of this function. int SourceSize(); @@ -7020,6 +7026,7 @@ class SharedFunctionInfo: public HeapObject { kUsesArguments, kHasDuplicateParameters, kNative, + kInlineBuiltin, kBoundFunction, kIsAnonymous, kNameShouldPrintAsAnonymous, @@ -7246,9 +7253,6 @@ class JSFunction: public JSObject { // Tells whether or not the function is on the concurrent recompilation queue. inline bool IsInRecompileQueue(); - // Check whether or not this function is inlineable. - bool IsInlineable(); - // [literals_or_bindings]: Fixed array holding either // the materialized literals or the bindings of a bound function. // diff --git a/src/runtime.cc b/src/runtime.cc index c9f152f9da..4b28a3f2b2 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -5379,6 +5379,20 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetNativeFlag) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_SetInlineBuiltinFlag) { + SealHandleScope shs(isolate); + RUNTIME_ASSERT(args.length() == 1); + + Handle object = args.at(0); + + if (object->IsJSFunction()) { + JSFunction* func = JSFunction::cast(*object); + func->shared()->set_inline_builtin(true); + } + return isolate->heap()->undefined_value(); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_StoreArrayLiteralElement) { HandleScope scope(isolate); RUNTIME_ASSERT(args.length() == 5); @@ -7802,6 +7816,35 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_Math_tan) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_PopulateTrigonometricTable) { + HandleScope scope(isolate); + ASSERT(args.length() == 3); + CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sin_table, 0); + CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, cos_table, 1); + CONVERT_SMI_ARG_CHECKED(samples, 2); + RUNTIME_ASSERT(sin_table->type() == kExternalDoubleArray); + RUNTIME_ASSERT(cos_table->type() == kExternalDoubleArray); + double* sin_buffer = reinterpret_cast( + JSArrayBuffer::cast(sin_table->buffer())->backing_store()); + double* cos_buffer = reinterpret_cast( + JSArrayBuffer::cast(cos_table->buffer())->backing_store()); + + static const double pi_half = 3.1415926535897932 / 2; + double interval = pi_half / samples; + for (int i = 0; i < samples + 1; i++) { + double sample = sin(i * interval); + sin_buffer[i] = sample; + cos_buffer[samples - i] = sample * interval; + } + + // Fill this to catch out of bound accesses when calculating Math.sin(pi/2). + sin_buffer[samples + 1] = sin(pi_half + interval); + cos_buffer[samples + 1] = cos(pi_half + interval) * interval; + + return isolate->heap()->undefined_value(); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_DateMakeDay) { SealHandleScope shs(isolate); ASSERT(args.length() == 2); diff --git a/src/runtime.h b/src/runtime.h index c316d4098f..5d2e99249c 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -106,6 +106,7 @@ namespace internal { F(AllocateInOldPointerSpace, 1, 1) \ F(AllocateInOldDataSpace, 1, 1) \ F(SetNativeFlag, 1, 1) \ + F(SetInlineBuiltinFlag, 1, 1) \ F(StoreArrayLiteralElement, 5, 1) \ F(DebugCallbackSupportsStepping, 1, 1) \ F(DebugPrepareStepInIfStepping, 1, 1) \ @@ -189,6 +190,7 @@ namespace internal { F(Math_sin, 1, 1) \ F(Math_sqrt, 1, 1) \ F(Math_tan, 1, 1) \ + F(PopulateTrigonometricTable, 3, 1) \ \ /* Regular expressions */ \ F(RegExpCompile, 3, 1) \ @@ -625,11 +627,9 @@ namespace internal { F(IsSpecObject, 1, 1) \ F(IsStringWrapperSafeForDefaultValueOf, 1, 1) \ F(MathPow, 2, 1) \ - F(MathSin, 1, 1) \ - F(MathCos, 1, 1) \ - F(MathTan, 1, 1) \ F(MathSqrt, 1, 1) \ F(MathLog, 1, 1) \ + F(MathFloor, 1, 1) \ F(IsRegExpEquivalent, 2, 1) \ F(HasCachedArrayIndex, 1, 1) \ F(GetCachedArrayIndex, 1, 1) \ diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index cc0d3d9f37..21451fc746 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -3650,42 +3650,6 @@ void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) { } -void FullCodeGenerator::EmitMathSin(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::SIN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(rax); -} - - -void FullCodeGenerator::EmitMathCos(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::COS, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(rax); -} - - -void FullCodeGenerator::EmitMathTan(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::TAN, - TranscendentalCacheStub::TAGGED); - ZoneList* args = expr->arguments(); - ASSERT(args->length() == 1); - VisitForStackValue(args->at(0)); - __ CallStub(&stub); - context()->Plug(rax); -} - - void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { // Load the argument on the stack and call the stub. TranscendentalCacheStub stub(TranscendentalCache::LOG, @@ -3708,6 +3672,16 @@ void FullCodeGenerator::EmitMathSqrt(CallRuntime* expr) { } +void FullCodeGenerator::EmitMathFloor(CallRuntime* expr) { + // Load the argument on the stack and call the runtime function. + ZoneList* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallRuntime(Runtime::kMath_floor, 1); + context()->Plug(rax); +} + + void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) { ZoneList* args = expr->arguments(); ASSERT(args->length() >= 2); diff --git a/test/mjsunit/sin-cos.js b/test/mjsunit/sin-cos.js index e38dfdf814..b893cce936 100644 --- a/test/mjsunit/sin-cos.js +++ b/test/mjsunit/sin-cos.js @@ -42,9 +42,101 @@ cosTest(); // By accident, the slow case for sine and cosine were both sine at // some point. This is a regression test for that issue. -var x = Math.pow(2, 70); +var x = Math.pow(2, 30); assertTrue(Math.sin(x) != Math.cos(x)); // Ensure that sine and log are not the same. x = 0.5; assertTrue(Math.sin(x) != Math.log(x)); + +// Test against approximation by series. +var factorial = [1]; +var accuracy = 50; +for (var i = 1; i < accuracy; i++) { + factorial[i] = factorial[i-1] * i; +} + +// We sum up in the reverse order for higher precision, as we expect the terms +// to grow smaller for x reasonably close to 0. +function precision_sum(array) { + var result = 0; + while (array.length > 0) { + result += array.pop(); + } + return result; +} + +function sin(x) { + var sign = 1; + var x2 = x*x; + var terms = []; + for (var i = 1; i < accuracy; i += 2) { + terms.push(sign * x / factorial[i]); + x *= x2; + sign *= -1; + } + return precision_sum(terms); +} + +function cos(x) { + var sign = -1; + var x2 = x*x; + x = x2; + var terms = [1]; + for (var i = 2; i < accuracy; i += 2) { + terms.push(sign * x / factorial[i]); + x *= x2; + sign *= -1; + } + return precision_sum(terms); +} + +function abs_error(fun, ref, x) { + return Math.abs(ref(x) - fun(x)); +} + +var test_inputs = []; +for (var i = -10000; i < 10000; i += 177) test_inputs.push(i/1257); +var epsilon = 0.000001; + +test_inputs.push(0); +test_inputs.push(0 + epsilon); +test_inputs.push(0 - epsilon); +test_inputs.push(Math.PI/2); +test_inputs.push(Math.PI/2 + epsilon); +test_inputs.push(Math.PI/2 - epsilon); +test_inputs.push(Math.PI); +test_inputs.push(Math.PI + epsilon); +test_inputs.push(Math.PI - epsilon); +test_inputs.push(- 2*Math.PI); +test_inputs.push(- 2*Math.PI + epsilon); +test_inputs.push(- 2*Math.PI - epsilon); + +var squares = []; +for (var i = 0; i < test_inputs.length; i++) { + var x = test_inputs[i]; + var err_sin = abs_error(Math.sin, sin, x); + var err_cos = abs_error(Math.cos, cos, x) + assertTrue(err_sin < 1E-13); + assertTrue(err_cos < 1E-13); + squares.push(err_sin*err_sin + err_cos*err_cos); +} + +// Sum squares up by adding them pairwise, to avoid losing precision. +while (squares.length > 1) { + var reduced = []; + if (squares.length % 2 == 1) reduced.push(squares.pop()); + // Remaining number of elements is even. + while(squares.length > 1) reduced.push(squares.pop() + squares.pop()); + squares = reduced; +} + +var err_rms = Math.sqrt(squares[0] / test_inputs.length / 2); +assertTrue(err_rms < 1E-14); + +assertEquals(-1, Math.cos({ valueOf: function() { return Math.PI; } })); +assertEquals(0, Math.sin("0x00000")); +assertTrue(isNaN(Math.sin(Infinity))); +assertTrue(isNaN(Math.cos("-Infinity"))); +assertEquals("Infinity", String(Math.tan(Math.PI/2))); +assertEquals("-Infinity", String(Math.tan(-Math.PI/2)));