builtins-string.tq 8.6 KB
Newer Older
1 2 3 4
// 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.

5 6
#include 'src/builtins/builtins-string-gen.h'

7
namespace string {
8

9 10 11
namespace runtime {
extern transitioning runtime ToString(Context, JSAny): String;
}
12 13 14 15 16 17 18

@export
transitioning macro ToStringImpl(context: Context, o: JSAny): String {
  let result: JSAny = o;
  while (true) {
    typeswitch (result) {
      case (num: Number): {
19
        return NumberToString(num);
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
      }
      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): {
35
        return runtime::ToString(context, o);
36 37 38 39 40 41 42 43 44 45
      }
    }
  }
  unreachable;
}

transitioning builtin ToString(context: Context, o: JSAny): String {
  return ToStringImpl(context, o);
}

46 47 48 49 50 51 52 53 54 55
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');
}
56

57 58 59 60 61 62 63
// ES6 #sec-string.prototype.valueof
transitioning javascript builtin
StringPrototypeValueOf(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return ToThisValue(
      receiver, PrimitiveType::kString, 'String.prototype.valueOf');
}
64

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
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++;
92
  }
93 94 95
  assert(arrayLength >= 0);
  assert(SmiTag(stringLength) >= arrayLength);
  array.length = arrayLength;
96

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
  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;
121
      static_assert(kMaxStringLengthFitsSmi);
122 123 124 125 126 127 128 129
      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;
130
    }
131
  }
132
}
133

134 135 136 137 138 139 140 141 142 143 144 145
// 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;
146
  }
147
}
148

149 150 151 152 153 154 155 156 157 158 159 160
// 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;
161
  }
162
}
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
// 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;
179
  }
180
}
181

182 183 184 185 186 187 188 189 190 191 192 193
// 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;
194
  }
195 196
  return string;
}
197

198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
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): {
221 222 223
        }
      }
    }
224 225
    // 2. b. Let s be ? ToString(value).
    s = ToString_Inline(arguments[0]);
226
  }
227 228 229
  // 3. If NewTarget is undefined, return s.
  if (newTarget == Undefined) {
    return s;
230
  }
231 232 233 234 235 236 237 238
  // 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;
}
239

240 241 242 243
transitioning builtin StringAddConvertLeft(implicit context: Context)(
    left: JSAny, right: String): String {
  return ToStringImpl(context, ToPrimitiveDefault(left)) + right;
}
244

245 246 247 248 249 250 251 252 253 254 255 256
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);
}
257
}