1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include 'src/builtins/builtins-string-gen.h'
6
7namespace string {
8
9namespace runtime {
10extern transitioning runtime ToString(Context, JSAny): String;
11}
12
13@export
14transitioning macro ToStringImpl(context: Context, o: JSAny): String {
15  let result: JSAny = o;
16  while (true) {
17    typeswitch (result) {
18      case (num: Number): {
19        return NumberToString(num);
20      }
21      case (str: String): {
22        return str;
23      }
24      case (oddball: Oddball): {
25        return oddball.to_string;
26      }
27      case (JSReceiver): {
28        result = NonPrimitiveToPrimitive_String(context, result);
29        continue;
30      }
31      case (Symbol): {
32        ThrowTypeError(MessageTemplate::kSymbolToString);
33      }
34      case (JSAny): {
35        return runtime::ToString(context, o);
36      }
37    }
38  }
39  unreachable;
40}
41
42transitioning builtin ToString(context: Context, o: JSAny): String {
43  return ToStringImpl(context, o);
44}
45
46extern macro StringBuiltinsAssembler::SubString(
47    String, uintptr, uintptr): String;
48
49// ES6 #sec-string.prototype.tostring
50transitioning javascript builtin
51StringPrototypeToString(
52    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
53  return ToThisValue(
54      receiver, PrimitiveType::kString, 'String.prototype.toString');
55}
56
57// ES6 #sec-string.prototype.valueof
58transitioning javascript builtin
59StringPrototypeValueOf(
60    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
61  return ToThisValue(
62      receiver, PrimitiveType::kString, 'String.prototype.valueOf');
63}
64
65extern macro StringBuiltinsAssembler::LoadSurrogatePairAt(
66    String, intptr, intptr, constexpr UnicodeEncoding): int32;
67extern macro StringBuiltinsAssembler::StringFromSingleUTF16EncodedCodePoint(
68    int32): String;
69
70// This function assumes StringPrimitiveWithNoCustomIteration is true.
71transitioning builtin StringToList(implicit context: Context)(string: String):
72    JSArray {
73  const kind = ElementsKind::PACKED_ELEMENTS;
74  const stringLength: intptr = string.length_intptr;
75
76  const nativeContext = LoadNativeContext(context);
77  const map: Map = LoadJSArrayElementsMap(kind, nativeContext);
78  const array: JSArray = AllocateJSArray(
79      kind, map, stringLength, SmiTag(stringLength),
80      AllocationFlag::kAllowLargeObjectAllocation);
81  const elements = UnsafeCast<FixedArray>(array.elements);
82  const encoding = UnicodeEncoding::UTF16;
83  let arrayLength: Smi = 0;
84  let i: intptr = 0;
85  while (i < stringLength) {
86    const ch: int32 = LoadSurrogatePairAt(string, stringLength, i, encoding);
87    const value: String = StringFromSingleUTF16EncodedCodePoint(ch);
88    elements[arrayLength] = value;
89    // Increment and continue the loop.
90    i = i + value.length_intptr;
91    arrayLength++;
92  }
93  assert(arrayLength >= 0);
94  assert(SmiTag(stringLength) >= arrayLength);
95  array.length = arrayLength;
96
97  return array;
98}
99
100transitioning macro GenerateStringAt(implicit context: Context)(
101    receiver: JSAny, position: JSAny,
102    methodName: constexpr string): never labels
103IfInBounds(String, uintptr, uintptr), IfOutOfBounds {
104  // 1. Let O be ? RequireObjectCoercible(this value).
105  // 2. Let S be ? ToString(O).
106  const string: String = ToThisString(receiver, methodName);
107
108  // 3. Let position be ? ToInteger(pos).
109  const indexNumber: Number = ToInteger_Inline(position);
110
111  // Convert the {position} to a uintptr and check that it's in bounds of
112  // the {string}.
113  typeswitch (indexNumber) {
114    case (indexSmi: Smi): {
115      const length: uintptr = string.length_uintptr;
116      const index: uintptr = Unsigned(Convert<intptr>(indexSmi));
117      // Max string length fits Smi range, so we can do an unsigned bounds
118      // check.
119      const kMaxStringLengthFitsSmi: constexpr bool =
120          kStringMaxLengthUintptr < kSmiMaxValue;
121      static_assert(kMaxStringLengthFitsSmi);
122      if (index >= length) goto IfOutOfBounds;
123      goto IfInBounds(string, index, length);
124    }
125    case (indexHeapNumber: HeapNumber): {
126      assert(IsNumberNormalized(indexHeapNumber));
127      // Valid string indices fit into Smi range, so HeapNumber index is
128      // definitely an out of bounds case.
129      goto IfOutOfBounds;
130    }
131  }
132}
133
134// ES6 #sec-string.prototype.charat
135transitioning javascript builtin StringPrototypeCharAt(
136    js-implicit context: NativeContext,
137    receiver: JSAny)(position: JSAny): JSAny {
138  try {
139    GenerateStringAt(receiver, position, 'String.prototype.charAt')
140        otherwise IfInBounds, IfOutOfBounds;
141  } label IfInBounds(string: String, index: uintptr, _length: uintptr) {
142    const code: int32 = StringCharCodeAt(string, index);
143    return StringFromSingleCharCode(code);
144  } label IfOutOfBounds {
145    return kEmptyString;
146  }
147}
148
149// ES6 #sec-string.prototype.charcodeat
150transitioning javascript builtin StringPrototypeCharCodeAt(
151    js-implicit context: NativeContext,
152    receiver: JSAny)(position: JSAny): JSAny {
153  try {
154    GenerateStringAt(receiver, position, 'String.prototype.charCodeAt')
155        otherwise IfInBounds, IfOutOfBounds;
156  } label IfInBounds(string: String, index: uintptr, _length: uintptr) {
157    const code: int32 = StringCharCodeAt(string, index);
158    return Convert<Smi>(code);
159  } label IfOutOfBounds {
160    return kNaN;
161  }
162}
163
164// ES6 #sec-string.prototype.codepointat
165transitioning javascript builtin StringPrototypeCodePointAt(
166    js-implicit context: NativeContext,
167    receiver: JSAny)(position: JSAny): JSAny {
168  try {
169    GenerateStringAt(receiver, position, 'String.prototype.codePointAt')
170        otherwise IfInBounds, IfOutOfBounds;
171  } label IfInBounds(string: String, index: uintptr, length: uintptr) {
172    // This is always a call to a builtin from Javascript, so we need to
173    // produce UTF32.
174    const code: int32 = LoadSurrogatePairAt(
175        string, Signed(length), Signed(index), UnicodeEncoding::UTF32);
176    return Convert<Smi>(code);
177  } label IfOutOfBounds {
178    return Undefined;
179  }
180}
181
182// ES6 String.prototype.concat(...args)
183// ES6 #sec-string.prototype.concat
184transitioning javascript builtin StringPrototypeConcat(
185    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
186  // Check that {receiver} is coercible to Object and convert it to a String.
187  let string: String = ToThisString(receiver, 'String.prototype.concat');
188
189  // Concatenate all the arguments passed to this builtin.
190  const length: intptr = Convert<intptr>(arguments.length);
191  for (let i: intptr = 0; i < length; i++) {
192    const temp: String = ToString_Inline(arguments[i]);
193    string = string + temp;
194  }
195  return string;
196}
197
198extern transitioning runtime
199SymbolDescriptiveString(implicit context: Context)(Symbol): String;
200
201// ES #sec-string-constructor
202// https://tc39.github.io/ecma262/#sec-string-constructor
203transitioning javascript builtin StringConstructor(
204    js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
205    target: JSFunction)(...arguments): JSAny {
206  const length: intptr = Convert<intptr>(arguments.length);
207  let s: String;
208  // 1. If no arguments were passed to this function invocation, let s be "".
209  if (length == 0) {
210    s = EmptyStringConstant();
211  } else {
212    // 2. Else,
213    // 2. a. If NewTarget is undefined and Type(value) is Symbol, return
214    // SymbolDescriptiveString(value).
215    if (newTarget == Undefined) {
216      typeswitch (arguments[0]) {
217        case (value: Symbol): {
218          return SymbolDescriptiveString(value);
219        }
220        case (JSAny): {
221        }
222      }
223    }
224    // 2. b. Let s be ? ToString(value).
225    s = ToString_Inline(arguments[0]);
226  }
227  // 3. If NewTarget is undefined, return s.
228  if (newTarget == Undefined) {
229    return s;
230  }
231  // 4. Return ! StringCreate(s, ? GetPrototypeFromConstructor(NewTarget,
232  // "%String.prototype%")).
233  const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget));
234  const obj =
235      UnsafeCast<JSPrimitiveWrapper>(AllocateFastOrSlowJSObjectFromMap(map));
236  obj.value = s;
237  return obj;
238}
239
240transitioning builtin StringAddConvertLeft(implicit context: Context)(
241    left: JSAny, right: String): String {
242  return ToStringImpl(context, ToPrimitiveDefault(left)) + right;
243}
244
245transitioning builtin StringAddConvertRight(implicit context: Context)(
246    left: String, right: JSAny): String {
247  return left + ToStringImpl(context, ToPrimitiveDefault(right));
248}
249
250builtin StringCharAt(implicit context: Context)(
251    receiver: String, position: uintptr): String {
252  // Load the character code at the {position} from the {receiver}.
253  const code: int32 = StringCharCodeAt(receiver, position);
254  // And return the single character string with only that {code}
255  return StringFromSingleCharCode(code);
256}
257}
258