[Math] implement Math.random as TFJ builtin.
R=bmeurer@chromium.org BUG=v8:5049, v8:5086 Review-Url: https://codereview.chromium.org/2402363002 Cr-Commit-Position: refs/heads/master@{#40149}
This commit is contained in:
parent
3d5ae0f7d3
commit
0d8e52123e
1
BUILD.gn
1
BUILD.gn
@ -398,7 +398,6 @@ action("js2c") {
|
||||
"src/js/symbol.js",
|
||||
"src/js/array.js",
|
||||
"src/js/string.js",
|
||||
"src/js/math.js",
|
||||
"src/js/regexp.js",
|
||||
"src/js/arraybuffer.js",
|
||||
"src/js/typedarray.js",
|
||||
|
@ -1951,6 +1951,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
Handle<JSFunction> math_pow =
|
||||
SimpleInstallFunction(math, "pow", Builtins::kMathPow, 2, true);
|
||||
native_context()->set_math_pow(*math_pow);
|
||||
SimpleInstallFunction(math, "random", Builtins::kMathRandom, 0, true);
|
||||
SimpleInstallFunction(math, "round", Builtins::kMathRound, 1, true);
|
||||
SimpleInstallFunction(math, "sign", Builtins::kMathSign, 1, true);
|
||||
SimpleInstallFunction(math, "sin", Builtins::kMathSin, 1, true);
|
||||
@ -1993,6 +1994,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
|
||||
math, factory->NewStringFromAsciiChecked("SQRT2"),
|
||||
factory->NewNumber(std::sqrt(2.0)),
|
||||
static_cast<PropertyAttributes>(DONT_DELETE | DONT_ENUM | READ_ONLY));
|
||||
JSObject::AddProperty(
|
||||
math, factory->to_string_tag_symbol(),
|
||||
factory->NewStringFromAsciiChecked("Math"),
|
||||
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY));
|
||||
}
|
||||
|
||||
{ // -- A r r a y B u f f e r
|
||||
|
@ -452,6 +452,46 @@ void Builtins::Generate_MathPow(CodeStubAssembler* assembler) {
|
||||
assembler->Return(result);
|
||||
}
|
||||
|
||||
// ES6 section 20.2.2.27 Math.random ( )
|
||||
void Builtins::Generate_MathRandom(CodeStubAssembler* assembler) {
|
||||
using compiler::Node;
|
||||
|
||||
Node* context = assembler->Parameter(3);
|
||||
Node* native_context = assembler->LoadNativeContext(context);
|
||||
|
||||
// Load cache index.
|
||||
CodeStubAssembler::Variable smi_index(assembler,
|
||||
MachineRepresentation::kTagged);
|
||||
smi_index.Bind(assembler->LoadContextElement(
|
||||
native_context, Context::MATH_RANDOM_INDEX_INDEX));
|
||||
|
||||
// Cached random numbers are exhausted if index is 0. Go to slow path.
|
||||
CodeStubAssembler::Label if_cached(assembler);
|
||||
assembler->GotoIf(assembler->SmiAbove(smi_index.value(),
|
||||
assembler->SmiConstant(Smi::kZero)),
|
||||
&if_cached);
|
||||
|
||||
// Cache exhausted, populate the cache. Return value is the new index.
|
||||
smi_index.Bind(
|
||||
assembler->CallRuntime(Runtime::kGenerateRandomNumbers, context));
|
||||
assembler->Goto(&if_cached);
|
||||
|
||||
// Compute next index by decrement.
|
||||
assembler->Bind(&if_cached);
|
||||
Node* new_smi_index = assembler->SmiSub(
|
||||
smi_index.value(), assembler->SmiConstant(Smi::FromInt(1)));
|
||||
assembler->StoreContextElement(
|
||||
native_context, Context::MATH_RANDOM_INDEX_INDEX, new_smi_index);
|
||||
|
||||
// Load and return next cached random number.
|
||||
Node* array = assembler->LoadContextElement(native_context,
|
||||
Context::MATH_RANDOM_CACHE_INDEX);
|
||||
Node* random = assembler->LoadFixedDoubleArrayElement(
|
||||
array, new_smi_index, MachineType::Float64(), 0,
|
||||
CodeStubAssembler::SMI_PARAMETERS);
|
||||
assembler->Return(assembler->ChangeFloat64ToTagged(random));
|
||||
}
|
||||
|
||||
// ES6 section 20.2.2.28 Math.round ( x )
|
||||
void Builtins::Generate_MathRound(CodeStubAssembler* assembler) {
|
||||
Generate_MathRoundingOperation(assembler, &CodeStubAssembler::Float64Round);
|
||||
|
@ -450,6 +450,8 @@ namespace internal {
|
||||
ASM(MathMin) \
|
||||
/* ES6 section 20.2.2.26 Math.pow ( x, y ) */ \
|
||||
TFJ(MathPow, 3) \
|
||||
/* ES6 section 20.2.2.27 Math.random */ \
|
||||
TFJ(MathRandom, 1) \
|
||||
/* ES6 section 20.2.2.28 Math.round ( x ) */ \
|
||||
TFJ(MathRound, 2) \
|
||||
/* ES6 section 20.2.2.29 Math.sign ( x ) */ \
|
||||
|
@ -1092,6 +1092,13 @@ Node* CodeStubAssembler::LoadContextElement(Node* context, int slot_index) {
|
||||
return Load(MachineType::AnyTagged(), context, IntPtrConstant(offset));
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::StoreContextElement(Node* context, int slot_index,
|
||||
Node* value) {
|
||||
int offset = Context::SlotOffset(slot_index);
|
||||
return Store(MachineRepresentation::kTagged, context, IntPtrConstant(offset),
|
||||
value);
|
||||
}
|
||||
|
||||
Node* CodeStubAssembler::LoadNativeContext(Node* context) {
|
||||
return LoadContextElement(context, Context::NATIVE_CONTEXT_INDEX);
|
||||
}
|
||||
|
@ -298,6 +298,8 @@ class CodeStubAssembler : public compiler::CodeAssembler {
|
||||
|
||||
// Context manipulation
|
||||
compiler::Node* LoadContextElement(compiler::Node* context, int slot_index);
|
||||
compiler::Node* StoreContextElement(compiler::Node* context, int slot_index,
|
||||
compiler::Node* value);
|
||||
compiler::Node* LoadNativeContext(compiler::Node* context);
|
||||
|
||||
compiler::Node* LoadJSArrayElementsMap(ElementsKind kind,
|
||||
|
@ -185,6 +185,8 @@ enum ContextLookupFlags {
|
||||
V(MAP_CACHE_INDEX, Object, map_cache) \
|
||||
V(MAP_ITERATOR_MAP_INDEX, Map, map_iterator_map) \
|
||||
V(STRING_ITERATOR_MAP_INDEX, Map, string_iterator_map) \
|
||||
V(MATH_RANDOM_INDEX_INDEX, Smi, math_random_index) \
|
||||
V(MATH_RANDOM_CACHE_INDEX, Object, math_random_cache) \
|
||||
V(MESSAGE_LISTENERS_INDEX, TemplateList, message_listeners) \
|
||||
V(NATIVES_UTILS_OBJECT_INDEX, Object, natives_utils_object) \
|
||||
V(NORMALIZED_MAP_CACHE_INDEX, Object, normalized_map_cache) \
|
||||
|
@ -780,6 +780,7 @@ Handle<Context> Factory::NewNativeContext() {
|
||||
Handle<Context> context = Handle<Context>::cast(array);
|
||||
context->set_native_context(*context);
|
||||
context->set_errors_thrown(Smi::kZero);
|
||||
context->set_math_random_index(Smi::kZero);
|
||||
Handle<WeakCell> weak_cell = NewWeakCell(context);
|
||||
context->set_self_weak_cell(*weak_cell);
|
||||
DCHECK(context->IsNativeContext());
|
||||
|
@ -14,14 +14,13 @@ var GlobalMap = global.Map;
|
||||
var GlobalObject = global.Object;
|
||||
var GlobalSet = global.Set;
|
||||
var hashCodeSymbol = utils.ImportNow("hash_code_symbol");
|
||||
var MathRandom;
|
||||
var MathRandom = global.Math.random;
|
||||
var MapIterator;
|
||||
var SetIterator;
|
||||
var speciesSymbol = utils.ImportNow("species_symbol");
|
||||
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
|
||||
|
||||
utils.Import(function(from) {
|
||||
MathRandom = from.MathRandom;
|
||||
MapIterator = from.MapIterator;
|
||||
SetIterator = from.SetIterator;
|
||||
});
|
||||
|
@ -1,60 +0,0 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
(function(global, utils) {
|
||||
"use strict";
|
||||
|
||||
%CheckIsBootstrapping();
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Imports
|
||||
|
||||
// The first two slots are reserved to persist PRNG state.
|
||||
define kRandomNumberStart = 2;
|
||||
|
||||
var GlobalMath = global.Math;
|
||||
var NaN = %GetRootNaN();
|
||||
var nextRandomIndex = 0;
|
||||
var randomNumbers = UNDEFINED;
|
||||
var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol");
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// ECMA 262 - 15.8.2.14
|
||||
function MathRandom() {
|
||||
// While creating a startup snapshot, %GenerateRandomNumbers returns a
|
||||
// normal array containing a single random number, and has to be called for
|
||||
// every new random number.
|
||||
// Otherwise, it returns a pre-populated typed array of random numbers. The
|
||||
// first two elements are reserved for the PRNG state.
|
||||
if (nextRandomIndex <= kRandomNumberStart) {
|
||||
randomNumbers = %GenerateRandomNumbers(randomNumbers);
|
||||
if (%_IsTypedArray(randomNumbers)) {
|
||||
nextRandomIndex = %_TypedArrayGetLength(randomNumbers);
|
||||
} else {
|
||||
nextRandomIndex = randomNumbers.length;
|
||||
}
|
||||
}
|
||||
return randomNumbers[--nextRandomIndex];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
%AddNamedProperty(GlobalMath, toStringTagSymbol, "Math", READ_ONLY | DONT_ENUM);
|
||||
|
||||
// Set up non-enumerable functions of the Math object and
|
||||
// set their names.
|
||||
utils.InstallFunctions(GlobalMath, DONT_ENUM, [
|
||||
"random", MathRandom,
|
||||
]);
|
||||
|
||||
%SetForceInlineFlag(MathRandom);
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Exports
|
||||
|
||||
utils.Export(function(to) {
|
||||
to.MathRandom = MathRandom;
|
||||
});
|
||||
|
||||
})
|
@ -15,58 +15,49 @@ namespace internal {
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GenerateRandomNumbers) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 1);
|
||||
if (isolate->serializer_enabled()) {
|
||||
// Random numbers in the snapshot are not really that random. And we cannot
|
||||
// return a typed array as it cannot be serialized. To make calling
|
||||
// Math.random possible when creating a custom startup snapshot, we simply
|
||||
// return a normal array with a single random number.
|
||||
Handle<HeapNumber> random_number = isolate->factory()->NewHeapNumber(
|
||||
isolate->random_number_generator()->NextDouble());
|
||||
Handle<FixedArray> array_backing = isolate->factory()->NewFixedArray(1);
|
||||
array_backing->set(0, *random_number);
|
||||
return *isolate->factory()->NewJSArrayWithElements(array_backing);
|
||||
}
|
||||
DCHECK(args.length() == 0);
|
||||
|
||||
static const int kState0Offset = 0;
|
||||
static const int kState1Offset = 1;
|
||||
static const int kRandomBatchSize = 64;
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, maybe_typed_array, 0);
|
||||
Handle<JSTypedArray> typed_array;
|
||||
// Allocate typed array if it does not yet exist.
|
||||
if (maybe_typed_array->IsJSTypedArray()) {
|
||||
typed_array = Handle<JSTypedArray>::cast(maybe_typed_array);
|
||||
Handle<Context> native_context = isolate->native_context();
|
||||
DCHECK_EQ(0, native_context->math_random_index()->value());
|
||||
|
||||
static const int kCacheSize = 64;
|
||||
static const int kState0Offset = kCacheSize - 1;
|
||||
static const int kState1Offset = kState0Offset - 1;
|
||||
// The index is decremented before used to access the cache.
|
||||
static const int kInitialIndex = kState1Offset;
|
||||
|
||||
Handle<FixedDoubleArray> cache;
|
||||
uint64_t state0 = 0;
|
||||
uint64_t state1 = 0;
|
||||
if (native_context->math_random_cache()->IsFixedDoubleArray()) {
|
||||
cache = Handle<FixedDoubleArray>(
|
||||
FixedDoubleArray::cast(native_context->math_random_cache()), isolate);
|
||||
state0 = double_to_uint64(cache->get_scalar(kState0Offset));
|
||||
state1 = double_to_uint64(cache->get_scalar(kState1Offset));
|
||||
} else {
|
||||
static const int kByteLength = kRandomBatchSize * kDoubleSize;
|
||||
Handle<JSArrayBuffer> buffer =
|
||||
isolate->factory()->NewJSArrayBuffer(SharedFlag::kNotShared, TENURED);
|
||||
JSArrayBuffer::SetupAllocatingData(buffer, isolate, kByteLength, true,
|
||||
SharedFlag::kNotShared);
|
||||
typed_array = isolate->factory()->NewJSTypedArray(
|
||||
kExternalFloat64Array, buffer, 0, kRandomBatchSize);
|
||||
cache = Handle<FixedDoubleArray>::cast(
|
||||
isolate->factory()->NewFixedDoubleArray(kCacheSize, TENURED));
|
||||
native_context->set_math_random_cache(*cache);
|
||||
// Initialize state if not yet initialized.
|
||||
while (state0 == 0 || state1 == 0) {
|
||||
isolate->random_number_generator()->NextBytes(&state0, sizeof(state0));
|
||||
isolate->random_number_generator()->NextBytes(&state1, sizeof(state1));
|
||||
}
|
||||
}
|
||||
|
||||
DisallowHeapAllocation no_gc;
|
||||
double* array =
|
||||
reinterpret_cast<double*>(typed_array->GetBuffer()->backing_store());
|
||||
// Fetch existing state.
|
||||
uint64_t state0 = double_to_uint64(array[kState0Offset]);
|
||||
uint64_t state1 = double_to_uint64(array[kState1Offset]);
|
||||
// Initialize state if not yet initialized.
|
||||
while (state0 == 0 || state1 == 0) {
|
||||
isolate->random_number_generator()->NextBytes(&state0, sizeof(state0));
|
||||
isolate->random_number_generator()->NextBytes(&state1, sizeof(state1));
|
||||
}
|
||||
FixedDoubleArray* raw_cache = *cache;
|
||||
// Create random numbers.
|
||||
for (int i = kState1Offset + 1; i < kRandomBatchSize; i++) {
|
||||
for (int i = 0; i < kInitialIndex; i++) {
|
||||
// Generate random numbers using xorshift128+.
|
||||
base::RandomNumberGenerator::XorShift128(&state0, &state1);
|
||||
array[i] = base::RandomNumberGenerator::ToDouble(state0, state1);
|
||||
raw_cache->set(i, base::RandomNumberGenerator::ToDouble(state0, state1));
|
||||
}
|
||||
|
||||
// Persist current state.
|
||||
array[kState0Offset] = uint64_to_double(state0);
|
||||
array[kState1Offset] = uint64_to_double(state1);
|
||||
return *typed_array;
|
||||
raw_cache->set(kState0Offset, uint64_to_double(state0));
|
||||
raw_cache->set(kState1Offset, uint64_to_double(state1));
|
||||
return Smi::FromInt(kInitialIndex);
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -351,7 +351,7 @@ namespace internal {
|
||||
F(LiveEditCompareStrings, 2, 1) \
|
||||
F(LiveEditRestartFrame, 2, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_MATHS(F) F(GenerateRandomNumbers, 1, 1)
|
||||
#define FOR_EACH_INTRINSIC_MATHS(F) F(GenerateRandomNumbers, 0, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_NUMBERS(F) \
|
||||
F(IsValidSmi, 1, 1) \
|
||||
|
@ -33,6 +33,9 @@ void PartialSerializer::Serialize(Object** o) {
|
||||
context->set(Context::NEXT_CONTEXT_LINK,
|
||||
isolate_->heap()->undefined_value());
|
||||
DCHECK(!context->global_object()->IsUndefined(context->GetIsolate()));
|
||||
// Reset math random cache to get fresh random numbers.
|
||||
context->set_math_random_index(Smi::kZero);
|
||||
context->set_math_random_cache(isolate_->heap()->undefined_value());
|
||||
}
|
||||
}
|
||||
VisitPointer(o);
|
||||
|
@ -2220,7 +2220,6 @@
|
||||
'js/symbol.js',
|
||||
'js/array.js',
|
||||
'js/string.js',
|
||||
'js/math.js',
|
||||
'js/regexp.js',
|
||||
'js/arraybuffer.js',
|
||||
'js/typedarray.js',
|
||||
|
@ -78,7 +78,7 @@ bytecodes: [
|
||||
/* 15 S> */ B(LdrUndefined), R(0),
|
||||
B(CreateArrayLiteral), U8(0), U8(0), U8(9),
|
||||
B(Star), R(1),
|
||||
B(CallJSRuntime), U8(143), R(0), U8(2),
|
||||
B(CallJSRuntime), U8(145), R(0), U8(2),
|
||||
/* 44 S> */ B(Return),
|
||||
]
|
||||
constant pool: [
|
||||
|
@ -80,9 +80,9 @@ assertTrue(extension_count == 2 || extension_count == 3);
|
||||
assertTrue(normal_count == 2 || normal_count == 3);
|
||||
|
||||
// Test a builtins script.
|
||||
var math_script = Debug.findScript('native math.js');
|
||||
assertEquals('native math.js', math_script.name);
|
||||
assertEquals(Debug.ScriptType.Native, math_script.type);
|
||||
var array_script = Debug.findScript('native array.js');
|
||||
assertEquals('native array.js', array_script.name);
|
||||
assertEquals(Debug.ScriptType.Native, array_script.type);
|
||||
|
||||
// Test a debugger script.
|
||||
var debug_delay_script = Debug.findScript('native debug.js');
|
||||
|
@ -83,7 +83,6 @@ function testScriptMirror(f, file_name, file_lines, type, compilation_type,
|
||||
|
||||
|
||||
// Test the script mirror for different functions.
|
||||
testScriptMirror(function(){}, 'mirror-script.js', 90, 2, 0);
|
||||
testScriptMirror(Math.random, 'native math.js', -1, 0, 0);
|
||||
testScriptMirror(eval('(function(){})'), null, 1, 2, 1, '(function(){})', 87);
|
||||
testScriptMirror(eval('(function(){\n })'), null, 2, 2, 1, '(function(){\n })', 88);
|
||||
testScriptMirror(function(){}, 'mirror-script.js', 89, 2, 0);
|
||||
testScriptMirror(eval('(function(){})'), null, 1, 2, 1, '(function(){})', 86);
|
||||
testScriptMirror(eval('(function(){\n })'), null, 2, 2, 1, '(function(){\n })', 87);
|
||||
|
Loading…
Reference in New Issue
Block a user