Custom call IC for Math.floor.

Review URL: http://codereview.chromium.org/3327022

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5499 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
vitalyr@chromium.org 2010-09-21 12:54:12 +00:00
parent b2f444f6e6
commit a6bbe9820e
13 changed files with 437 additions and 45 deletions

View File

@ -1633,6 +1633,16 @@ Object* CallStubCompiler::CompileStringFromCharCodeCall(
}
Object* CallStubCompiler::CompileMathFloorCall(Object* object,
JSObject* holder,
JSGlobalPropertyCell* cell,
JSFunction* function,
String* name) {
// TODO(872): implement this.
return Heap::undefined_value();
}
Object* CallStubCompiler::CompileCallConstant(Object* object,
JSObject* holder,
JSFunction* function,

View File

@ -1344,33 +1344,40 @@ bool Genesis::InstallNatives() {
}
static void InstallCustomCallGenerator(
Handle<JSFunction> holder_function,
CallStubCompiler::CustomGeneratorOwner owner_flag,
const char* function_name,
int id) {
Handle<JSObject> owner;
if (owner_flag == CallStubCompiler::FUNCTION) {
owner = Handle<JSObject>::cast(holder_function);
} else {
ASSERT(owner_flag == CallStubCompiler::INSTANCE_PROTOTYPE);
owner = Handle<JSObject>(
JSObject::cast(holder_function->instance_prototype()));
static Handle<JSObject> ResolveCustomCallGeneratorHolder(
Handle<Context> global_context,
const char* holder_expr) {
Handle<GlobalObject> global(global_context->global());
const char* period_pos = strchr(holder_expr, '.');
if (period_pos == NULL) {
return Handle<JSObject>::cast(
GetProperty(global, Factory::LookupAsciiSymbol(holder_expr)));
}
ASSERT_EQ(".prototype", period_pos);
Vector<const char> property(holder_expr, period_pos - holder_expr);
Handle<JSFunction> function = Handle<JSFunction>::cast(
GetProperty(global, Factory::LookupSymbol(property)));
return Handle<JSObject>(JSObject::cast(function->prototype()));
}
static void InstallCustomCallGenerator(Handle<JSObject> holder,
const char* function_name,
int id) {
Handle<String> name = Factory::LookupAsciiSymbol(function_name);
Handle<JSFunction> function(JSFunction::cast(owner->GetProperty(*name)));
Handle<JSFunction> function(JSFunction::cast(holder->GetProperty(*name)));
function->shared()->set_function_data(Smi::FromInt(id));
}
void Genesis::InstallCustomCallGenerators() {
HandleScope scope;
#define INSTALL_CALL_GENERATOR(holder_fun, owner_flag, fun_name, name) \
{ \
Handle<JSFunction> holder(global_context()->holder_fun##_function()); \
const int id = CallStubCompiler::k##name##CallGenerator; \
InstallCustomCallGenerator(holder, CallStubCompiler::owner_flag, \
#fun_name, id); \
#define INSTALL_CALL_GENERATOR(holder_expr, fun_name, name) \
{ \
Handle<JSObject> holder = ResolveCustomCallGeneratorHolder( \
global_context(), #holder_expr); \
const int id = CallStubCompiler::k##name##CallGenerator; \
InstallCustomCallGenerator(holder, #fun_name, id); \
}
CUSTOM_CALL_IC_GENERATORS(INSTALL_CALL_GENERATOR)
#undef INSTALL_CALL_GENERATOR

View File

@ -2205,6 +2205,16 @@ void Assembler::sqrtsd(XMMRegister dst, XMMRegister src) {
}
void Assembler::andpd(XMMRegister dst, XMMRegister src) {
EnsureSpace ensure_space(this);
last_pc_ = pc_;
EMIT(0x66);
EMIT(0x0F);
EMIT(0x54);
emit_sse_operand(dst, src);
}
void Assembler::ucomisd(XMMRegister dst, XMMRegister src) {
ASSERT(CpuFeatures::IsEnabled(SSE2));
EnsureSpace ensure_space(this);
@ -2227,7 +2237,29 @@ void Assembler::movmskpd(Register dst, XMMRegister src) {
}
void Assembler::movdqa(const Operand& dst, XMMRegister src ) {
void Assembler::cmpltsd(XMMRegister dst, XMMRegister src) {
ASSERT(CpuFeatures::IsEnabled(SSE2));
EnsureSpace ensure_space(this);
last_pc_ = pc_;
EMIT(0xF2);
EMIT(0x0F);
EMIT(0xC2);
emit_sse_operand(dst, src);
EMIT(1); // LT == 1
}
void Assembler::movaps(XMMRegister dst, XMMRegister src) {
ASSERT(CpuFeatures::IsEnabled(SSE2));
EnsureSpace ensure_space(this);
last_pc_ = pc_;
EMIT(0x0F);
EMIT(0x28);
emit_sse_operand(dst, src);
}
void Assembler::movdqa(const Operand& dst, XMMRegister src) {
ASSERT(CpuFeatures::IsEnabled(SSE2));
EnsureSpace ensure_space(this);
last_pc_ = pc_;
@ -2384,6 +2416,19 @@ void Assembler::ptest(XMMRegister dst, XMMRegister src) {
emit_sse_operand(dst, src);
}
void Assembler::psllq(XMMRegister reg, int8_t imm8) {
ASSERT(CpuFeatures::IsEnabled(SSE2));
EnsureSpace ensure_space(this);
last_pc_ = pc_;
EMIT(0x66);
EMIT(0x0F);
EMIT(0x73);
emit_sse_operand(esi, reg); // esi == 6
EMIT(imm8);
}
void Assembler::emit_sse_operand(XMMRegister reg, const Operand& adr) {
Register ireg = { reg.code() };
emit_operand(ireg, adr);

View File

@ -795,9 +795,15 @@ class Assembler : public Malloced {
void xorpd(XMMRegister dst, XMMRegister src);
void sqrtsd(XMMRegister dst, XMMRegister src);
void andpd(XMMRegister dst, XMMRegister src);
void ucomisd(XMMRegister dst, XMMRegister src);
void movmskpd(Register dst, XMMRegister src);
void cmpltsd(XMMRegister dst, XMMRegister src);
void movaps(XMMRegister dst, XMMRegister src);
void movdqa(XMMRegister dst, const Operand& src);
void movdqa(const Operand& dst, XMMRegister src);
void movdqu(XMMRegister dst, const Operand& src);
@ -813,6 +819,8 @@ class Assembler : public Malloced {
void pxor(XMMRegister dst, XMMRegister src);
void ptest(XMMRegister dst, XMMRegister src);
void psllq(XMMRegister reg, int8_t imm8);
// Parallel XMM operations.
void movntdqa(XMMRegister src, const Operand& dst);
void movntdq(const Operand& dst, XMMRegister src);

View File

@ -958,6 +958,14 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer,
} else if (f0byte == 0xA2 || f0byte == 0x31) {
AppendToBuffer("%s", f0mnem);
data += 2;
} else if (f0byte == 0x28) {
data += 2;
int mod, regop, rm;
get_modrm(*data, &mod, &regop, &rm);
AppendToBuffer("movaps %s,%s",
NameOfXMMRegister(regop),
NameOfXMMRegister(rm));
data++;
} else if ((f0byte & 0xF0) == 0x80) {
data += JumpConditional(data, branch_hint);
} else if (f0byte == 0xBE || f0byte == 0xBF || f0byte == 0xB6 ||
@ -1157,6 +1165,23 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer,
NameOfXMMRegister(regop),
NameOfXMMRegister(rm));
data++;
} else if (*data == 0x73) {
data++;
int mod, regop, rm;
get_modrm(*data, &mod, &regop, &rm);
int8_t imm8 = static_cast<int8_t>(data[1]);
AppendToBuffer("psllq %s,%d",
NameOfXMMRegister(rm),
static_cast<int>(imm8));
data += 2;
} else if (*data == 0x54) {
data++;
int mod, regop, rm;
get_modrm(*data, &mod, &regop, &rm);
AppendToBuffer("andpd %s,%s",
NameOfXMMRegister(regop),
NameOfXMMRegister(rm));
data++;
} else {
UnimplementedInstruction();
}
@ -1275,6 +1300,23 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer,
NameOfXMMRegister(rm));
data++;
}
} else if (b2 == 0xC2) {
// Intel manual 2A, Table 3-18.
const char* const pseudo_op[] = {
"cmpeqsd",
"cmpltsd",
"cmplesd",
"cmpunordsd",
"cmpneqsd",
"cmpnltsd",
"cmpnlesd",
"cmpordsd"
};
AppendToBuffer("%s %s,%s",
pseudo_op[data[1]],
NameOfXMMRegister(regop),
NameOfXMMRegister(rm));
data += 2;
} else {
if (mod != 0x3) {
AppendToBuffer("%s %s,", mnem, NameOfXMMRegister(regop));
@ -1368,7 +1410,7 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer,
" %s",
tmp_buffer_.start());
return instr_len;
}
} // NOLINT (function is too long)
//------------------------------------------------------------------------------

View File

@ -1553,6 +1553,17 @@ void MacroAssembler::ConvertToInt32(Register dst,
}
void MacroAssembler::LoadPowerOf2(XMMRegister dst,
Register scratch,
int power) {
ASSERT(is_uintn(power + HeapNumber::kExponentBias,
HeapNumber::kExponentBits));
mov(scratch, Immediate(power + HeapNumber::kExponentBias));
movd(dst, Operand(scratch));
psllq(dst, HeapNumber::kMantissaBits);
}
void MacroAssembler::JumpIfInstanceTypeIsNotSequentialAscii(
Register instance_type,
Register scratch,

View File

@ -258,6 +258,8 @@ class MacroAssembler: public Assembler {
TypeInfo info,
Label* on_not_int32);
void LoadPowerOf2(XMMRegister dst, Register scratch, int power);
// Abort execution if argument is not a number. Used in debug code.
void AbortIfNotNumber(Register object);

View File

@ -1819,6 +1819,131 @@ Object* CallStubCompiler::CompileStringFromCharCodeCall(
}
Object* CallStubCompiler::CompileMathFloorCall(Object* object,
JSObject* holder,
JSGlobalPropertyCell* cell,
JSFunction* function,
String* name) {
// ----------- S t a t e -------------
// -- ecx : name
// -- esp[0] : return address
// -- esp[(argc - n) * 4] : arg[n] (zero-based)
// -- ...
// -- esp[(argc + 1) * 4] : receiver
// -----------------------------------
if (!CpuFeatures::IsSupported(SSE2)) return Heap::undefined_value();
CpuFeatures::Scope use_sse2(SSE2);
const int argc = arguments().immediate();
// If the object is not a JSObject or we got an unexpected number of
// arguments, bail out to the regular call.
if (!object->IsJSObject() || argc != 1) return Heap::undefined_value();
Label miss;
GenerateNameCheck(name, &miss);
if (cell == NULL) {
__ mov(edx, Operand(esp, 2 * kPointerSize));
STATIC_ASSERT(kSmiTag == 0);
__ test(edx, Immediate(kSmiTagMask));
__ j(zero, &miss);
CheckPrototypes(JSObject::cast(object), edx, holder, ebx, eax, edi, name,
&miss);
} else {
ASSERT(cell->value() == function);
GenerateGlobalReceiverCheck(JSObject::cast(object), holder, name, &miss);
GenerateLoadFunctionFromCell(cell, function, &miss);
}
// Load the (only) argument into eax.
__ mov(eax, Operand(esp, 1 * kPointerSize));
// Check if the argument is a smi.
Label smi;
STATIC_ASSERT(kSmiTag == 0);
__ test(eax, Immediate(kSmiTagMask));
__ j(zero, &smi);
// Check if the argument is a heap number and load its value into xmm0.
Label slow;
__ CheckMap(eax, Factory::heap_number_map(), &slow, true);
__ movdbl(xmm0, FieldOperand(eax, HeapNumber::kValueOffset));
// Check if the argument is strictly positive. Note this also
// discards NaN.
__ xorpd(xmm1, xmm1);
__ ucomisd(xmm0, xmm1);
__ j(below_equal, &slow);
// Do a truncating conversion.
__ cvttsd2si(eax, Operand(xmm0));
// Check if the result fits into a smi. Note this also checks for
// 0x80000000 which signals a failed conversion.
Label wont_fit_into_smi;
__ test(eax, Immediate(0xc0000000));
__ j(not_zero, &wont_fit_into_smi);
// Smi tag and return.
__ SmiTag(eax);
__ bind(&smi);
__ ret(2 * kPointerSize);
// Check if the argument is < 2^kMantissaBits.
Label already_round;
__ bind(&wont_fit_into_smi);
__ LoadPowerOf2(xmm1, ebx, HeapNumber::kMantissaBits);
__ ucomisd(xmm0, xmm1);
__ j(above_equal, &already_round);
// Save a copy of the argument.
__ movaps(xmm2, xmm0);
// Compute (argument + 2^kMantissaBits) - 2^kMantissaBits.
__ addsd(xmm0, xmm1);
__ subsd(xmm0, xmm1);
// Compare the argument and the tentative result to get the right mask:
// if xmm2 < xmm0:
// xmm2 = 1...1
// else:
// xmm2 = 0...0
__ cmpltsd(xmm2, xmm0);
// Subtract 1 if the argument was less than the tentative result.
__ LoadPowerOf2(xmm1, ebx, 0);
__ andpd(xmm1, xmm2);
__ subsd(xmm0, xmm1);
// Return a new heap number.
__ AllocateHeapNumber(eax, ebx, edx, &slow);
__ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0);
__ ret(2 * kPointerSize);
// Return the argument (when it's an already round heap number).
__ bind(&already_round);
__ mov(eax, Operand(esp, 1 * kPointerSize));
__ ret(2 * kPointerSize);
// Tail call the full function. We do not have to patch the receiver
// because the function makes no use of it.
__ bind(&slow);
__ InvokeFunction(function, arguments(), JUMP_FUNCTION);
__ bind(&miss);
// ecx: function name.
Object* obj = GenerateMissBranch();
if (obj->IsFailure()) return obj;
// Return the generated code.
return (cell == NULL) ? GetCode(function) : GetCode(NORMAL, name);
}
Object* CallStubCompiler::CompileCallConstant(Object* object,
JSObject* holder,
JSFunction* function,

View File

@ -1227,7 +1227,7 @@ Object* CallStubCompiler::CompileCustomCall(int generator_id,
String* fname) {
ASSERT(generator_id >= 0 && generator_id < kNumCallGenerators);
switch (generator_id) {
#define CALL_GENERATOR_CASE(ignored1, ignored2, ignored3, name) \
#define CALL_GENERATOR_CASE(ignored1, ignored2, name) \
case k##name##CallGenerator: \
return CallStubCompiler::Compile##name##Call(object, \
holder, \

View File

@ -614,29 +614,25 @@ class KeyedStoreStubCompiler: public StubCompiler {
// Installation of custom call generators for the selected builtins is
// handled by the bootstrapper.
//
// Each entry has a name of a global function (lowercased), a flag
// controlling whether the generator is set on the function itself or
// on its instance prototype, a name of a builtin function on the
// function or its instance prototype (the one the generator is set
// for), and a name of a generator itself (used to build ids and
// generator function names).
#define CUSTOM_CALL_IC_GENERATORS(V) \
V(array, INSTANCE_PROTOTYPE, push, ArrayPush) \
V(array, INSTANCE_PROTOTYPE, pop, ArrayPop) \
V(string, INSTANCE_PROTOTYPE, charCodeAt, StringCharCodeAt) \
V(string, INSTANCE_PROTOTYPE, charAt, StringCharAt) \
V(string, FUNCTION, fromCharCode, StringFromCharCode)
// Each entry has a name of a global object property holding an object
// optionally followed by ".prototype" (this controls whether the
// generator is set on the object itself or, in case it's a function,
// on the its instance prototype), a name of a builtin function on the
// object (the one the generator is set for), and a name of the
// generator (used to build ids and generator function names).
#define CUSTOM_CALL_IC_GENERATORS(V) \
V(Array.prototype, push, ArrayPush) \
V(Array.prototype, pop, ArrayPop) \
V(String.prototype, charCodeAt, StringCharCodeAt) \
V(String.prototype, charAt, StringCharAt) \
V(String, fromCharCode, StringFromCharCode) \
V(Math, floor, MathFloor)
class CallStubCompiler: public StubCompiler {
public:
enum CustomGeneratorOwner {
FUNCTION,
INSTANCE_PROTOTYPE
};
enum {
#define DECLARE_CALL_GENERATOR_ID(ignored1, ignore2, ignored3, name) \
#define DECLARE_CALL_GENERATOR_ID(ignored1, ignore2, name) \
k##name##CallGenerator,
CUSTOM_CALL_IC_GENERATORS(DECLARE_CALL_GENERATOR_ID)
#undef DECLARE_CALL_GENERATOR_ID
@ -675,11 +671,11 @@ class CallStubCompiler: public StubCompiler {
JSFunction* function,
String* name);
#define DECLARE_CALL_GENERATOR(ignored1, ignored2, ignored3, name) \
Object* Compile##name##Call(Object* object, \
JSObject* holder, \
JSGlobalPropertyCell* cell, \
JSFunction* function, \
#define DECLARE_CALL_GENERATOR(ignored1, ignored2, name) \
Object* Compile##name##Call(Object* object, \
JSObject* holder, \
JSGlobalPropertyCell* cell, \
JSFunction* function, \
String* fname);
CUSTOM_CALL_IC_GENERATORS(DECLARE_CALL_GENERATOR)
#undef DECLARE_CALL_GENERATOR

View File

@ -1548,6 +1548,16 @@ Object* CallStubCompiler::CompileStringFromCharCodeCall(
}
Object* CallStubCompiler::CompileMathFloorCall(Object* object,
JSObject* holder,
JSGlobalPropertyCell* cell,
JSFunction* function,
String* name) {
// TODO(872): implement this.
return Heap::undefined_value();
}
Object* CallStubCompiler::CompileCallInterceptor(JSObject* object,
JSObject* holder,
String* name) {

View File

@ -412,6 +412,24 @@ TEST(DisasmIa320) {
}
}
// andpd, cmpltsd, movaps, psllq.
{
if (CpuFeatures::IsSupported(SSE2)) {
CpuFeatures::Scope fscope(SSE2);
__ andpd(xmm0, xmm1);
__ andpd(xmm1, xmm2);
__ cmpltsd(xmm0, xmm1);
__ cmpltsd(xmm1, xmm2);
__ movaps(xmm0, xmm1);
__ movaps(xmm1, xmm2);
__ psllq(xmm0, 17);
__ psllq(xmm1, 42);
}
}
__ ret(0);
CodeDesc desc;

118
test/mjsunit/math-floor.js Normal file
View File

@ -0,0 +1,118 @@
// Copyright 2010 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.
// Flags: --max-new-space-size=262144
function zero() {
var x = 0.5;
return (function() { return x - 0.5; })();
}
function test() {
assertEquals(0, Math.floor(0));
assertEquals(0, Math.floor(zero()));
assertEquals(1/-0, 1/Math.floor(-0)); // 0 == -0, so we use reciprocals.
assertEquals(Infinity, Math.floor(Infinity));
assertEquals(-Infinity, Math.floor(-Infinity));
assertNaN(Math.floor(NaN));
assertEquals(0, Math.floor(0.1));
assertEquals(0, Math.floor(0.5));
assertEquals(0, Math.floor(0.7));
assertEquals(-1, Math.floor(-0.1));
assertEquals(-1, Math.floor(-0.5));
assertEquals(-1, Math.floor(-0.7));
assertEquals(1, Math.floor(1));
assertEquals(1, Math.floor(1.1));
assertEquals(1, Math.floor(1.5));
assertEquals(1, Math.floor(1.7));
assertEquals(-1, Math.floor(-1));
assertEquals(-2, Math.floor(-1.1));
assertEquals(-2, Math.floor(-1.5));
assertEquals(-2, Math.floor(-1.7));
assertEquals(0, Math.floor(Number.MIN_VALUE));
assertEquals(-1, Math.floor(-Number.MIN_VALUE));
assertEquals(Number.MAX_VALUE, Math.floor(Number.MAX_VALUE));
assertEquals(-Number.MAX_VALUE, Math.floor(-Number.MAX_VALUE));
assertEquals(Infinity, Math.floor(Infinity));
assertEquals(-Infinity, Math.floor(-Infinity));
// 2^30 is a smi boundary.
var two_30 = 1 << 30;
assertEquals(two_30, Math.floor(two_30));
assertEquals(two_30, Math.floor(two_30 + 0.1));
assertEquals(two_30, Math.floor(two_30 + 0.5));
assertEquals(two_30, Math.floor(two_30 + 0.7));
assertEquals(two_30 - 1, Math.floor(two_30 - 1));
assertEquals(two_30 - 1, Math.floor(two_30 - 1 + 0.1));
assertEquals(two_30 - 1, Math.floor(two_30 - 1 + 0.5));
assertEquals(two_30 - 1, Math.floor(two_30 - 1 + 0.7));
assertEquals(-two_30, Math.floor(-two_30));
assertEquals(-two_30, Math.floor(-two_30 + 0.1));
assertEquals(-two_30, Math.floor(-two_30 + 0.5));
assertEquals(-two_30, Math.floor(-two_30 + 0.7));
assertEquals(-two_30 + 1, Math.floor(-two_30 + 1));
assertEquals(-two_30 + 1, Math.floor(-two_30 + 1 + 0.1));
assertEquals(-two_30 + 1, Math.floor(-two_30 + 1 + 0.5));
assertEquals(-two_30 + 1, Math.floor(-two_30 + 1 + 0.7));
// 2^52 is a precision boundary.
var two_52 = (1 << 30) * (1 << 22);
assertEquals(two_52, Math.floor(two_52));
assertEquals(two_52, Math.floor(two_52 + 0.1));
assertEquals(two_52, two_52 + 0.5);
assertEquals(two_52, Math.floor(two_52 + 0.5));
assertEquals(two_52 + 1, two_52 + 0.7);
assertEquals(two_52 + 1, Math.floor(two_52 + 0.7));
assertEquals(two_52 - 1, Math.floor(two_52 - 1));
assertEquals(two_52 - 1, Math.floor(two_52 - 1 + 0.1));
assertEquals(two_52 - 1, Math.floor(two_52 - 1 + 0.5));
assertEquals(two_52 - 1, Math.floor(two_52 - 1 + 0.7));
assertEquals(-two_52, Math.floor(-two_52));
assertEquals(-two_52, Math.floor(-two_52 + 0.1));
assertEquals(-two_52, Math.floor(-two_52 + 0.5));
assertEquals(-two_52, Math.floor(-two_52 + 0.7));
assertEquals(-two_52 + 1, Math.floor(-two_52 + 1));
assertEquals(-two_52 + 1, Math.floor(-two_52 + 1 + 0.1));
assertEquals(-two_52 + 1, Math.floor(-two_52 + 1 + 0.5));
assertEquals(-two_52 + 1, Math.floor(-two_52 + 1 + 0.7));
}
// Test in a loop to cover the custom IC and GC-related issues.
for (var i = 0; i < 500; i++) {
test();
}