blob: 6c1eec1b2d68a5bd82e8ffed0aa6f357d0282c10 [file] [log] [blame]
// 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/builtins/builtins-string-gen.h'
namespace string {
namespace runtime {
extern transitioning runtime ToString(Context, JSAny): String;
}
@export
transitioning macro ToStringImpl(context: Context, o: JSAny): String {
let result: JSAny = o;
while (true) {
typeswitch (result) {
case (num: Number): {
return NumberToString(num);
}
case (str: String): {
return str;
}
case (oddball: Oddball): {
return oddball.to_string;
}
case (JSReceiver): {
result = NonPrimitiveToPrimitive_String(context, result);
continue;
}
case (Symbol): {
ThrowTypeError(MessageTemplate::kSymbolToString);
}
case (JSAny): {
return runtime::ToString(context, o);
}
}
}
unreachable;
}
transitioning builtin ToString(context: Context, o: JSAny): String {
return ToStringImpl(context, o);
}
extern macro StringBuiltinsAssembler::SubString(
String, uintptr, uintptr): String;
// ES6 #sec-string.prototype.tostring
transitioning javascript builtin
StringPrototypeToString(
js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
return ToThisValue(
receiver, PrimitiveType::kString, 'String.prototype.toString');
}
// ES6 #sec-string.prototype.valueof
transitioning javascript builtin
StringPrototypeValueOf(
js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
return ToThisValue(
receiver, PrimitiveType::kString, 'String.prototype.valueOf');
}
extern macro StringBuiltinsAssembler::LoadSurrogatePairAt(
String, intptr, intptr, constexpr UnicodeEncoding): int32;
extern macro StringBuiltinsAssembler::StringFromSingleUTF16EncodedCodePoint(
int32): String;
// This function assumes StringPrimitiveWithNoCustomIteration is true.
transitioning builtin StringToList(implicit context: Context)(string: String):
JSArray {
const kind = ElementsKind::PACKED_ELEMENTS;
const stringLength: intptr = string.length_intptr;
const nativeContext = LoadNativeContext(context);
const map: Map = LoadJSArrayElementsMap(kind, nativeContext);
const array: JSArray = AllocateJSArray(
kind, map, stringLength, SmiTag(stringLength),
AllocationFlag::kAllowLargeObjectAllocation);
const elements = UnsafeCast<FixedArray>(array.elements);
const encoding = UnicodeEncoding::UTF16;
let arrayLength: Smi = 0;
let i: intptr = 0;
while (i < stringLength) {
const ch: int32 = LoadSurrogatePairAt(string, stringLength, i, encoding);
const value: String = StringFromSingleUTF16EncodedCodePoint(ch);
elements[arrayLength] = value;
// Increment and continue the loop.
i = i + value.length_intptr;
arrayLength++;
}
assert(arrayLength >= 0);
assert(SmiTag(stringLength) >= arrayLength);
array.length = arrayLength;
return array;
}
transitioning macro GenerateStringAt(implicit context: Context)(
receiver: JSAny, position: JSAny,
methodName: constexpr string): never labels
IfInBounds(String, uintptr, uintptr), IfOutOfBounds {
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
const string: String = ToThisString(receiver, methodName);
// 3. Let position be ? ToInteger(pos).
const indexNumber: Number = ToInteger_Inline(position);
// Convert the {position} to a uintptr and check that it's in bounds of
// the {string}.
typeswitch (indexNumber) {
case (indexSmi: Smi): {
const length: uintptr = string.length_uintptr;
const index: uintptr = Unsigned(Convert<intptr>(indexSmi));
// Max string length fits Smi range, so we can do an unsigned bounds
// check.
const kMaxStringLengthFitsSmi: constexpr bool =
kStringMaxLengthUintptr < kSmiMaxValue;
static_assert(kMaxStringLengthFitsSmi);
if (index >= length) goto IfOutOfBounds;
goto IfInBounds(string, index, length);
}
case (indexHeapNumber: HeapNumber): {
assert(IsNumberNormalized(indexHeapNumber));
// Valid string indices fit into Smi range, so HeapNumber index is
// definitely an out of bounds case.
goto IfOutOfBounds;
}
}
}
// ES6 #sec-string.prototype.charat
transitioning javascript builtin StringPrototypeCharAt(
js-implicit context: NativeContext,
receiver: JSAny)(position: JSAny): JSAny {
try {
GenerateStringAt(receiver, position, 'String.prototype.charAt')
otherwise IfInBounds, IfOutOfBounds;
} label IfInBounds(string: String, index: uintptr, _length: uintptr) {
const code: int32 = StringCharCodeAt(string, index);
return StringFromSingleCharCode(code);
} label IfOutOfBounds {
return kEmptyString;
}
}
// ES6 #sec-string.prototype.charcodeat
transitioning javascript builtin StringPrototypeCharCodeAt(
js-implicit context: NativeContext,
receiver: JSAny)(position: JSAny): JSAny {
try {
GenerateStringAt(receiver, position, 'String.prototype.charCodeAt')
otherwise IfInBounds, IfOutOfBounds;
} label IfInBounds(string: String, index: uintptr, _length: uintptr) {
const code: int32 = StringCharCodeAt(string, index);
return Convert<Smi>(code);
} label IfOutOfBounds {
return kNaN;
}
}
// ES6 #sec-string.prototype.codepointat
transitioning javascript builtin StringPrototypeCodePointAt(
js-implicit context: NativeContext,
receiver: JSAny)(position: JSAny): JSAny {
try {
GenerateStringAt(receiver, position, 'String.prototype.codePointAt')
otherwise IfInBounds, IfOutOfBounds;
} label IfInBounds(string: String, index: uintptr, length: uintptr) {
// This is always a call to a builtin from Javascript, so we need to
// produce UTF32.
const code: int32 = LoadSurrogatePairAt(
string, Signed(length), Signed(index), UnicodeEncoding::UTF32);
return Convert<Smi>(code);
} label IfOutOfBounds {
return Undefined;
}
}
// ES6 String.prototype.concat(...args)
// ES6 #sec-string.prototype.concat
transitioning javascript builtin StringPrototypeConcat(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
// Check that {receiver} is coercible to Object and convert it to a String.
let string: String = ToThisString(receiver, 'String.prototype.concat');
// Concatenate all the arguments passed to this builtin.
const length: intptr = Convert<intptr>(arguments.length);
for (let i: intptr = 0; i < length; i++) {
const temp: String = ToString_Inline(arguments[i]);
string = string + temp;
}
return string;
}
extern transitioning runtime
SymbolDescriptiveString(implicit context: Context)(Symbol): String;
// ES #sec-string-constructor
// https://tc39.github.io/ecma262/#sec-string-constructor
transitioning javascript builtin StringConstructor(
js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
target: JSFunction)(...arguments): JSAny {
const length: intptr = Convert<intptr>(arguments.length);
let s: String;
// 1. If no arguments were passed to this function invocation, let s be "".
if (length == 0) {
s = EmptyStringConstant();
} else {
// 2. Else,
// 2. a. If NewTarget is undefined and Type(value) is Symbol, return
// SymbolDescriptiveString(value).
if (newTarget == Undefined) {
typeswitch (arguments[0]) {
case (value: Symbol): {
return SymbolDescriptiveString(value);
}
case (JSAny): {
}
}
}
// 2. b. Let s be ? ToString(value).
s = ToString_Inline(arguments[0]);
}
// 3. If NewTarget is undefined, return s.
if (newTarget == Undefined) {
return s;
}
// 4. Return ! StringCreate(s, ? GetPrototypeFromConstructor(NewTarget,
// "%String.prototype%")).
const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget));
const obj =
UnsafeCast<JSPrimitiveWrapper>(AllocateFastOrSlowJSObjectFromMap(map));
obj.value = s;
return obj;
}
transitioning builtin StringAddConvertLeft(implicit context: Context)(
left: JSAny, right: String): String {
return ToStringImpl(context, ToPrimitiveDefault(left)) + right;
}
transitioning builtin StringAddConvertRight(implicit context: Context)(
left: String, right: JSAny): String {
return left + ToStringImpl(context, ToPrimitiveDefault(right));
}
builtin StringCharAt(implicit context: Context)(
receiver: String, position: uintptr): String {
// Load the character code at the {position} from the {receiver}.
const code: int32 = StringCharCodeAt(receiver, position);
// And return the single character string with only that {code}
return StringFromSingleCharCode(code);
}
}