• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, result);
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  dcheck(arrayLength >= 0);
94  dcheck(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      StaticAssertStringLengthFitsSmi();
120      if (index >= length) goto IfOutOfBounds;
121      goto IfInBounds(string, index, length);
122    }
123    case (indexHeapNumber: HeapNumber): {
124      dcheck(IsNumberNormalized(indexHeapNumber));
125      // Valid string indices fit into Smi range, so HeapNumber index is
126      // definitely an out of bounds case.
127      goto IfOutOfBounds;
128    }
129  }
130}
131
132// ES6 #sec-string.prototype.charat
133transitioning javascript builtin StringPrototypeCharAt(
134    js-implicit context: NativeContext,
135    receiver: JSAny)(position: JSAny): JSAny {
136  try {
137    GenerateStringAt(receiver, position, 'String.prototype.charAt')
138        otherwise IfInBounds, IfOutOfBounds;
139  } label IfInBounds(string: String, index: uintptr, _length: uintptr) {
140    const code: char16 = StringCharCodeAt(string, index);
141    return StringFromSingleCharCode(code);
142  } label IfOutOfBounds {
143    return kEmptyString;
144  }
145}
146
147// ES6 #sec-string.prototype.charcodeat
148transitioning javascript builtin StringPrototypeCharCodeAt(
149    js-implicit context: NativeContext,
150    receiver: JSAny)(position: JSAny): JSAny {
151  try {
152    GenerateStringAt(receiver, position, 'String.prototype.charCodeAt')
153        otherwise IfInBounds, IfOutOfBounds;
154  } label IfInBounds(string: String, index: uintptr, _length: uintptr) {
155    const code: uint32 = StringCharCodeAt(string, index);
156    return Convert<Smi>(code);
157  } label IfOutOfBounds {
158    return kNaN;
159  }
160}
161
162// ES6 #sec-string.prototype.codepointat
163transitioning javascript builtin StringPrototypeCodePointAt(
164    js-implicit context: NativeContext,
165    receiver: JSAny)(position: JSAny): JSAny {
166  try {
167    GenerateStringAt(receiver, position, 'String.prototype.codePointAt')
168        otherwise IfInBounds, IfOutOfBounds;
169  } label IfInBounds(string: String, index: uintptr, length: uintptr) {
170    // This is always a call to a builtin from Javascript, so we need to
171    // produce UTF32.
172    const code: int32 = LoadSurrogatePairAt(
173        string, Signed(length), Signed(index), UnicodeEncoding::UTF32);
174    return Convert<Smi>(code);
175  } label IfOutOfBounds {
176    return Undefined;
177  }
178}
179
180// ES6 String.prototype.concat(...args)
181// ES6 #sec-string.prototype.concat
182transitioning javascript builtin StringPrototypeConcat(
183    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
184  // Check that {receiver} is coercible to Object and convert it to a String.
185  let string: String = ToThisString(receiver, 'String.prototype.concat');
186
187  // Concatenate all the arguments passed to this builtin.
188  const length: intptr = Convert<intptr>(arguments.length);
189  for (let i: intptr = 0; i < length; i++) {
190    const temp: String = ToString_Inline(arguments[i]);
191    string = string + temp;
192  }
193  return string;
194}
195
196extern transitioning runtime
197SymbolDescriptiveString(implicit context: Context)(Symbol): String;
198
199// ES #sec-string-constructor
200// https://tc39.github.io/ecma262/#sec-string-constructor
201transitioning javascript builtin StringConstructor(
202    js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
203    target: JSFunction)(...arguments): JSAny {
204  const length: intptr = Convert<intptr>(arguments.length);
205  let s: String;
206  // 1. If no arguments were passed to this function invocation, let s be "".
207  if (length == 0) {
208    s = EmptyStringConstant();
209  } else {
210    // 2. Else,
211    // 2. a. If NewTarget is undefined and Type(value) is Symbol, return
212    // SymbolDescriptiveString(value).
213    if (newTarget == Undefined) {
214      typeswitch (arguments[0]) {
215        case (value: Symbol): {
216          return SymbolDescriptiveString(value);
217        }
218        case (JSAny): {
219        }
220      }
221    }
222    // 2. b. Let s be ? ToString(value).
223    s = ToString_Inline(arguments[0]);
224  }
225  // 3. If NewTarget is undefined, return s.
226  if (newTarget == Undefined) {
227    return s;
228  }
229  // 4. Return ! StringCreate(s, ? GetPrototypeFromConstructor(NewTarget,
230  // "%String.prototype%")).
231  const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget));
232  const obj =
233      UnsafeCast<JSPrimitiveWrapper>(AllocateFastOrSlowJSObjectFromMap(map));
234  obj.value = s;
235  return obj;
236}
237
238transitioning builtin StringAddConvertLeft(implicit context: Context)(
239    left: JSAny, right: String): String {
240  return ToStringImpl(context, ToPrimitiveDefault(left)) + right;
241}
242
243transitioning builtin StringAddConvertRight(implicit context: Context)(
244    left: String, right: JSAny): String {
245  return left + ToStringImpl(context, ToPrimitiveDefault(right));
246}
247
248builtin StringCharAt(implicit context: Context)(
249    receiver: String, position: uintptr): String {
250  // Load the character code at the {position} from the {receiver}.
251  const code: char16 = StringCharCodeAt(receiver, position);
252  // And return the single character string with only that {code}
253  return StringFromSingleCharCode(code);
254}
255}
256
257// Check two slices for equal content.
258// Checking from both ends simultaniously allows us to detect differences
259// quickly even when the slices share a prefix or a suffix.
260macro EqualContent<T1: type, T2: type>(
261    a: ConstSlice<T1>, b: ConstSlice<T2>): bool {
262  const length = a.length;
263  if (length != b.length) return false;
264  if (a.GCUnsafeStartPointer() == b.GCUnsafeStartPointer()) return true;
265  // This creates references to the first and last characters of the slices,
266  // which can be out-of-bounds if the slices are empty. But in this case,
267  // the references will never be accessed.
268  let aFirst = a.UncheckedAtIndex(0);
269  let bFirst = b.UncheckedAtIndex(0);
270  let aLast = a.UncheckedAtIndex(length - 1);
271  let bLast = b.UncheckedAtIndex(length - 1);
272  while (aFirst.offset <= aLast.offset) {
273    if (*aFirst != *bFirst || *aLast != *bLast) return false;
274    aFirst = unsafe::AddOffset(aFirst, 1);
275    aLast = unsafe::AddOffset(aLast, -1);
276    bFirst = unsafe::AddOffset(bFirst, 1);
277    bLast = unsafe::AddOffset(bLast, -1);
278  }
279  return true;
280}
281