From e83fd01ce6cbaa15920b45cfd37d6c514475d9e8 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Tue, 12 Nov 2013 14:43:18 +0000 Subject: [PATCH] Reland "Implement Math.sin, cos and tan using table lookup and spline interpolation." This relands r17594 with necessary fixes. R=jkummerow@chromium.org BUG= Review URL: https://codereview.chromium.org/70003004 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17654 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 36 -------------- src/hydrogen.cc | 43 +++-------------- src/ia32/full-codegen-ia32.cc | 36 -------------- src/math.js | 96 ++++++++++++++++++++++++++++++++++++-- src/mips/full-codegen-mips.cc | 43 +++-------------- src/objects-inl.h | 3 ++ src/objects.cc | 24 ++++------ src/objects.h | 10 ++-- src/runtime.cc | 43 +++++++++++++++++ src/runtime.h | 5 +- src/x64/full-codegen-x64.cc | 36 -------------- test/mjsunit/constant-folding-2.js | 24 ---------- test/mjsunit/sin-cos.js | 95 ++++++++++++++++++++++++++++++++++++- test/mozilla/mozilla.status | 3 ++ test/test262/test262.status | 1 + 15 files changed, 269 insertions(+), 229 deletions(-) diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index dc012b5..0cb8e46 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -3762,42 +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)); - __ 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, diff --git a/src/hydrogen.cc b/src/hydrogen.cc index cc6bb34..7c42a3b 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -6705,6 +6705,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() > @@ -6714,7 +6719,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; } @@ -7094,9 +7099,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. @@ -7175,9 +7177,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(); @@ -9506,36 +9505,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())); diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 18ac191..8a51fbc 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -3728,42 +3728,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, diff --git a/src/math.js b/src/math.js index d5cabb0..2b9ab50 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 @@ -179,7 +179,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 @@ -189,7 +189,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. @@ -198,6 +198,92 @@ function MathImul(x, y) { } +var MathSinImpl = function(x) { + InitTrigonometricFunctions(); + return MathSinImpl(x); +} + + +var MathCosImpl = function(x) { + InitTrigonometricFunctions(); + return MathCosImpl(x); +} + + +var InitTrigonometricFunctions; + + +// Define constants and interpolation functions. +// Also define the initialization function that populates the lookup table +// and then wires up the function definitions. +function SetupTrigonometricFunctions() { + 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; + var table_cos_interval; + + // This implements sine using 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. + var Interpolation = function(x) { + 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)); + } + + var MathSinInterpolation = function(x) { + var multiple = MathFloor(x * inverse_pi_half); + if (%_IsMinusZero(multiple)) return multiple; + x = (multiple & 1) * pi_half + + (1 - ((multiple & 1) << 1)) * (x - multiple * pi_half); + return Interpolation(x) * (1 - (multiple & 2)) + 0; + } + + // Cosine is sine with a phase offset of pi/2. + var MathCosInterpolation = function(x) { + var multiple = MathFloor(x * inverse_pi_half); + var phase = multiple + 1; + x = (phase & 1) * pi_half + + (1 - ((phase & 1) << 1)) * (x - multiple * pi_half); + return Interpolation(x) * (1 - (phase & 2)) + 0; + }; + + %SetInlineBuiltinFlag(Interpolation); + %SetInlineBuiltinFlag(MathSinInterpolation); + %SetInlineBuiltinFlag(MathCosInterpolation); + + InitTrigonometricFunctions = function() { + table_sin = new global.Float64Array(samples + 2); + table_cos_interval = new global.Float64Array(samples + 2); + %PopulateTrigonometricTable(table_sin, table_cos_interval, samples); + MathSinImpl = MathSinInterpolation; + MathCosImpl = MathCosInterpolation; + } +} + +SetupTrigonometricFunctions(); + + // ------------------------------------------------------------------- function SetUpMath() { @@ -270,6 +356,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 caa7352..38334d5 100644 --- a/src/mips/full-codegen-mips.cc +++ b/src/mips/full-codegen-mips.cc @@ -3762,35 +3762,9 @@ 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) { +void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::TAN, + TranscendentalCacheStub stub(TranscendentalCache::LOG, TranscendentalCacheStub::TAGGED); ZoneList* args = expr->arguments(); ASSERT(args->length() == 1); @@ -3801,25 +3775,22 @@ void FullCodeGenerator::EmitMathTan(CallRuntime* expr) { } -void FullCodeGenerator::EmitMathLog(CallRuntime* expr) { - // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::LOG, - TranscendentalCacheStub::TAGGED); +void FullCodeGenerator::EmitMathSqrt(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)); - __ mov(a0, result_register()); // Stub requires parameter in a0 and on tos. - __ CallStub(&stub); + __ CallRuntime(Runtime::kMath_sqrt, 1); context()->Plug(v0); } -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_sqrt, 1); + __ CallRuntime(Runtime::kMath_floor, 1); context()->Plug(v0); } diff --git a/src/objects-inl.h b/src/objects-inl.h index bef807e..92f52b3 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 e5b4763..935e875 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -9742,20 +9742,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; @@ -10024,6 +10010,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 0099cb2..d7fda9c 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 bf4fa4e..348e1df 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 30b23e3..2254ed1 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,9 +627,6 @@ 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(IsMinusZero, 1, 1) \ diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index a6c3214..d5fa5e5 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -3686,42 +3686,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, diff --git a/test/mjsunit/constant-folding-2.js b/test/mjsunit/constant-folding-2.js index 9e6b2c6..f429c6c 100644 --- a/test/mjsunit/constant-folding-2.js +++ b/test/mjsunit/constant-folding-2.js @@ -128,30 +128,6 @@ test(function mathMax() { assertEquals("Infinity", String(1 / Math.max(0.0, -0.0))); }); -test(function mathSin() { - assertEquals(0.0, Math.sin(0.0)); - assertTrue(0.8 < Math.sin(1) && Math.sin(1) < 0.9); - assertEquals("NaN", String(Math.sin(Infinity))); - assertEquals("NaN", String(Math.sin(-Infinity))); - assertEquals("NaN", String(Math.sin(NaN))); -}); - -test(function mathCos() { - assertEquals(1.0, Math.cos(0.0)); - assertTrue(0.5 < Math.cos(1) && Math.cos(1) < 0.6); - assertEquals("NaN", String(Math.cos(Infinity))); - assertEquals("NaN", String(Math.cos(-Infinity))); - assertEquals("NaN", String(Math.cos(NaN))); -}); - -test(function mathTan() { - assertEquals(0.0, Math.tan(0.0)); - assertTrue(1.5 < Math.tan(1) && Math.tan(1) < 1.6); - assertEquals("NaN", String(Math.tan(Infinity))); - assertEquals("NaN", String(Math.tan(-Infinity))); - assertEquals("NaN", String(Math.tan(NaN))); -}); - test(function mathExp() { assertEquals(1.0, Math.exp(0.0)); assertTrue(2.7 < Math.exp(1) && Math.exp(1) < 2.8); diff --git a/test/mjsunit/sin-cos.js b/test/mjsunit/sin-cos.js index e38dfdf..1176b6c 100644 --- a/test/mjsunit/sin-cos.js +++ b/test/mjsunit/sin-cos.js @@ -42,9 +42,102 @@ 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")); +assertEquals(1, Math.cos("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))); diff --git a/test/mozilla/mozilla.status b/test/mozilla/mozilla.status index b27e991..9e23dce 100644 --- a/test/mozilla/mozilla.status +++ b/test/mozilla/mozilla.status @@ -600,6 +600,9 @@ 'ecma/TypeConversion/9.3.1-3': [FAIL_OK], + # Math.tan expectations are more strict than the spec. + 'ecma/Math/15.8.2.18': [FAIL_OK], + ##################### FAILING TESTS ##################### # This section is for tests that fail in V8 and pass in JSC. diff --git a/test/test262/test262.status b/test/test262/test262.status index e546266..80a302b 100644 --- a/test/test262/test262.status +++ b/test/test262/test262.status @@ -73,6 +73,7 @@ # trigonometric functions are platform/compiler dependent. Furthermore, the # expectation values by far deviates from the actual result given by an # arbitrary-precision calculator, making those tests partly bogus. + 'S15.8.2.7_A7': [PASS, FAIL_OK], # Math.cos 'S15.8.2.8_A6': [PASS, FAIL_OK], # Math.exp (less precise with --fast-math) 'S15.8.2.16_A7': [PASS, FAIL_OK], # Math.sin 'S15.8.2.18_A7': [PASS, FAIL_OK], # Math.tan -- 2.7.4