Reland "Implement Math.sin, cos and tan using table lookup and spline interpolation."
authoryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 12 Nov 2013 14:43:18 +0000 (14:43 +0000)
committeryangguo@chromium.org <yangguo@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 12 Nov 2013 14:43:18 +0000 (14:43 +0000)
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

15 files changed:
src/arm/full-codegen-arm.cc
src/hydrogen.cc
src/ia32/full-codegen-ia32.cc
src/math.js
src/mips/full-codegen-mips.cc
src/objects-inl.h
src/objects.cc
src/objects.h
src/runtime.cc
src/runtime.h
src/x64/full-codegen-x64.cc
test/mjsunit/constant-folding-2.js
test/mjsunit/sin-cos.js
test/mozilla/mozilla.status
test/test262/test262.status

index dc012b5..0cb8e46 100644 (file)
@@ -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<Expression*>* 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<Expression*>* 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<Expression*>* 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,
index cc6bb34..7c42a3b 100644 (file)
@@ -6705,6 +6705,11 @@ int HOptimizedGraphBuilder::InliningAstSize(Handle<JSFunction> target) {
   Handle<JSFunction> caller = current_info()->closure();
   Handle<SharedFunctionInfo> 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<JSFunction> 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<HCallStub>(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<HCallStub>(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<HCallStub>(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()));
index 18ac191..8a51fbc 100644 (file)
@@ -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<Expression*>* 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<Expression*>* 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<Expression*>* 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,
index d5cabb0..2b9ab50 100644 (file)
@@ -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();
index caa7352..38334d5 100644 (file)
@@ -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<Expression*>* 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<Expression*>* 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<Expression*>* 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<Expression*>* 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<Expression*>* args = expr->arguments();
   ASSERT(args->length() == 1);
   VisitForStackValue(args->at(0));
-  __ CallRuntime(Runtime::kMath_sqrt, 1);
+  __ CallRuntime(Runtime::kMath_floor, 1);
   context()->Plug(v0);
 }
 
index bef807e..92f52b3 100644 (file)
@@ -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);
 }
index e5b4763..935e875 100644 (file)
@@ -9742,20 +9742,6 @@ bool JSFunction::EnsureCompiled(Handle<JSFunction> 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<JSObject> object) {
   if (object->IsGlobalObject()) return;
 
@@ -10024,6 +10010,16 @@ Handle<Object> 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();
 }
index 0099cb2..d7fda9c 100644 (file)
@@ -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.
   //
index bf4fa4e..348e1df 100644 (file)
@@ -5379,6 +5379,20 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetNativeFlag) {
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_SetInlineBuiltinFlag) {
+  SealHandleScope shs(isolate);
+  RUNTIME_ASSERT(args.length() == 1);
+
+  Handle<Object> object = args.at<Object>(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<double*>(
+      JSArrayBuffer::cast(sin_table->buffer())->backing_store());
+  double* cos_buffer = reinterpret_cast<double*>(
+      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);
index 30b23e3..2254ed1 100644 (file)
@@ -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)                                                       \
index a6c3214..d5fa5e5 100644 (file)
@@ -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<Expression*>* 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<Expression*>* 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<Expression*>* 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,
index 9e6b2c6..f429c6c 100644 (file)
@@ -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);
index e38dfdf..1176b6c 100644 (file)
@@ -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)));
index b27e991..9e23dce 100644 (file)
   '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.
index e546266..80a302b 100644 (file)
@@ -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