Search code examples
dartmathimplementation

Dart source code for basic mathematical functions


Just out of interest I recently tried to take a look at the source code of some basic mathematical functions in the dart programming language (more specifically, in the dart:math package).

Take e.g. the cosine function. It was easy enough to find its documentation

Cosine documentation

and click on the 'View source code' button in the top right corner. However, here the problems begin. In the respective file, the only related line of code is

/// Converts [radians] to a [double] and returns the cosine of the value.
///
/// If [radians] is not a finite number, the result is NaN.
external double cos(num radians);

with no hint whatsoever where to find the actual implementation. In fact, it seems that it is not at all contained in the sdk/lib/math directory as one might expect.

Has anyone an idea where to find it? Thank you in advance!


Solution

  • I am not a developer on the Dart project so I might get all of this wrong. So see my answer here as my best guess into what is going on. :)

    When running native, Dart uses the libc version of cos. The implementation are a bit tricky to find but let's try do an attempt. The definition you have found are defined as external which means the actual implementation are getting patched in depending on the running platform.

    So for native, we need to look at:

    @pragma("vm:exact-result-type", "dart:core#_Double")
    @pragma("vm:prefer-inline")
    double cos(num radians) => _cos(radians.toDouble());
    ...
    @pragma("vm:recognized", "other")
    @pragma("vm:prefer-inline")
    external double _cos(double x);
    

    https://github.com/dart-lang/sdk/blob/3b128c5454834a1aaef37d9bb12595e7c217ab61/sdk/lib/_internal/vm/lib/math_patch.dart#L133-L135

    The hint here is vm:recognized which tells the Dart compiler that it should handle this method call special. We can in the SDK find a list of methods it should recognize for special handling. And here we find:

    // (class-name, function-name, recognized enum, fingerprint).
    // When adding a new function, add a 0 as the fingerprint and run the build in
    // debug mode to get the correct fingerprint from the mismatch error.
    ...
      V(::, _sin, MathSin, 0x17cc3e23)                                             \
      V(::, _cos, MathCos, 0xf485f165)                                             \
      V(::, _tan, MathTan, 0xeb0bc957)                                             \
      V(::, _asin, MathAsin, 0x29d649be)                                           \
      V(::, _acos, MathAcos, 0x1ffc14fb)                                           \
      V(::, _atan, MathAtan, 0x10ebd512)                                           \
      V(::, _atan2, MathAtan2, 0x58c66573)                                         \
      V(::, _sqrt, MathSqrt, 0x0309a7b0)                                           \
      V(::, _exp, MathExp, 0x00e673f0)                                             \
      V(::, _log, MathLog, 0x099ff882)                                             \
    ...
    

    We can then later find trace of MathCos in il.cc:

    const RuntimeEntry& InvokeMathCFunctionInstr::TargetFunction() const {
      switch (recognized_kind_) {
        case MethodRecognizer::kDoubleTruncateToDouble:
          return kLibcTruncRuntimeEntry;
        case MethodRecognizer::kDoubleRoundToDouble:
          return kLibcRoundRuntimeEntry;
        case MethodRecognizer::kDoubleFloorToDouble:
          return kLibcFloorRuntimeEntry;
        case MethodRecognizer::kDoubleCeilToDouble:
          return kLibcCeilRuntimeEntry;
        case MethodRecognizer::kMathDoublePow:
          return kLibcPowRuntimeEntry;
        case MethodRecognizer::kDoubleMod:
          return kDartModuloRuntimeEntry;
        case MethodRecognizer::kMathTan:
          return kLibcTanRuntimeEntry;
        case MethodRecognizer::kMathAsin:
          return kLibcAsinRuntimeEntry;
        case MethodRecognizer::kMathSin:
          return kLibcSinRuntimeEntry;
        case MethodRecognizer::kMathCos:
          return kLibcCosRuntimeEntry;
        case MethodRecognizer::kMathAcos:
          return kLibcAcosRuntimeEntry;
        case MethodRecognizer::kMathAtan:
          return kLibcAtanRuntimeEntry;
        case MethodRecognizer::kMathAtan2:
          return kLibcAtan2RuntimeEntry;
        case MethodRecognizer::kMathExp:
          return kLibcExpRuntimeEntry;
        case MethodRecognizer::kMathLog:
          return kLibcLogRuntimeEntry;
        default:
          UNREACHABLE();
      }
      return kLibcPowRuntimeEntry;
    }
    

    https://github.com/dart-lang/sdk/blob/3b128c5454834a1aaef37d9bb12595e7c217ab61/runtime/vm/compiler/backend/il.cc#L6991-L7027

    The name of this constant kinda gives away that we are using libc. But finding the definition of kLibcCosRuntimeEntry is not obvious since this constant are getting generated using the following macro:

    #define DEFINE_RAW_LEAF_RUNTIME_ENTRY(name, argument_count, is_float, func)    \
      extern const RuntimeEntry k##name##RuntimeEntry(                             \
          "DFLRT_" #name, func, argument_count, true, is_float,                    \
          /*can_lazy_deopt=*/false)
    

    https://github.com/dart-lang/sdk/blob/3b128c5454834a1aaef37d9bb12595e7c217ab61/runtime/vm/runtime_entry.h#L147-L150

    And is then used here:

    DEFINE_RAW_LEAF_RUNTIME_ENTRY(
        LibcCos,
        1,
        true /* is_float */,
        reinterpret_cast<RuntimeFunction>(static_cast<UnaryMathCFunction>(&cos)));
    

    https://github.com/dart-lang/sdk/blob/3b128c5454834a1aaef37d9bb12595e7c217ab61/runtime/vm/runtime_entry.cc#L3894-L3898

    Where &cos refer to the cos method that have been globally imported from math.h here:

    #include <math.h>
    

    https://github.com/dart-lang/sdk/blob/3b128c5454834a1aaef37d9bb12595e7c217ab61/runtime/platform/globals.h#L83