// Copyright 2019 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. #include 'src/ic/binary-op-assembler.h' extern enum Operation extends uint31 { // Binary operations. kAdd, kSubtract, kMultiply, kDivide, kModulus, kExponentiate, kBitwiseAnd, kBitwiseOr, kBitwiseXor, kShiftLeft, kShiftRight, kShiftRightLogical, // Unary operations. kBitwiseNot, kNegate, kIncrement, kDecrement, // Compare operations. kEqual, kStrictEqual, kLessThan, kLessThanOrEqual, kGreaterThan, kGreaterThanOrEqual } namespace runtime { extern transitioning runtime DoubleToStringWithRadix(implicit context: Context)(Number, Number): String; extern transitioning runtime StringParseFloat(implicit context: Context)( String): Number; extern transitioning runtime StringParseInt(implicit context: Context)( JSAny, JSAny): Number; extern runtime BigIntUnaryOp(Context, BigInt, SmiTagged): BigInt; extern runtime BigIntBinaryOp( Context, Numeric, Numeric, SmiTagged): BigInt; } // namespace runtime namespace number { extern macro NaNStringConstant(): String; extern macro ZeroStringConstant(): String; extern macro InfinityStringConstant(): String; extern macro MinusInfinityStringConstant(): String; const kAsciiZero: constexpr int32 = 48; // '0' (ascii) const kAsciiLowerCaseA: constexpr int32 = 97; // 'a' (ascii) transitioning macro ThisNumberValue(implicit context: Context)( receiver: JSAny, method: constexpr string): Number { return UnsafeCast( ToThisValue(receiver, PrimitiveType::kNumber, method)); } macro ToCharCode(input: int32): char8 { dcheck(0 <= input && input < 36); return input < 10 ? %RawDownCast(Unsigned(input + kAsciiZero)) : %RawDownCast(Unsigned(input - 10 + kAsciiLowerCaseA)); } @export macro NumberToStringSmi(x: int32, radix: int32): String labels Slow { const isNegative: bool = x < 0; let n: int32 = x; if (!isNegative) { // Fast case where the result is a one character string. if (x < radix) { if (x == 0) { return ZeroStringConstant(); } return StringFromSingleCharCode(ToCharCode(n)); } } else { dcheck(isNegative); if (n == kMinInt32) { goto Slow; } n = 0 - n; } // Calculate length and pre-allocate the result string. let temp: int32 = n; let length: int32 = isNegative ? Convert(1) : Convert(0); while (temp > 0) { temp = temp / radix; length = length + 1; } dcheck(length > 0); const strSeq = AllocateNonEmptySeqOneByteString(Unsigned(length)); let cursor: intptr = Convert(length) - 1; while (n > 0) { const digit: int32 = n % radix; n = n / radix; *UnsafeConstCast(&strSeq.chars[cursor]) = ToCharCode(digit); cursor = cursor - 1; } if (isNegative) { dcheck(cursor == 0); // Insert '-' to result. *UnsafeConstCast(&strSeq.chars[0]) = 45; } else { dcheck(cursor == -1); // In sync with Factory::SmiToString: If radix = 10 and positive number, // update hash for string. if (radix == 10) { dcheck(strSeq.raw_hash_field == kNameEmptyHashField); strSeq.raw_hash_field = MakeArrayIndexHash(Unsigned(x), Unsigned(length)); } } return strSeq; } // https://tc39.github.io/ecma262/#sec-number.prototype.tostring transitioning javascript builtin NumberPrototypeToString( js-implicit context: NativeContext, receiver: JSAny)(...arguments): String { // 1. Let x be ? thisNumberValue(this value). const x = ThisNumberValue(receiver, 'Number.prototype.toString'); // 2. If radix is not present, let radixNumber be 10. // 3. Else if radix is undefined, let radixNumber be 10. // 4. Else, let radixNumber be ? ToInteger(radix). const radix: JSAny = arguments[0]; const radixNumber: Number = radix == Undefined ? 10 : ToInteger_Inline(radix); // 5. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. if (radixNumber < 2 || radixNumber > 36) { ThrowRangeError(MessageTemplate::kToRadixFormatRange); } // 6. If radixNumber = 10, return ! ToString(x). if (radixNumber == 10) { return NumberToString(x); } // 7. Return the String representation of this Number // value using the radix specified by radixNumber. if (TaggedIsSmi(x)) { return NumberToStringSmi(Convert(x), Convert(radixNumber)) otherwise return runtime::DoubleToStringWithRadix(x, radixNumber); } if (x == -0) { return ZeroStringConstant(); } else if (::NumberIsNaN(x)) { return NaNStringConstant(); } else if (x == V8_INFINITY) { return InfinityStringConstant(); } else if (x == MINUS_V8_INFINITY) { return MinusInfinityStringConstant(); } return runtime::DoubleToStringWithRadix(x, radixNumber); } // ES6 #sec-number.isfinite javascript builtin NumberIsFinite( js-implicit context: NativeContext, receiver: JSAny)(value: JSAny): Boolean { typeswitch (value) { case (Smi): { return True; } case (h: HeapNumber): { const number: float64 = Convert(h); const infiniteOrNaN: bool = Float64IsNaN(number - number); return Convert(!infiniteOrNaN); } case (JSAnyNotNumber): { return False; } } } // ES6 #sec-number.isinteger javascript builtin NumberIsInteger(js-implicit context: NativeContext)( value: JSAny): Boolean { return SelectBooleanConstant(IsInteger(value)); } // ES6 #sec-number.isnan javascript builtin NumberIsNaN(js-implicit context: NativeContext)( value: JSAny): Boolean { typeswitch (value) { case (Smi): { return False; } case (h: HeapNumber): { const number: float64 = Convert(h); return Convert(Float64IsNaN(number)); } case (JSAnyNotNumber): { return False; } } } // ES6 #sec-number.issafeinteger javascript builtin NumberIsSafeInteger(js-implicit context: NativeContext)( value: JSAny): Boolean { return SelectBooleanConstant(IsSafeInteger(value)); } // ES6 #sec-number.prototype.valueof transitioning javascript builtin NumberPrototypeValueOf( js-implicit context: NativeContext, receiver: JSAny)(): JSAny { return ToThisValue( receiver, PrimitiveType::kNumber, 'Number.prototype.valueOf'); } // ES6 #sec-number.parsefloat transitioning javascript builtin NumberParseFloat( js-implicit context: NativeContext)(value: JSAny): Number { try { typeswitch (value) { case (s: Smi): { return s; } case (h: HeapNumber): { // The input is already a Number. Take care of -0. // The sense of comparison is important for the NaN case. return (Convert(h) == 0) ? SmiConstant(0) : h; } case (s: String): { goto String(s); } case (HeapObject): { goto String(string::ToString(context, value)); } } } label String(s: String) { // Check if the string is a cached array index. const hash: NameHash = s.raw_hash_field; if (IsIntegerIndex(hash) && hash.array_index_length < kMaxCachedArrayIndexLength) { const arrayIndex: uint32 = hash.array_index_value; return SmiFromUint32(arrayIndex); } // Fall back to the runtime to convert string to a number. return runtime::StringParseFloat(s); } } extern macro TruncateFloat64ToWord32(float64): uint32; transitioning builtin ParseInt(implicit context: Context)( input: JSAny, radix: JSAny): Number { try { // Check if radix should be 10 (i.e. undefined, 0 or 10). if (radix != Undefined && !TaggedEqual(radix, SmiConstant(10)) && !TaggedEqual(radix, SmiConstant(0))) { goto CallRuntime; } typeswitch (input) { case (s: Smi): { return s; } case (h: HeapNumber): { // Check if the input value is in Signed32 range. const asFloat64: float64 = Convert(h); const asInt32: int32 = Signed(TruncateFloat64ToWord32(asFloat64)); // The sense of comparison is important for the NaN case. if (asFloat64 == ChangeInt32ToFloat64(asInt32)) goto Int32(asInt32); // Check if the absolute value of input is in the [1,1<<31[ range. Call // the runtime for the range [0,1[ because the result could be -0. const kMaxAbsValue: float64 = 2147483648.0; const absInput: float64 = math::Float64Abs(asFloat64); if (absInput < kMaxAbsValue && absInput >= 1.0) goto Int32(asInt32); goto CallRuntime; } case (s: String): { goto String(s); } case (HeapObject): { goto CallRuntime; } } } label Int32(i: int32) { return ChangeInt32ToTagged(i); } label String(s: String) { // Check if the string is a cached array index. const hash: NameHash = s.raw_hash_field; if (IsIntegerIndex(hash) && hash.array_index_length < kMaxCachedArrayIndexLength) { const arrayIndex: uint32 = hash.array_index_value; return SmiFromUint32(arrayIndex); } // Fall back to the runtime. goto CallRuntime; } label CallRuntime { tail runtime::StringParseInt(input, radix); } } // ES6 #sec-number.parseint transitioning javascript builtin NumberParseInt( js-implicit context: NativeContext)(value: JSAny, radix: JSAny): Number { return ParseInt(value, radix); } extern builtin NonNumberToNumeric(implicit context: Context)(JSAny): Numeric; extern builtin BitwiseXor(implicit context: Context)(Number, Number): Number; extern builtin Subtract(implicit context: Context)(Number, Number): Number; extern builtin Add(implicit context: Context)(Number, Number): Number; extern builtin StringAddConvertLeft(implicit context: Context)( JSAny, String): JSAny; extern builtin StringAddConvertRight(implicit context: Context)( String, JSAny): JSAny; extern macro BitwiseOp(int32, int32, constexpr Operation): Number; extern macro RelationalComparison( constexpr Operation, JSAny, JSAny, Context): Boolean; // TODO(bbudge) Use a simpler macro structure that doesn't loop when converting // non-numbers, if such a code sequence doesn't make the builtin bigger. transitioning macro ToNumericOrPrimitive(implicit context: Context)( value: JSAny): JSAny { typeswitch (value) { case (v: JSReceiver): { return NonPrimitiveToPrimitive_Default(context, v); } case (v: JSPrimitive): { return NonNumberToNumeric(v); } } } transitioning builtin Add(implicit context: Context)( leftArg: JSAny, rightArg: JSAny): JSAny { let left: JSAny = leftArg; let right: JSAny = rightArg; try { while (true) { typeswitch (left) { case (left: Smi): { typeswitch (right) { case (right: Smi): { return math::TrySmiAdd(left, right) otherwise goto Float64s( SmiToFloat64(left), SmiToFloat64(right)); } case (right: HeapNumber): { goto Float64s(SmiToFloat64(left), Convert(right)); } case (right: BigInt): { goto Numerics(left, right); } case (right: String): { goto StringAddConvertLeft(left, right); } case (HeapObject): { right = ToNumericOrPrimitive(right); continue; } } } case (left: HeapNumber): { typeswitch (right) { case (right: Smi): { goto Float64s(Convert(left), SmiToFloat64(right)); } case (right: HeapNumber): { goto Float64s(Convert(left), Convert(right)); } case (right: BigInt): { goto Numerics(left, right); } case (right: String): { goto StringAddConvertLeft(left, right); } case (HeapObject): { right = ToNumericOrPrimitive(right); continue; } } } case (left: BigInt): { typeswitch (right) { case (right: Numeric): { goto Numerics(left, right); } case (right: String): { goto StringAddConvertLeft(left, right); } case (HeapObject): { right = ToNumericOrPrimitive(right); continue; } } } case (left: String): { goto StringAddConvertRight(left, right); } case (leftReceiver: JSReceiver): { left = ToPrimitiveDefault(leftReceiver); } case (HeapObject): { // left: HeapObject typeswitch (right) { case (right: String): { goto StringAddConvertLeft(left, right); } case (rightReceiver: JSReceiver): { // left is JSPrimitive and right is JSReceiver, convert right // with priority. right = ToPrimitiveDefault(rightReceiver); continue; } case (JSPrimitive): { // Neither left or right is JSReceiver, convert left. left = NonNumberToNumeric(left); continue; } } } } } } label StringAddConvertLeft(left: JSAny, right: String) { tail StringAddConvertLeft(left, right); } label StringAddConvertRight(left: String, right: JSAny) { tail StringAddConvertRight(left, right); } label Numerics(left: Numeric, right: Numeric) { tail bigint::BigIntAdd(left, right); } label Float64s(left: float64, right: float64) { return AllocateHeapNumberWithValue(left + right); } unreachable; } // Unary type switch on Number | BigInt. macro UnaryOp1(implicit context: Context)(value: JSAny): never labels Number(Number), BigInt(BigInt) { let x: JSAny = value; while (true) { typeswitch (x) { case (n: Number): { goto Number(n); } case (b: BigInt): { goto BigInt(b); } case (JSAnyNotNumeric): { x = NonNumberToNumeric(x); } } } unreachable; } // Unary type switch on Smi | HeapNumber | BigInt. macro UnaryOp2(implicit context: Context)(value: JSAny): never labels Smi(Smi), HeapNumber(HeapNumber), BigInt(BigInt) { let x: JSAny = value; while (true) { typeswitch (x) { case (s: Smi): { goto Smi(s); } case (h: HeapNumber): { goto HeapNumber(h); } case (b: BigInt): { goto BigInt(b); } case (JSAnyNotNumeric): { x = NonNumberToNumeric(x); } } } unreachable; } // Binary type switch on Number | BigInt. macro BinaryOp1(implicit context: Context)( leftVal: JSAny, rightVal: JSAny): never labels Number(Number, Number), AtLeastOneBigInt(Numeric, Numeric) { let left: JSAny = leftVal; let right: JSAny = rightVal; while (true) { try { typeswitch (left) { case (left: Number): { typeswitch (right) { case (right: Number): { goto Number(left, right); } case (right: BigInt): { goto AtLeastOneBigInt(left, right); } case (JSAnyNotNumeric): { goto RightNotNumeric; } } } case (left: BigInt): { typeswitch (right) { case (right: Numeric): { goto AtLeastOneBigInt(left, right); } case (JSAnyNotNumeric): { goto RightNotNumeric; } } } case (JSAnyNotNumeric): { left = NonNumberToNumeric(left); } } } label RightNotNumeric { right = NonNumberToNumeric(right); } } unreachable; } // Binary type switch on Smi | HeapNumber | BigInt. macro BinaryOp2(implicit context: Context)(leftVal: JSAny, rightVal: JSAny): never labels Smis(Smi, Smi), Float64s(float64, float64), AtLeastOneBigInt(Numeric, Numeric) { let left: JSAny = leftVal; let right: JSAny = rightVal; while (true) { try { typeswitch (left) { case (left: Smi): { typeswitch (right) { case (right: Smi): { goto Smis(left, right); } case (right: HeapNumber): { goto Float64s(SmiToFloat64(left), Convert(right)); } case (right: BigInt): { goto AtLeastOneBigInt(left, right); } case (JSAnyNotNumeric): { goto RightNotNumeric; } } } case (left: HeapNumber): { typeswitch (right) { case (right: Smi): { goto Float64s(Convert(left), SmiToFloat64(right)); } case (right: HeapNumber): { goto Float64s(Convert(left), Convert(right)); } case (right: BigInt): { goto AtLeastOneBigInt(left, right); } case (JSAnyNotNumeric): { goto RightNotNumeric; } } } case (left: BigInt): { typeswitch (right) { case (right: Numeric): { goto AtLeastOneBigInt(left, right); } case (JSAnyNotNumeric): { goto RightNotNumeric; } } } case (JSAnyNotNumeric): { left = NonNumberToNumeric(left); } } } label RightNotNumeric { right = NonNumberToNumeric(right); } } unreachable; } builtin Subtract(implicit context: Context)( left: JSAny, right: JSAny): Numeric { try { BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt; } label Smis(left: Smi, right: Smi) { try { return math::TrySmiSub(left, right) otherwise Overflow; } label Overflow { goto Float64s(SmiToFloat64(left), SmiToFloat64(right)); } } label Float64s(left: float64, right: float64) { return AllocateHeapNumberWithValue(left - right); } label AtLeastOneBigInt(left: Numeric, right: Numeric) { tail bigint::BigIntSubtract(left, right); } } builtin Multiply(implicit context: Context)( left: JSAny, right: JSAny): Numeric { try { BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt; } label Smis(left: Smi, right: Smi) { // The result is not necessarily a smi, in case of overflow. return SmiMul(left, right); } label Float64s(left: float64, right: float64) { return AllocateHeapNumberWithValue(left * right); } label AtLeastOneBigInt(left: Numeric, right: Numeric) { tail runtime::BigIntBinaryOp( context, left, right, SmiTag(Operation::kMultiply)); } } const kSmiValueSize: constexpr int32 generates 'kSmiValueSize'; const kMinInt32: constexpr int32 generates 'kMinInt'; const kMinInt31: constexpr int32 generates 'kMinInt31'; const kMinimumDividend: int32 = (kSmiValueSize == 32) ? kMinInt32 : kMinInt31; builtin Divide(implicit context: Context)(left: JSAny, right: JSAny): Numeric { try { BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt; } label Smis(left: Smi, right: Smi) { // TODO(jkummerow): Consider just always doing a double division. // Bail out if {divisor} is zero. if (right == 0) goto SmiBailout(left, right); // Bail out if dividend is zero and divisor is negative. if (left == 0 && right < 0) goto SmiBailout(left, right); const dividend: int32 = SmiToInt32(left); const divisor: int32 = SmiToInt32(right); // Bail out if dividend is kMinInt31 (or kMinInt32 if Smis are 32 bits) // and divisor is -1. if (divisor == -1 && dividend == kMinimumDividend) { goto SmiBailout(left, right); } // TODO(epertoso): consider adding a machine instruction that returns // both the result and the remainder. const result: int32 = dividend / divisor; const truncated: int32 = result * divisor; if (dividend != truncated) goto SmiBailout(left, right); return SmiFromInt32(result); } label SmiBailout(left: Smi, right: Smi) { goto Float64s(SmiToFloat64(left), SmiToFloat64(right)); } label Float64s(left: float64, right: float64) { return AllocateHeapNumberWithValue(left / right); } label AtLeastOneBigInt(left: Numeric, right: Numeric) { tail runtime::BigIntBinaryOp( context, left, right, SmiTag(Operation::kDivide)); } } builtin Modulus(implicit context: Context)(left: JSAny, right: JSAny): Numeric { try { BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt; } label Smis(left: Smi, right: Smi) { return SmiMod(left, right); } label Float64s(left: float64, right: float64) { return AllocateHeapNumberWithValue(left % right); } label AtLeastOneBigInt(left: Numeric, right: Numeric) { tail runtime::BigIntBinaryOp( context, left, right, SmiTag(Operation::kModulus)); } } builtin Exponentiate(implicit context: Context)( left: JSAny, right: JSAny): Numeric { try { BinaryOp1(left, right) otherwise Numbers, AtLeastOneBigInt; } label Numbers(left: Number, right: Number) { return math::MathPowImpl(left, right); } label AtLeastOneBigInt(left: Numeric, right: Numeric) { tail runtime::BigIntBinaryOp( context, left, right, SmiTag(Operation::kExponentiate)); } } builtin Negate(implicit context: Context)(value: JSAny): Numeric { try { UnaryOp2(value) otherwise Smi, HeapNumber, BigInt; } label Smi(s: Smi) { return SmiMul(s, -1); } label HeapNumber(h: HeapNumber) { return AllocateHeapNumberWithValue(Convert(h) * -1.0); } label BigInt(b: BigInt) { tail runtime::BigIntUnaryOp( context, b, SmiTag(Operation::kNegate)); } } builtin BitwiseNot(implicit context: Context)(value: JSAny): Numeric { try { UnaryOp1(value) otherwise Number, BigInt; } label Number(n: Number) { tail BitwiseXor(n, -1); } label BigInt(b: BigInt) { return runtime::BigIntUnaryOp( context, b, SmiTag(Operation::kBitwiseNot)); } } builtin Decrement(implicit context: Context)(value: JSAny): Numeric { try { UnaryOp1(value) otherwise Number, BigInt; } label Number(n: Number) { tail Subtract(n, 1); } label BigInt(b: BigInt) { return runtime::BigIntUnaryOp( context, b, SmiTag(Operation::kDecrement)); } } builtin Increment(implicit context: Context)(value: JSAny): Numeric { try { UnaryOp1(value) otherwise Number, BigInt; } label Number(n: Number) { tail Add(n, 1); } label BigInt(b: BigInt) { return runtime::BigIntUnaryOp( context, b, SmiTag(Operation::kIncrement)); } } // Bitwise binary operations. extern macro BinaryOpAssembler::Generate_BitwiseBinaryOp( constexpr Operation, JSAny, JSAny, Context): Object; builtin ShiftLeft(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp(Operation::kShiftLeft, left, right, context); } builtin ShiftRight(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp(Operation::kShiftRight, left, right, context); } builtin ShiftRightLogical(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp( Operation::kShiftRightLogical, left, right, context); } builtin BitwiseAnd(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp(Operation::kBitwiseAnd, left, right, context); } builtin BitwiseOr(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp(Operation::kBitwiseOr, left, right, context); } builtin BitwiseXor(implicit context: Context)( left: JSAny, right: JSAny): Object { return Generate_BitwiseBinaryOp(Operation::kBitwiseXor, left, right, context); } // Relational builtins. builtin LessThan(implicit context: Context)(left: JSAny, right: JSAny): Object { return RelationalComparison(Operation::kLessThan, left, right, context); } builtin LessThanOrEqual(implicit context: Context)( left: JSAny, right: JSAny): Object { return RelationalComparison( Operation::kLessThanOrEqual, left, right, context); } builtin GreaterThan(implicit context: Context)( left: JSAny, right: JSAny): Object { return RelationalComparison(Operation::kGreaterThan, left, right, context); } builtin GreaterThanOrEqual(implicit context: Context)( left: JSAny, right: JSAny): Object { return RelationalComparison( Operation::kGreaterThanOrEqual, left, right, context); } builtin Equal(implicit context: Context)(left: JSAny, right: JSAny): Object { return Equal(left, right, context); } builtin StrictEqual(implicit context: Context)( left: JSAny, right: JSAny): Object { return ::StrictEqual(left, right); } } // namespace number