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