mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Compute Math functions as V8 does and link libm only when a program needs it
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { systemLibraryLinkerArgs } from "./link-plan-core.js";
|
||||
import { MACOS_ARM64_TARGET, LINUX_ARM64_GNU_TARGET, LINUX_X64_GNU_TARGET } from "./targets.js";
|
||||
|
||||
test("Linux links libm as needed and other system libraries as given", () => {
|
||||
for (const target of [LINUX_X64_GNU_TARGET, LINUX_ARM64_GNU_TARGET]) {
|
||||
expect(systemLibraryLinkerArgs(target, ["m"])).toEqual([
|
||||
"-Wl,--as-needed",
|
||||
"-lm",
|
||||
"-Wl,--no-as-needed",
|
||||
]);
|
||||
expect(systemLibraryLinkerArgs(target, ["z", "m"])).toEqual([
|
||||
"-lz",
|
||||
"-Wl,--as-needed",
|
||||
"-lm",
|
||||
"-Wl,--no-as-needed",
|
||||
]);
|
||||
}
|
||||
expect(systemLibraryLinkerArgs(MACOS_ARM64_TARGET, ["System"])).toEqual(["-lSystem"]);
|
||||
});
|
||||
@@ -70,3 +70,19 @@ export function executableLinkInputs(options: {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** The linker arguments for an executable's system libraries. On Linux, libm
|
||||
* links as needed: the runtime defines the libm functions most programs
|
||||
* reach (scr_glibc_math.c) and Math's transcendentals (scr_ieee754.c), so a
|
||||
* program that calls no other libm function, such as pow, starts without
|
||||
* loading libm.so. */
|
||||
export function systemLibraryLinkerArgs(
|
||||
target: Pick<NativeTargetSpec, "platform">,
|
||||
names: readonly string[],
|
||||
): string[] {
|
||||
return names.flatMap((name) =>
|
||||
name === "m" && target.platform === "linux"
|
||||
? ["-Wl,--as-needed", "-lm", "-Wl,--no-as-needed"]
|
||||
: [`-l${name}`],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { type NativeArtifactDependency } from "./native/contracts.js";
|
||||
import { RuntimePackError, stageRuntimePackArtifacts } from "./runtime-pack.js";
|
||||
import type { NativeLinkPlan } from "./link-plan.js";
|
||||
import { lldLinkerArgs } from "./lld.js";
|
||||
import { systemLibraryLinkerArgs } from "./link-plan-core.js";
|
||||
import {
|
||||
createDarwinDebugSymbols,
|
||||
installDarwinDebugSymbols,
|
||||
@@ -248,7 +249,7 @@ export async function linkNativeExecutable(
|
||||
...lld,
|
||||
...plan.driverFlags,
|
||||
...plan.inputs.map((input) => staged.replacements.get(input) ?? input),
|
||||
...plan.systemLibraries.map((name) => `-l${name}`),
|
||||
...systemLibraryLinkerArgs(plan.target, plan.systemLibraries),
|
||||
"-o",
|
||||
privateOut,
|
||||
];
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
type ArrayAccess,
|
||||
} from "./dense-array-access.js";
|
||||
import { emitWrappingSum, integerOrNaNRange, markWrappingOperands } from "./wrapping-sums.js";
|
||||
import { emitPowerOfTwoQuotient, isPowerOfTwoCall } from "./lib-filesystem.js";
|
||||
import { emitPowF64, emitPowerOfTwoQuotient, isPowerOfTwoCall } from "./lib-filesystem.js";
|
||||
|
||||
/** The signed integer predicates of the number compares. */
|
||||
const INTEGER_COMPARES: Record<string, string> = {
|
||||
@@ -305,7 +305,6 @@ export function emitOperatorExpr(
|
||||
"===": "oeq",
|
||||
"!==": "une",
|
||||
};
|
||||
const libm: Record<string, string> = { "**": "pow" };
|
||||
const bit: Record<string, "and" | "or" | "xor" | "shl" | "ashr" | "lshr"> = {
|
||||
"&": "and",
|
||||
"|": "or",
|
||||
@@ -471,32 +470,8 @@ export function emitOperatorExpr(
|
||||
B.line(`${t} = ${e.op === ">>>" ? "uitofp" : "sitofp"} i32 ${result} to double`);
|
||||
return { name: t, type: e.type, uint32: result };
|
||||
} else {
|
||||
const fn = libm[e.op];
|
||||
if (fn === undefined) throw new LlvmUnsupportedError(`bin:${e.op}`, e.loc);
|
||||
host.declare(`declare double @${fn}(double, double)`);
|
||||
// libm's pow answers 1 for pow(±1, ±Infinity) and pow(1, NaN), where
|
||||
// `**` answers NaN (and LLVM folds pow(1.0, x) to 1.0): an inline
|
||||
// guard selects NaN for a base of ±1 with a non-finite exponent and
|
||||
// keeps the direct pow call. A finite literal exponent, or a literal
|
||||
// base other than ±1, cannot meet those cases and needs no guard.
|
||||
if (fn === "pow" && !libmPowAgrees(e.left, e.right)) {
|
||||
host.declare(`declare double @llvm.fabs.f64(double)`);
|
||||
const power = B.tmp();
|
||||
B.line(`${power} = call double @pow(double ${l.name}, double ${r.name})`);
|
||||
const base = B.tmp();
|
||||
const unit = B.tmp();
|
||||
const magnitude = B.tmp();
|
||||
const finite = B.tmp();
|
||||
const reject = B.tmp();
|
||||
B.line(`${base} = call double @llvm.fabs.f64(double ${l.name})`);
|
||||
B.line(`${unit} = fcmp oeq double ${base}, 1.0`);
|
||||
B.line(`${magnitude} = call double @llvm.fabs.f64(double ${r.name})`);
|
||||
B.line(`${finite} = fcmp olt double ${magnitude}, 0x7FF0000000000000`);
|
||||
B.line(`${reject} = select i1 ${finite}, i1 false, i1 ${unit}`);
|
||||
B.line(`${t} = select i1 ${reject}, double 0x7FF8000000000000, double ${power}`);
|
||||
} else {
|
||||
B.line(`${t} = call double @${fn}(double ${l.name}, double ${r.name})`);
|
||||
}
|
||||
if (e.op !== "**") throw new LlvmUnsupportedError(`bin:${e.op}`, e.loc);
|
||||
return { name: emitPowF64(host, l.name, r.name, e.right), type: e.type };
|
||||
}
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
@@ -1473,19 +1448,3 @@ export function emitRecordLitFields(
|
||||
host.initField(ptr, type, inline, v);
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether libm pow matches `**` for these operands: every case except a
|
||||
* base of ±1 with a NaN or infinite exponent agrees. */
|
||||
function libmPowAgrees(base: IrExpr, exponent: IrExpr): boolean {
|
||||
const power = numericLiteral(exponent);
|
||||
if (power !== null && Number.isFinite(power)) return true;
|
||||
const value = numericLiteral(base);
|
||||
return value !== null && !Number.isNaN(value) && Math.abs(value) !== 1;
|
||||
}
|
||||
|
||||
function numericLiteral(expr: IrExpr): number | null {
|
||||
if (expr.kind === "numLit") return expr.value;
|
||||
if (expr.kind === "unary" && expr.op === "-" && expr.operand.kind === "numLit")
|
||||
return -expr.operand.value;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { LlvmEmitterContext, LibCallExpr, LibCallPrefix, LlValue } from "./
|
||||
import {
|
||||
ASSERT_VERDICT_LIB_FNS,
|
||||
LIB_FN_SYMS,
|
||||
PURE_MATH_LIB_FNS,
|
||||
mockTimerSym,
|
||||
USES_TIMERS_LIB_FNS,
|
||||
} from "./lib-shared.js";
|
||||
@@ -777,7 +778,8 @@ function emitSymbolLibCall(
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ${retDecl} @${sym}(${argList})`);
|
||||
const attrs = PURE_MATH_LIB_FNS.has(e.fn) ? " nounwind willreturn memory(none)" : "";
|
||||
B.line(`${t} = call ${retDecl} @${sym}(${argList})${attrs}`);
|
||||
// The result joins its frame BEFORE the pending check so an unwind
|
||||
// releases the dummy (NULL for refcounted returns) harmlessly.
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
|
||||
@@ -1027,6 +1027,54 @@ function emitPowerOfTwo(host: LlvmEmitterContext, n: string): string {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** `base ** exponent` and `Math.pow(base, exponent)` as V8's math::pow
|
||||
* answers them: a literal exponent 2 is `base * base`, a literal 0.5 is
|
||||
* `sqrt(base + 0)` (+Infinity for an infinite base), and every other
|
||||
* exponent calls scr_math_pow, which applies V8's other special cases
|
||||
* before the platform pow. The platform pow is never called by its libm
|
||||
* name, which LLVM would rewrite (pow(2, x) to exp2(x), pow(x, -1) to 1/x)
|
||||
* into answers that can differ from V8's. */
|
||||
export function emitPowF64(
|
||||
host: LlvmEmitterContext,
|
||||
base: string,
|
||||
exponent: string,
|
||||
exponentExpr: IrExpr,
|
||||
): string {
|
||||
const B = host.B;
|
||||
const literal =
|
||||
exponentExpr.kind === "numLit"
|
||||
? exponentExpr.value
|
||||
: exponentExpr.kind === "unary" &&
|
||||
exponentExpr.op === "-" &&
|
||||
exponentExpr.operand.kind === "numLit"
|
||||
? -exponentExpr.operand.value
|
||||
: null;
|
||||
const out = B.tmp();
|
||||
if (literal === 2) {
|
||||
B.line(`${out} = fmul double ${base}, ${base}`);
|
||||
return out;
|
||||
}
|
||||
if (literal === 0.5) {
|
||||
host.declare(`declare double @llvm.sqrt.f64(double)`);
|
||||
host.declare(`declare double @llvm.fabs.f64(double)`);
|
||||
const plus = B.tmp(),
|
||||
root = B.tmp(),
|
||||
magnitude = B.tmp(),
|
||||
infinite = B.tmp();
|
||||
B.line(`${plus} = fadd double ${base}, ${f64Lit(0)}`);
|
||||
B.line(`${root} = call double @llvm.sqrt.f64(double ${plus})`);
|
||||
B.line(`${magnitude} = call double @llvm.fabs.f64(double ${base})`);
|
||||
B.line(`${infinite} = fcmp oeq double ${magnitude}, 0x7FF0000000000000`);
|
||||
B.line(`${out} = select i1 ${infinite}, double 0x7FF0000000000000, double ${root}`);
|
||||
return out;
|
||||
}
|
||||
host.declare(`declare double @scr_math_pow(double, double)`);
|
||||
B.line(
|
||||
`${out} = call double @scr_math_pow(double ${base}, double ${exponent}) nounwind willreturn`,
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** `Math.pow(2, n)`, the divisor emitPowerOfTwoQuotient takes. */
|
||||
export function isPowerOfTwoCall(e: IrExpr): boolean {
|
||||
return (
|
||||
@@ -1099,6 +1147,11 @@ export function emitPrimitiveLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const v = host.emitExpr(e.args[1]!);
|
||||
return { name: emitPowerOfTwo(host, v.name), type: e.type };
|
||||
}
|
||||
if (e.fn === "math.pow") {
|
||||
const base = host.emitExpr(e.args[0]!);
|
||||
const exponent = host.emitExpr(e.args[1]!);
|
||||
return { name: emitPowF64(host, base.name, exponent.name, e.args[1]!), type: e.type };
|
||||
}
|
||||
if (e.fn === "math.floor" || e.fn === "math.ceil") {
|
||||
const intr = e.fn === "math.floor" ? "floor" : "ceil";
|
||||
const v = host.emitExpr(e.args[0]!);
|
||||
|
||||
@@ -35,6 +35,31 @@ export const ASSERT_VERDICT_LIB_FNS: ReadonlyMap<string, number | null> = new Ma
|
||||
["assert.partialDeepResult", null],
|
||||
]);
|
||||
|
||||
/** The Math functions that call scr_ieee754.c. They read and write no
|
||||
* memory, so calls with the same argument fold and loops may hoist them. */
|
||||
export const PURE_MATH_LIB_FNS: ReadonlySet<string> = new Set([
|
||||
"math.sin",
|
||||
"math.sinh",
|
||||
"math.cos",
|
||||
"math.cosh",
|
||||
"math.tan",
|
||||
"math.tanh",
|
||||
"math.asin",
|
||||
"math.asinh",
|
||||
"math.acos",
|
||||
"math.acosh",
|
||||
"math.atan",
|
||||
"math.atanh",
|
||||
"math.cbrt",
|
||||
"math.exp",
|
||||
"math.expm1",
|
||||
"math.log",
|
||||
"math.log1p",
|
||||
"math.log2",
|
||||
"math.log10",
|
||||
"math.atan2",
|
||||
]);
|
||||
|
||||
export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"assert.expectsErrDyn": "scr_assert_expects_err_dyn",
|
||||
"assert.throwsValidate": "scr_assert_throws_validate",
|
||||
@@ -118,27 +143,29 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"str.codePoints": "scr_str_code_points",
|
||||
"num.fromString": "scr_string_to_number",
|
||||
"math.round": "scr_math_round",
|
||||
"math.sin": "sin",
|
||||
"math.sinh": "sinh",
|
||||
"math.cos": "cos",
|
||||
"math.cosh": "cosh",
|
||||
"math.tan": "tan",
|
||||
"math.tanh": "tanh",
|
||||
"math.asin": "asin",
|
||||
"math.asinh": "asinh",
|
||||
"math.acos": "acos",
|
||||
"math.acosh": "acosh",
|
||||
"math.atan": "atan",
|
||||
"math.atanh": "atanh",
|
||||
"math.cbrt": "cbrt",
|
||||
"math.exp": "exp",
|
||||
"math.expm1": "expm1",
|
||||
// Math's transcendental functions are V8's (scr_ieee754.c), so their
|
||||
// results are the ones Node prints.
|
||||
"math.sin": "scr_ieee754_sin",
|
||||
"math.sinh": "scr_ieee754_sinh",
|
||||
"math.cos": "scr_ieee754_cos",
|
||||
"math.cosh": "scr_ieee754_cosh",
|
||||
"math.tan": "scr_ieee754_tan",
|
||||
"math.tanh": "scr_ieee754_tanh",
|
||||
"math.asin": "scr_ieee754_asin",
|
||||
"math.asinh": "scr_ieee754_asinh",
|
||||
"math.acos": "scr_ieee754_acos",
|
||||
"math.acosh": "scr_ieee754_acosh",
|
||||
"math.atan": "scr_ieee754_atan",
|
||||
"math.atanh": "scr_ieee754_atanh",
|
||||
"math.cbrt": "scr_ieee754_cbrt",
|
||||
"math.exp": "scr_ieee754_exp",
|
||||
"math.expm1": "scr_ieee754_expm1",
|
||||
"math.sqrt": "sqrt",
|
||||
"math.log": "log",
|
||||
"math.log1p": "log1p",
|
||||
"math.log2": "log2",
|
||||
"math.log10": "log10",
|
||||
"math.atan2": "atan2",
|
||||
"math.log": "scr_ieee754_log",
|
||||
"math.log1p": "scr_ieee754_log1p",
|
||||
"math.log2": "scr_ieee754_log2",
|
||||
"math.log10": "scr_ieee754_log10",
|
||||
"math.atan2": "scr_ieee754_atan2",
|
||||
"math.pow": "scr_math_pow",
|
||||
// encodeURIComponent throws URIError on a lone surrogate
|
||||
// (MAY_THROW_LIB_FNS); decodeUriComponent is with the throwing tier below.
|
||||
|
||||
@@ -74,6 +74,8 @@ export const EXECUTABLE_RUNTIME_SOURCES = [
|
||||
"scr_slots.c",
|
||||
"scr_alloc.c",
|
||||
"scr_cipher_info.c",
|
||||
"scr_ieee754.c",
|
||||
"scr_glibc_math.c",
|
||||
] as const;
|
||||
|
||||
export function runtimeSrcDir(): string {
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
CompileRequestResult,
|
||||
} from "../compile-types.js";
|
||||
import { LlvmUnsupportedError } from "../backend/llvm/emitter.js";
|
||||
import { executableLinkInputs } from "../backend/link-plan-core.js";
|
||||
import { executableLinkInputs, systemLibraryLinkerArgs } from "../backend/link-plan-core.js";
|
||||
import { formatNativeLinkInfo } from "../backend/native-link-info-core.js";
|
||||
import {
|
||||
emitNativeObject,
|
||||
@@ -527,7 +527,7 @@ export class NativeCompiler {
|
||||
...lld,
|
||||
...plan.driverFlags,
|
||||
...plan.inputs,
|
||||
...plan.systemLibraries.map((name) => `-l${name}`),
|
||||
...systemLibraryLinkerArgs(target, plan.systemLibraries),
|
||||
"-o",
|
||||
stagedOutput,
|
||||
];
|
||||
|
||||
@@ -90,7 +90,7 @@ console.log(remainder(5, 2.5), signed(-7.5), maybeZero(7.5, 2.5));
|
||||
expect(maybeZero).toContain("rem.float");
|
||||
expect(ll).not.toContain("@llvm.fptosi.sat");
|
||||
expect(ll).not.toContain("@fmod");
|
||||
expect(ll).toContain("call double @pow(");
|
||||
expect(ll).toContain("call double @scr_math_pow(");
|
||||
|
||||
// Where truncation is an integer round trip, a dividend below 2^62 takes
|
||||
// the i64 remainder by the power of two and adds back its fraction; a
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7717-math-fdlibm.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7718-math-fdlibm.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -348,6 +348,11 @@ async function build() {
|
||||
"artifacts/licenses/v8-number-radix.txt",
|
||||
"BSD-3-Clause",
|
||||
],
|
||||
[
|
||||
join(vendorRoot, "v8-ieee754", "LICENSE"),
|
||||
"artifacts/licenses/v8-ieee754.txt",
|
||||
"BSD-3-Clause",
|
||||
],
|
||||
[join(vendorRoot, "unicode", "LICENSE"), "artifacts/licenses/unicode.txt", "Unicode-3.0"],
|
||||
[join(zlib, "LICENSE"), "artifacts/licenses/zlib.txt", "Zlib"],
|
||||
[join(vendorRoot, "brotli", "LICENSE"), "artifacts/licenses/brotli.txt", "MIT"],
|
||||
|
||||
@@ -52,6 +52,10 @@ export const BASE_RUNTIME_SOURCES = [
|
||||
"scr_alloc.c",
|
||||
// crypto.getCipherInfo's table; it links only into programs that call it.
|
||||
"scr_cipher_info.c",
|
||||
// Math's transcendental functions as V8 computes them, and on glibc the
|
||||
// exact libm functions that let a program link without libm.so.
|
||||
"scr_ieee754.c",
|
||||
"scr_glibc_math.c",
|
||||
];
|
||||
|
||||
export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
|
||||
@@ -447,6 +447,11 @@ async function build() {
|
||||
"artifacts/licenses/v8-number-radix.txt",
|
||||
"BSD-3-Clause",
|
||||
],
|
||||
[
|
||||
join(vendorRoot, "v8-ieee754", "LICENSE"),
|
||||
"artifacts/licenses/v8-ieee754.txt",
|
||||
"BSD-3-Clause",
|
||||
],
|
||||
[join(zlib, "LICENSE"), "artifacts/licenses/zlib.txt", "Zlib"],
|
||||
[join(vendorRoot, "brotli", "LICENSE"), "artifacts/licenses/brotli.txt", "MIT"],
|
||||
[join(mbedtls, "LICENSE"), "artifacts/licenses/mbedtls.txt", "Apache-2.0"],
|
||||
|
||||
@@ -4141,8 +4141,8 @@ static bool scr_bytes_rw_check(const ScrBytes *b, double offset, size_t width) {
|
||||
* exactly Node — and writes zeros downstream. */
|
||||
static bool scr_bytes_check_int(const ScrBytes *b, double value, double offset,
|
||||
size_t width, bool sign) {
|
||||
double max = sign ? exp2((double)(8 * width - 1)) - 1 : exp2((double)(8 * width)) - 1;
|
||||
double min = sign ? -exp2((double)(8 * width - 1)) : 0;
|
||||
double max = sign ? ldexp(1.0, (int)(8 * width - 1)) - 1 : ldexp(1.0, (int)(8 * width)) - 1;
|
||||
double min = sign ? -ldexp(1.0, (int)(8 * width - 1)) : 0;
|
||||
if (value > max || value < min) {
|
||||
char recv[48];
|
||||
scr_num_received(value, recv);
|
||||
|
||||
@@ -44,8 +44,8 @@ static size_t scr_bytes_dyn_width(ScrBytesNumKind kind) {
|
||||
|
||||
/* Node's checkInt range test and message, before the offset is looked at. */
|
||||
static bool scr_bytes_dyn_value_in_range(double value, size_t width, bool sign) {
|
||||
double max = sign ? exp2((double)(8 * width - 1)) - 1 : exp2((double)(8 * width)) - 1;
|
||||
double min = sign ? -exp2((double)(8 * width - 1)) : 0;
|
||||
double max = sign ? ldexp(1.0, (int)(8 * width - 1)) - 1 : ldexp(1.0, (int)(8 * width)) - 1;
|
||||
double min = sign ? -ldexp(1.0, (int)(8 * width - 1)) : 0;
|
||||
if (!(value > max || value < min)) return true;
|
||||
char recv[48];
|
||||
scr_num_received(value, recv);
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
/* The libm functions that glibc executables reach without calling a Math
|
||||
* transcendental: floor, ceil, trunc and round (LLVM's rounding intrinsics
|
||||
* on x86-64 before SSE4.1, and the runtime's own calls), fmod (the %
|
||||
* operator), sqrt (Math.sqrt's call for a negative argument), and ldexp,
|
||||
* scalbn and frexp, which glibc exports from both libc and libm (the link
|
||||
* would otherwise take them from libm). Each is exactly specified, so these
|
||||
* definitions answer what glibc's do, the NaN payloads of NaN inputs
|
||||
* included. Defining them in the executable lets a program that uses no
|
||||
* other libm function link without libm.so (-lm is linked as needed),
|
||||
* which saves loading it at startup. Library builds keep the embedder's
|
||||
* libm. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__GLIBC__) && !defined(SCR_LIB)
|
||||
|
||||
/* The sanitizer runtime calls some of these functions while it initializes,
|
||||
* and the dynamic loader calls the resolvers before that: none of the code
|
||||
* here carries sanitizer instrumentation. no_sanitize("thread") alone keeps
|
||||
* ThreadSanitizer's function entry and exit calls, which crash before its
|
||||
* runtime initializes; disable_sanitizer_instrumentation removes them. */
|
||||
#pragma clang attribute push(__attribute__((no_sanitize("address", "undefined", "thread"), disable_sanitizer_instrumentation)), apply_to = function)
|
||||
|
||||
static inline uint64_t sgm_bits(double d) {
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &d, sizeof bits);
|
||||
return bits;
|
||||
}
|
||||
|
||||
static inline double sgm_double(uint64_t bits) {
|
||||
double d;
|
||||
memcpy(&d, &bits, sizeof d);
|
||||
return d;
|
||||
}
|
||||
|
||||
#define SGM_SIGN (UINT64_C(1) << 63)
|
||||
|
||||
/* Hidden: the executable's own references bind to these definitions, which
|
||||
* are not exported (libc.so and libm.so define the same names), so section
|
||||
* GC drops the ones a program does not call. */
|
||||
#define SGM_DEFINE __attribute__((visibility("hidden")))
|
||||
|
||||
/* Values of magnitude below 2^52 round through an integer conversion: the
|
||||
* truncated value, stepped by one when the rounding direction needs it, with
|
||||
* the value's sign so that a zero result keeps it (ceil(-0.5) is -0). */
|
||||
static inline int sgm_small(double x) { return __builtin_fabs(x) < 0x1p52; }
|
||||
|
||||
/* The unbiased exponent; 1024 for the infinities and NaN. Values with an
|
||||
* exponent of 52 or more are integers already. */
|
||||
static inline int sgm_exponent(uint64_t bits) { return (int)((bits >> 52) & 0x7FF) - 1023; }
|
||||
|
||||
static double sgm_floor(double x) {
|
||||
if (sgm_small(x)) {
|
||||
double t = (double)(int64_t)x;
|
||||
return __builtin_copysign(t > x ? t - 1.0 : t, x);
|
||||
}
|
||||
uint64_t bits = sgm_bits(x);
|
||||
int e = sgm_exponent(bits);
|
||||
if (e >= 52) return e == 1024 ? x + x : x;
|
||||
if (e < 0) {
|
||||
if ((bits << 1) == 0) return x;
|
||||
return bits & SGM_SIGN ? -1.0 : 0.0;
|
||||
}
|
||||
uint64_t fraction = (UINT64_C(1) << (52 - e)) - 1;
|
||||
if ((bits & fraction) == 0) return x;
|
||||
if (bits & SGM_SIGN) bits += fraction;
|
||||
return sgm_double(bits & ~fraction);
|
||||
}
|
||||
|
||||
static double sgm_ceil(double x) {
|
||||
if (sgm_small(x)) {
|
||||
double t = (double)(int64_t)x;
|
||||
return __builtin_copysign(t < x ? t + 1.0 : t, x);
|
||||
}
|
||||
uint64_t bits = sgm_bits(x);
|
||||
int e = sgm_exponent(bits);
|
||||
if (e >= 52) return e == 1024 ? x + x : x;
|
||||
if (e < 0) {
|
||||
if ((bits << 1) == 0) return x;
|
||||
return bits & SGM_SIGN ? -0.0 : 1.0;
|
||||
}
|
||||
uint64_t fraction = (UINT64_C(1) << (52 - e)) - 1;
|
||||
if ((bits & fraction) == 0) return x;
|
||||
if (!(bits & SGM_SIGN)) bits += fraction;
|
||||
return sgm_double(bits & ~fraction);
|
||||
}
|
||||
|
||||
static double sgm_trunc(double x) {
|
||||
if (sgm_small(x)) return __builtin_copysign((double)(int64_t)x, x);
|
||||
uint64_t bits = sgm_bits(x);
|
||||
int e = sgm_exponent(bits);
|
||||
if (e >= 52) return e == 1024 ? x + x : x;
|
||||
if (e < 0) return sgm_double(bits & SGM_SIGN);
|
||||
uint64_t fraction = (UINT64_C(1) << (52 - e)) - 1;
|
||||
return sgm_double(bits & ~fraction);
|
||||
}
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#include <cpuid.h>
|
||||
#include <smmintrin.h>
|
||||
|
||||
/* glibc selects SSE4.1's rounding instruction for floor, ceil and trunc when
|
||||
* the CPU has it, and so do these: the same results, with no call into the
|
||||
* integer path. */
|
||||
__attribute__((target("sse4.1"))) static double sgm_floor_sse41(double x) {
|
||||
return _mm_cvtsd_f64(_mm_floor_sd(_mm_set_sd(x), _mm_set_sd(x)));
|
||||
}
|
||||
__attribute__((target("sse4.1"))) static double sgm_ceil_sse41(double x) {
|
||||
return _mm_cvtsd_f64(_mm_ceil_sd(_mm_set_sd(x), _mm_set_sd(x)));
|
||||
}
|
||||
__attribute__((target("sse4.1"))) static double sgm_trunc_sse41(double x) {
|
||||
return _mm_cvtsd_f64(_mm_round_sd(_mm_set_sd(x), _mm_set_sd(x), _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC));
|
||||
}
|
||||
|
||||
static int sgm_has_sse41(void) {
|
||||
unsigned eax, ebx, ecx, edx;
|
||||
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & bit_SSE4_1) != 0;
|
||||
}
|
||||
|
||||
typedef double (*SgmUnary)(double);
|
||||
static SgmUnary sgm_resolve_floor(void) { return sgm_has_sse41() ? sgm_floor_sse41 : sgm_floor; }
|
||||
static SgmUnary sgm_resolve_ceil(void) { return sgm_has_sse41() ? sgm_ceil_sse41 : sgm_ceil; }
|
||||
static SgmUnary sgm_resolve_trunc(void) { return sgm_has_sse41() ? sgm_trunc_sse41 : sgm_trunc; }
|
||||
SGM_DEFINE double floor(double x) __attribute__((ifunc("sgm_resolve_floor")));
|
||||
SGM_DEFINE double ceil(double x) __attribute__((ifunc("sgm_resolve_ceil")));
|
||||
SGM_DEFINE double trunc(double x) __attribute__((ifunc("sgm_resolve_trunc")));
|
||||
#else
|
||||
SGM_DEFINE double floor(double x) { return sgm_floor(x); }
|
||||
SGM_DEFINE double ceil(double x) { return sgm_ceil(x); }
|
||||
SGM_DEFINE double trunc(double x) { return sgm_trunc(x); }
|
||||
#endif
|
||||
|
||||
/* Math.sqrt is LLVM's sqrt libcall: the square root instruction inline, and
|
||||
* a call to sqrt for a negative argument, where libm would set errno. This
|
||||
* definition answers that call with the same instruction. */
|
||||
SGM_DEFINE double sqrt(double x) {
|
||||
#if defined(__x86_64__)
|
||||
return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_set_sd(x), _mm_set_sd(x)));
|
||||
#elif defined(__aarch64__)
|
||||
double r;
|
||||
__asm__("fsqrt %d0, %d1" : "=w"(r) : "w"(x));
|
||||
return r;
|
||||
#else
|
||||
return __builtin_sqrt(x);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Halfway cases round away from zero. */
|
||||
SGM_DEFINE double round(double x) {
|
||||
uint64_t bits = sgm_bits(x);
|
||||
int e = sgm_exponent(bits);
|
||||
if (e >= 52) return e == 1024 ? x + x : x;
|
||||
if (e < 0) {
|
||||
uint64_t sign = bits & SGM_SIGN;
|
||||
return e == -1 ? sgm_double(sign | sgm_bits(1.0)) : sgm_double(sign);
|
||||
}
|
||||
uint64_t fraction = (UINT64_C(1) << (52 - e)) - 1;
|
||||
if ((bits & fraction) == 0) return x;
|
||||
bits += UINT64_C(1) << (51 - e);
|
||||
return sgm_double(bits & ~fraction);
|
||||
}
|
||||
|
||||
/* The significand of a finite nonzero double as an integer below 2^53, and
|
||||
* its exponent so that the value's magnitude is significand * 2^exponent. */
|
||||
static inline uint64_t sgm_significand(uint64_t bits, int *exponent) {
|
||||
int biased = (int)((bits >> 52) & 0x7FF);
|
||||
uint64_t significand = bits & ((UINT64_C(1) << 52) - 1);
|
||||
if (biased == 0) {
|
||||
*exponent = -1074;
|
||||
return significand;
|
||||
}
|
||||
*exponent = biased - 1075;
|
||||
return significand | (UINT64_C(1) << 52);
|
||||
}
|
||||
|
||||
/* x - n*y for the integer n = trunc(x/y), which is exact: the remainder
|
||||
* comes from shift-and-subtract over the significands. NaN, an infinite x
|
||||
* and a zero y answer (x*y)/(x*y), as glibc's fmod does. */
|
||||
SGM_DEFINE double fmod(double x, double y) {
|
||||
uint64_t xb = sgm_bits(x), yb = sgm_bits(y);
|
||||
uint64_t xmag = xb & ~SGM_SIGN, ymag = yb & ~SGM_SIGN;
|
||||
uint64_t infinity = UINT64_C(0x7FF0000000000000);
|
||||
if (ymag == 0 || xmag >= infinity || ymag > infinity) return (x * y) / (x * y);
|
||||
if (xmag < ymag) return x;
|
||||
if (xmag == ymag) return 0.0 * x;
|
||||
int ex, ey;
|
||||
uint64_t mx = sgm_significand(xb, &ex);
|
||||
uint64_t my = sgm_significand(yb, &ey);
|
||||
/* Normalize both to 53 significant bits, so the loop below shifts one
|
||||
* exponent step at a time. */
|
||||
while (mx < (UINT64_C(1) << 52)) {
|
||||
mx <<= 1;
|
||||
ex--;
|
||||
}
|
||||
while (my < (UINT64_C(1) << 52)) {
|
||||
my <<= 1;
|
||||
ey--;
|
||||
}
|
||||
for (; ex > ey; ex--) {
|
||||
if (mx >= my) mx -= my;
|
||||
if (mx == 0) return 0.0 * x;
|
||||
mx <<= 1;
|
||||
}
|
||||
if (mx >= my) mx -= my;
|
||||
if (mx == 0) return 0.0 * x;
|
||||
/* The remainder is mx * 2^ey, below y: rebuild it as a double. */
|
||||
while (mx < (UINT64_C(1) << 52)) {
|
||||
mx <<= 1;
|
||||
ey--;
|
||||
}
|
||||
uint64_t result;
|
||||
if (ey + 1075 >= 1) {
|
||||
result = ((uint64_t)(ey + 1075) << 52) | (mx & ((UINT64_C(1) << 52) - 1));
|
||||
} else {
|
||||
result = mx >> (1 - (ey + 1075));
|
||||
}
|
||||
return sgm_double(result | (xb & SGM_SIGN));
|
||||
}
|
||||
|
||||
/* x * 2^n, rounded once. Exponents beyond one double's range scale in
|
||||
* steps; the step toward the subnormals stays 53 bits above the smallest
|
||||
* normal so that only the last multiplication rounds. */
|
||||
SGM_DEFINE double scalbn(double x, int n) {
|
||||
if (n > 1023) {
|
||||
x *= 0x1p1023;
|
||||
n -= 1023;
|
||||
if (n > 1023) {
|
||||
x *= 0x1p1023;
|
||||
n -= 1023;
|
||||
if (n > 1023) n = 1023;
|
||||
}
|
||||
} else if (n < -1022) {
|
||||
x *= 0x1p-1022 * 0x1p53;
|
||||
n += 1022 - 53;
|
||||
if (n < -1022) {
|
||||
x *= 0x1p-1022 * 0x1p53;
|
||||
n += 1022 - 53;
|
||||
if (n < -1022) n = -1022;
|
||||
}
|
||||
}
|
||||
return x * sgm_double((uint64_t)(0x3FF + n) << 52);
|
||||
}
|
||||
|
||||
SGM_DEFINE double ldexp(double x, int n) { return scalbn(x, n); }
|
||||
|
||||
/* The significand in [0.5, 1) and the power of two: x = significand * 2^e.
|
||||
* Zero, the infinities and NaN answer themselves with e = 0. */
|
||||
SGM_DEFINE double frexp(double x, int *e) {
|
||||
uint64_t bits = sgm_bits(x);
|
||||
int biased = (int)((bits >> 52) & 0x7FF);
|
||||
if (biased == 0) {
|
||||
if ((bits << 1) == 0) {
|
||||
*e = 0;
|
||||
return x;
|
||||
}
|
||||
x = frexp(x * 0x1p64, e);
|
||||
*e -= 64;
|
||||
return x;
|
||||
}
|
||||
if (biased == 0x7FF) {
|
||||
*e = 0;
|
||||
return x;
|
||||
}
|
||||
*e = biased - 1022;
|
||||
return sgm_double((bits & ~(UINT64_C(0x7FF) << 52)) | (UINT64_C(0x3FE) << 52));
|
||||
}
|
||||
|
||||
#pragma clang attribute pop
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
#ifndef SCR_IEEE754_H
|
||||
#define SCR_IEEE754_H
|
||||
|
||||
/* Math's transcendental functions as V8 computes them (scr_ieee754.c). */
|
||||
double scr_ieee754_acos(double x);
|
||||
double scr_ieee754_acosh(double x);
|
||||
double scr_ieee754_asin(double x);
|
||||
double scr_ieee754_asinh(double x);
|
||||
double scr_ieee754_atan(double x);
|
||||
double scr_ieee754_atan2(double y, double x);
|
||||
double scr_ieee754_atanh(double x);
|
||||
double scr_ieee754_cbrt(double x);
|
||||
double scr_ieee754_cos(double x);
|
||||
double scr_ieee754_cosh(double x);
|
||||
double scr_ieee754_exp(double x);
|
||||
double scr_ieee754_expm1(double x);
|
||||
double scr_ieee754_log(double x);
|
||||
double scr_ieee754_log10(double x);
|
||||
double scr_ieee754_log1p(double x);
|
||||
double scr_ieee754_log2(double x);
|
||||
double scr_ieee754_sin(double x);
|
||||
double scr_ieee754_sinh(double x);
|
||||
double scr_ieee754_tan(double x);
|
||||
double scr_ieee754_tanh(double x);
|
||||
|
||||
#endif
|
||||
@@ -209,7 +209,7 @@ static double scr_js_number_arithmetic(double a, double b, const ScrStr *operati
|
||||
case '/': return a / b;
|
||||
case '%': return fmod(a, b);
|
||||
default:
|
||||
if (operation->len == 2) return (fabs(a) == 1 && !isfinite(b)) ? NAN : pow(a, b);
|
||||
if (operation->len == 2) return scr_math_pow(a, b);
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11160,9 +11160,8 @@ ScrDyn *scr_dyn_arithmetic(const ScrDyn *left, const ScrDyn *right, const ScrStr
|
||||
if (result) { out = scr_dyn_new_bigint(result); scr_bigint_release(result); }
|
||||
} else {
|
||||
double a = l->v.num, b = r->v.num;
|
||||
// ECMAScript differs from libm for ±1 ** ±Infinity and 1 ** NaN.
|
||||
double result = op == '-' ? a - b : op == '/' ? a / b : op == '%' ? fmod(a, b)
|
||||
: operation->len == 2 ? ((fabs(a) == 1 && !isfinite(b)) ? NAN : pow(a, b)) : a * b;
|
||||
: operation->len == 2 ? scr_math_pow(a, b) : a * b;
|
||||
out = scr_dyn_new_num(result);
|
||||
}
|
||||
scr_dyn_release(l);
|
||||
|
||||
@@ -6828,11 +6828,17 @@ double scr_math_round(double x) {
|
||||
return (r == 0.0 && x < 0.0) ? -0.0 : r;
|
||||
}
|
||||
|
||||
/* IEEE pow(±1, ±Infinity) and pow(1, NaN) return 1; ECMAScript requires
|
||||
* NaN. The other special cases, including signed zero and odd integer
|
||||
* powers, follow libm. */
|
||||
/* V8's math::pow: the platform pow, as Node's Math.pow and ** call it,
|
||||
* after the cases V8 answers itself. IEEE pow(±1, ±Infinity) and
|
||||
* pow(1, NaN) return 1; ECMAScript requires NaN. A NaN base becomes the
|
||||
* quiet NaN, and the exponents 2 and 0.5 answer base * base and
|
||||
* sqrt(base + 0) (+Infinity for an infinite base), as V8's optimizing
|
||||
* compilers do; pow can differ from those in the last bit. */
|
||||
double scr_math_pow(double base, double exponent) {
|
||||
if (isnan(exponent) || (isinf(exponent) && fabs(base) == 1.0)) return NAN;
|
||||
if (isnan(base)) base = NAN;
|
||||
if (exponent == 2.0) return base * base;
|
||||
if (exponent == 0.5) return isinf(base) ? INFINITY : sqrt(base + 0.0);
|
||||
/* 2 ** n for an integer n in the double range is an exact power of two,
|
||||
* the value pow returns; its bits are the exponent alone. */
|
||||
if (base == 2.0 && exponent >= -1074.0 && exponent <= 1023.0) {
|
||||
@@ -12815,8 +12821,15 @@ ScrStr *scr_num_to_string_radix_f64(double value, double requested) {
|
||||
bool negative = value < 0;
|
||||
value = fabs(value);
|
||||
double integer = floor(value), fraction = value - integer;
|
||||
double tolerance = 0.5 * (nextafter(value, INFINITY) - value);
|
||||
if (tolerance <= 0) tolerance = nextafter(0, INFINITY);
|
||||
/* The next double above the positive finite value: its bits plus one
|
||||
* (Infinity above the largest double). */
|
||||
uint64_t value_bits;
|
||||
memcpy(&value_bits, &value, sizeof value_bits);
|
||||
value_bits += 1;
|
||||
double next;
|
||||
memcpy(&next, &value_bits, sizeof next);
|
||||
double tolerance = 0.5 * (next - value);
|
||||
if (tolerance <= 0) tolerance = 0x1p-1074;
|
||||
if (fraction >= tolerance) {
|
||||
buffer[end++] = '.';
|
||||
do {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "scr_string_methods.h"
|
||||
#include "scr_qs.h"
|
||||
#include "scr_array.h"
|
||||
#include "scr_ieee754.h"
|
||||
#include "scr_regex.h"
|
||||
#include "scr_map.h"
|
||||
#include "scr_closure.h"
|
||||
|
||||
Vendored
+7
@@ -16,6 +16,13 @@ The x86_64 and AArch64 register-save/restore and `makecontext` logic is adapted
|
||||
|
||||
The double-to-shortest-decimal core of Ryū (d2s and its headers only — the float and fixed/exponent variants are not vendored). scr_number.c textually `#include`s d2s.c and calls its internal `d2d` digit generator; ECMA-262 digit *placement* stays in scr_number.c. The only modification to upstream files: `#include "ryu/x.h"` flattened to `#include "x.h"` in d2s.c and d2s_intrinsics.h so the directory is self-contained. To update, re-fetch the listed files at the new commit and re-apply that include flattening.
|
||||
|
||||
## v8-ieee754/ (derived source)
|
||||
|
||||
- Project: https://github.com/v8/v8, `src/base/ieee754.cc` as Node v24.22.0 bundles it (`deps/v8`, V8 13.6.233.17)
|
||||
- License: BSD-3-Clause (V8), with fdlibm's notice (see v8-ieee754/LICENSE)
|
||||
|
||||
V8's adaptation of fdlibm, the code behind Node's Math.sin, Math.exp, Math.cbrt and the other transcendental functions on every platform. `src/scr_ieee754.c` is a C port of the file, so compiled Math members print the values Node prints. The function bodies are unchanged; the port renames the entry points (`scr_ieee754_*`), gives the kernels internal linkage, and replaces V8's C++ helpers (bit casts, wraparound arithmetic, NaN and infinity constants) with C. `legacy::pow` is not ported: Node's Math.pow calls the platform pow. To update, re-fetch `deps/v8/src/base/ieee754.cc` at the new Node version and repeat the same conversion.
|
||||
|
||||
## quickjs-ng/
|
||||
|
||||
- Project: https://github.com/quickjs-ng/quickjs
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
This license applies to all parts of V8 that are not externally
|
||||
maintained libraries. The externally maintained libraries used by V8
|
||||
are:
|
||||
|
||||
- PCRE test suite, located in
|
||||
test/mjsunit/third_party/regexp-pcre/regexp-pcre.js. This is based on the
|
||||
test suite from PCRE-7.3, which is copyrighted by the University
|
||||
of Cambridge and Google, Inc. The copyright notice and license
|
||||
are embedded in regexp-pcre.js.
|
||||
|
||||
- Layout tests, located in test/mjsunit/third_party/object-keys. These are
|
||||
based on layout tests from webkit.org which are copyrighted by
|
||||
Apple Computer, Inc. and released under a 3-clause BSD license.
|
||||
|
||||
- Strongtalk assembler, the basis of the files assembler-arm-inl.h,
|
||||
assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h,
|
||||
assembler-ia32.cc, assembler-ia32.h, assembler-x64-inl.h,
|
||||
assembler-x64.cc, assembler-x64.h, assembler.cc and assembler.h.
|
||||
This code is copyrighted by Sun Microsystems Inc. and released
|
||||
under a 3-clause BSD license.
|
||||
|
||||
- Valgrind client API header, located at third_party/valgrind/valgrind.h
|
||||
This is released under the BSD license.
|
||||
|
||||
- The Wasm C/C++ API headers, located at third_party/wasm-api/wasm.{h,hh}
|
||||
This is released under the Apache license. The API's upstream prototype
|
||||
implementation also formed the basis of V8's implementation in
|
||||
src/wasm/c-api.cc.
|
||||
|
||||
These libraries have their own licenses; we recommend you read them,
|
||||
as their terms may differ from the terms below.
|
||||
|
||||
Further license information can be found in LICENSE files located in
|
||||
sub-directories.
|
||||
|
||||
Copyright 2014, the V8 project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
src/scr_ieee754.c is a port of V8's src/base/ieee754.cc, which V8 adapted
|
||||
from fdlibm. fdlibm's notice:
|
||||
|
||||
====================================================
|
||||
Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
|
||||
|
||||
Developed at SunSoft, a Sun Microsystems, Inc. business.
|
||||
Permission to use, copy, modify, and distribute this
|
||||
software is freely granted, provided that this notice
|
||||
is preserved.
|
||||
====================================================
|
||||
@@ -1,20 +1,17 @@
|
||||
// Math members execute in the island engine; results exit as ordinary
|
||||
// static numbers. Members with exact IEEE results print directly. The
|
||||
// transcendentals round through toFixed(9): the engine uses the system
|
||||
// libm and Node uses V8's fdlibm port, which can differ in the last ulp
|
||||
// (tan(1), asin(0.5), acos(0.5) do on macOS).
|
||||
// Math members print the values Node prints, the transcendentals
|
||||
// included: they are V8's fdlibm functions.
|
||||
console.log(Math.floor(7.6), Math.ceil(7.2), Math.round(7.5), Math.round(-7.5), Math.trunc(-7.9));
|
||||
console.log(Math.abs(-3.5), Math.sign(-12), Math.sign(0), Math.sign(4.2));
|
||||
console.log(Math.sqrt(144), Math.sqrt(2), Math.pow(2, 10), Math.pow(2, 0.5));
|
||||
console.log(Math.hypot(3, 4), Math.min(2, -9), Math.max(2, -9));
|
||||
console.log(Math.log2(1024), Math.log10(100000), Math.exp(0), Math.log(1));
|
||||
console.log(Math.PI, Math.E);
|
||||
console.log(Math.cbrt(27).toFixed(9), Math.exp(1).toFixed(9), Math.log(10).toFixed(9));
|
||||
console.log(Math.sin(1).toFixed(9), Math.cos(1).toFixed(9), Math.tan(1).toFixed(9));
|
||||
console.log(Math.asin(0.5).toFixed(9), Math.acos(0.5).toFixed(9));
|
||||
console.log(Math.atan(1).toFixed(9), Math.atan2(1, 1).toFixed(9), Math.atan2(-1, -1).toFixed(9));
|
||||
// Island results chain into static arithmetic like any number.
|
||||
console.log(Math.cbrt(27), Math.exp(1), Math.log(10));
|
||||
console.log(Math.sin(1), Math.cos(1), Math.tan(1));
|
||||
console.log(Math.asin(0.5), Math.acos(0.5));
|
||||
console.log(Math.atan(1), Math.atan2(1, 1), Math.atan2(-1, -1));
|
||||
// Math results chain into static arithmetic like any number.
|
||||
const area = Math.PI * Math.pow(2, 2);
|
||||
console.log(area.toFixed(6), Math.floor(area) + 1);
|
||||
// Non-finite edges: strict exits accept every engine number.
|
||||
// Non-finite results.
|
||||
console.log(Math.log(0), Math.log(-1), Math.sqrt(-1), Math.pow(0, -1));
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Math's transcendental functions print the values Node prints: each line
|
||||
// digests the result bits of one function over a few thousand inputs, so
|
||||
// a difference in the last bit of any result changes the line. Sample
|
||||
// values print in full, and pow's special cases follow V8's.
|
||||
const bits = new Float64Array(1);
|
||||
const words = new Uint32Array(bits.buffer);
|
||||
|
||||
function digest(values: number[]): string {
|
||||
let h1 = 0x811c9dc5;
|
||||
let h2 = 0x01000193;
|
||||
for (const v of values) {
|
||||
// NaN prints as NaN; its bits are not part of the result.
|
||||
bits[0] = Number.isNaN(v) ? 0 : v;
|
||||
h1 = Math.imul(h1 ^ words[0], 0x01000193) >>> 0;
|
||||
h2 = Math.imul(h2 ^ words[1], 0x01000193) >>> 0;
|
||||
}
|
||||
return `${h1}:${h2}`;
|
||||
}
|
||||
|
||||
let seed = 12345;
|
||||
function next(): number {
|
||||
seed = (seed * 1103515245 + 12345) % 2147483648;
|
||||
return seed / 2147483648;
|
||||
}
|
||||
|
||||
const wide: number[] = [];
|
||||
const unit: number[] = [];
|
||||
const small: number[] = [];
|
||||
for (let i = 0; i < 3000; i++) {
|
||||
wide.push((next() - 0.5) * Math.pow(10, Math.floor(next() * 12) - 4));
|
||||
unit.push(next() * 2 - 1);
|
||||
small.push((next() - 0.5) * 40);
|
||||
}
|
||||
const positive = wide.map((x) => Math.abs(x));
|
||||
const edges = [0, -0, 1, -1, 0.5, -0.5, 2, Math.PI, Math.E, 1e-300, 5e-324, 1e300, Infinity, -Infinity, NaN];
|
||||
|
||||
const unary: [string, (x: number) => number, number[]][] = [
|
||||
["sin", Math.sin, wide],
|
||||
["cos", Math.cos, wide],
|
||||
["tan", Math.tan, wide],
|
||||
["asin", Math.asin, unit],
|
||||
["acos", Math.acos, unit],
|
||||
["atan", Math.atan, wide],
|
||||
["sinh", Math.sinh, small],
|
||||
["cosh", Math.cosh, small],
|
||||
["tanh", Math.tanh, small],
|
||||
["asinh", Math.asinh, wide],
|
||||
["acosh", Math.acosh, positive.map((x) => x + 1)],
|
||||
["atanh", Math.atanh, unit],
|
||||
["exp", Math.exp, small],
|
||||
["expm1", Math.expm1, small],
|
||||
["log", Math.log, positive],
|
||||
["log1p", Math.log1p, positive],
|
||||
["log2", Math.log2, positive],
|
||||
["log10", Math.log10, positive],
|
||||
["cbrt", Math.cbrt, wide],
|
||||
["sqrt", Math.sqrt, positive],
|
||||
];
|
||||
for (const [name, fn, inputs] of unary) {
|
||||
console.log(name, digest(inputs.map(fn)), digest(edges.map(fn)), fn(0.3), fn(0.75));
|
||||
}
|
||||
|
||||
// Math.sin(i) and Math.cos(i * 0.37) for i below 4096: main's libm calls
|
||||
// printed 284 of these 8192 values differently from Node.
|
||||
const sines: number[] = [];
|
||||
const cosines: number[] = [];
|
||||
for (let i = 0; i < 4096; i++) {
|
||||
sines.push(Math.sin(i));
|
||||
cosines.push(Math.cos(i * 0.37));
|
||||
}
|
||||
console.log("sin(i)", digest(sines), "cos(i*0.37)", digest(cosines), Math.cos(2.59));
|
||||
|
||||
const atan2: number[] = [];
|
||||
const pow: number[] = [];
|
||||
for (let i = 0; i < wide.length; i++) {
|
||||
atan2.push(Math.atan2(wide[i], small[i]));
|
||||
pow.push(Math.pow(positive[i] % 50, small[i] / 4));
|
||||
}
|
||||
console.log("atan2", digest(atan2), Math.atan2(2.5, 3), Math.atan2(-0, -1));
|
||||
console.log("pow", digest(pow), Math.pow(1.1, 0.3), 1.0000001 ** 1e7);
|
||||
|
||||
// V8 answers these itself: x * x, the square root, and NaN for ±1 ** ±Infinity.
|
||||
const squares = wide.map((x) => x ** 2);
|
||||
const roots = positive.map((x) => Math.pow(x, 0.5));
|
||||
console.log("pow 2", digest(squares), "pow 0.5", digest(roots));
|
||||
// The same exponents when they are only known at run time.
|
||||
const two = Number(process.argv[2] ?? "2");
|
||||
const half = two / 4;
|
||||
console.log("pow runtime 2", digest(wide.map((x) => x ** two)), "0.5", digest(positive.map((x) => Math.pow(x, half))));
|
||||
console.log(Math.pow(-Infinity, 0.5), Math.pow(-0, 0.5), (-8) ** 0.5, 1 ** Infinity, (-1) ** -Infinity, NaN ** 0);
|
||||
// Rounding is exact: Math.floor, Math.ceil and Math.trunc over the same
|
||||
// inputs and over halves, signed zeros included.
|
||||
const halves = wide.map((x, i) => Math.round(x * 4) / 4 - (i % 2 === 0 ? 0 : 0.5));
|
||||
console.log("floor", digest(wide.map(Math.floor)), digest(halves.map(Math.floor)), digest(edges.map(Math.floor)));
|
||||
console.log("ceil", digest(wide.map(Math.ceil)), digest(halves.map(Math.ceil)), digest(edges.map(Math.ceil)));
|
||||
console.log("trunc", digest(wide.map(Math.trunc)), digest(halves.map(Math.trunc)), Object.is(Math.ceil(-0.5), -0), Object.is(Math.floor(-0), -0));
|
||||
console.log(Math.hypot(3, 4), Math.hypot(1e200, 1e200), 7 % 2.5, -7.5 % 2, Math.floor(-0.5), Math.ceil(-0.5), Math.round(2.5), Math.trunc(-4.7));
|
||||
@@ -0,0 +1,12 @@
|
||||
// Math functions and ** over untyped values print the values Node prints
|
||||
// (V8's fdlibm results, and V8's pow special cases).
|
||||
const values = JSON.parse("[0.3, 0.75, 2.5, -7.25, 1e-8, 123456.789, 1e21]");
|
||||
const rows = [];
|
||||
for (const x of values) {
|
||||
rows.push([Math.sin(x), Math.cos(x), Math.tan(x), Math.exp(x / 100), Math.cbrt(x), Math.atan2(x, 3)].join(" "));
|
||||
rows.push([Math.log(Math.abs(x)), Math.log10(Math.abs(x)), Math.sinh(x / 1e5), Math.cosh(x / 1e5), Math.expm1(x / 1e6)].join(" "));
|
||||
rows.push([x ** 2, x ** 0.5, Math.abs(x) ** 0.3, x % 1.5].join(" "));
|
||||
}
|
||||
console.log(rows.join("\n"));
|
||||
const edges = JSON.parse('[1, -1, null]');
|
||||
console.log(edges[0] ** Infinity, edges[1] ** -Infinity, Number(edges[2]) ** 0.5, (-Infinity) ** 0.5);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3357, "san": 4900 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3519, "san": 4613 }
|
||||
Reference in New Issue
Block a user