1 // Copyright 2017 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 #ifndef V8_INTL_SUPPORT 6 #error Internationalization is expected to be enabled. 7 #endif // V8_INTL_SUPPORT 8 9 #include "src/builtins/builtins-iterator-gen.h" 10 #include "src/builtins/builtins-utils-gen.h" 11 #include "src/code-stub-assembler.h" 12 #include "src/objects-inl.h" 13 #include "src/objects.h" 14 #include "src/objects/js-list-format-inl.h" 15 #include "src/objects/js-list-format.h" 16 17 namespace v8 { 18 namespace internal { 19 20 template <class T> 21 using TNode = compiler::TNode<T>; 22 23 class IntlBuiltinsAssembler : public CodeStubAssembler { 24 public: 25 explicit IntlBuiltinsAssembler(compiler::CodeAssemblerState* state) 26 : CodeStubAssembler(state) {} 27 28 void ListFormatCommon(TNode<Context> context, TNode<Int32T> argc, 29 Runtime::FunctionId format_func_id, 30 const char* method_name); 31 32 TNode<JSArray> AllocateEmptyJSArray(TNode<Context> context); 33 }; 34 35 TF_BUILTIN(StringToLowerCaseIntl, IntlBuiltinsAssembler) { 36 Node* const string = Parameter(Descriptor::kString); 37 Node* const context = Parameter(Descriptor::kContext); 38 39 CSA_ASSERT(this, IsString(string)); 40 41 Label call_c(this), return_string(this), runtime(this, Label::kDeferred); 42 43 // Early exit on empty strings. 44 TNode<Smi> const length = LoadStringLengthAsSmi(string); 45 GotoIf(SmiEqual(length, SmiConstant(0)), &return_string); 46 47 // Unpack strings if possible, and bail to runtime unless we get a one-byte 48 // flat string. 49 ToDirectStringAssembler to_direct( 50 state(), string, ToDirectStringAssembler::kDontUnpackSlicedStrings); 51 to_direct.TryToDirect(&runtime); 52 53 Node* const instance_type = to_direct.instance_type(); 54 CSA_ASSERT(this, 55 Word32BinaryNot(IsIndirectStringInstanceType(instance_type))); 56 GotoIfNot(IsOneByteStringInstanceType(instance_type), &runtime); 57 58 // For short strings, do the conversion in CSA through the lookup table. 59 60 Node* const dst = AllocateSeqOneByteString(context, length); 61 62 const int kMaxShortStringLength = 24; // Determined empirically. 63 GotoIf(SmiGreaterThan(length, SmiConstant(kMaxShortStringLength)), &call_c); 64 65 { 66 Node* const dst_ptr = PointerToSeqStringData(dst); 67 VARIABLE(var_cursor, MachineType::PointerRepresentation(), 68 IntPtrConstant(0)); 69 70 Node* const start_address = to_direct.PointerToData(&call_c); 71 TNode<IntPtrT> const end_address = 72 Signed(IntPtrAdd(start_address, SmiUntag(length))); 73 74 Node* const to_lower_table_addr = 75 ExternalConstant(ExternalReference::intl_to_latin1_lower_table()); 76 77 VARIABLE(var_did_change, MachineRepresentation::kWord32, Int32Constant(0)); 78 79 VariableList push_vars({&var_cursor, &var_did_change}, zone()); 80 BuildFastLoop(push_vars, start_address, end_address, 81 [=, &var_cursor, &var_did_change](Node* current) { 82 Node* c = Load(MachineType::Uint8(), current); 83 Node* lower = 84 Load(MachineType::Uint8(), to_lower_table_addr, 85 ChangeInt32ToIntPtr(c)); 86 StoreNoWriteBarrier(MachineRepresentation::kWord8, dst_ptr, 87 var_cursor.value(), lower); 88 89 var_did_change.Bind(Word32Or(Word32NotEqual(c, lower), 90 var_did_change.value())); 91 92 Increment(&var_cursor); 93 }, 94 kCharSize, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); 95 96 // Return the original string if it remained unchanged in order to preserve 97 // e.g. internalization and private symbols (such as the preserved object 98 // hash) on the source string. 99 GotoIfNot(var_did_change.value(), &return_string); 100 101 Return(dst); 102 } 103 104 // Call into C for case conversion. The signature is: 105 // Object* ConvertOneByteToLower(String* src, String* dst, Isolate* isolate); 106 BIND(&call_c); 107 { 108 Node* const src = to_direct.string(); 109 110 Node* const function_addr = 111 ExternalConstant(ExternalReference::intl_convert_one_byte_to_lower()); 112 Node* const isolate_ptr = 113 ExternalConstant(ExternalReference::isolate_address(isolate())); 114 115 MachineType type_ptr = MachineType::Pointer(); 116 MachineType type_tagged = MachineType::AnyTagged(); 117 118 Node* const result = 119 CallCFunction3(type_tagged, type_tagged, type_tagged, type_ptr, 120 function_addr, src, dst, isolate_ptr); 121 122 Return(result); 123 } 124 125 BIND(&return_string); 126 Return(string); 127 128 BIND(&runtime); 129 { 130 Node* const result = CallRuntime(Runtime::kStringToLowerCaseIntl, 131 NoContextConstant(), string); 132 Return(result); 133 } 134 } 135 136 TF_BUILTIN(StringPrototypeToLowerCaseIntl, IntlBuiltinsAssembler) { 137 Node* const maybe_string = Parameter(Descriptor::kReceiver); 138 Node* const context = Parameter(Descriptor::kContext); 139 140 Node* const string = 141 ToThisString(context, maybe_string, "String.prototype.toLowerCase"); 142 143 Return(CallBuiltin(Builtins::kStringToLowerCaseIntl, context, string)); 144 } 145 146 void IntlBuiltinsAssembler::ListFormatCommon(TNode<Context> context, 147 TNode<Int32T> argc, 148 Runtime::FunctionId format_func_id, 149 const char* method_name) { 150 CodeStubArguments args(this, ChangeInt32ToIntPtr(argc)); 151 152 // Label has_list(this); 153 // 1. Let lf be this value. 154 // 2. If Type(lf) is not Object, throw a TypeError exception. 155 TNode<Object> receiver = args.GetReceiver(); 156 157 // 3. If lf does not have an [[InitializedListFormat]] internal slot, throw a 158 // TypeError exception. 159 ThrowIfNotInstanceType(context, receiver, JS_INTL_LIST_FORMAT_TYPE, 160 method_name); 161 TNode<JSListFormat> list_format = CAST(receiver); 162 163 // 4. If list is not provided or is undefined, then 164 TNode<Object> list = args.GetOptionalArgumentValue(0); 165 Label has_list(this); 166 { 167 GotoIfNot(IsUndefined(list), &has_list); 168 if (format_func_id == Runtime::kFormatList) { 169 // a. Return an empty String. 170 args.PopAndReturn(EmptyStringConstant()); 171 } else { 172 DCHECK_EQ(format_func_id, Runtime::kFormatListToParts); 173 // a. Return an empty Array. 174 args.PopAndReturn(AllocateEmptyJSArray(context)); 175 } 176 } 177 BIND(&has_list); 178 { 179 // 5. Let x be ? IterableToList(list). 180 IteratorBuiltinsAssembler iterator_assembler(state()); 181 // TODO(adamk): Consider exposing IterableToList as a buitin and calling 182 // it from here instead of inlining the operation. 183 TNode<JSArray> x = iterator_assembler.IterableToList(context, list); 184 185 // 6. Return ? FormatList(lf, x). 186 args.PopAndReturn(CallRuntime(format_func_id, context, list_format, x)); 187 } 188 } 189 190 TNode<JSArray> IntlBuiltinsAssembler::AllocateEmptyJSArray( 191 TNode<Context> context) { 192 return CAST(CodeStubAssembler::AllocateJSArray( 193 PACKED_ELEMENTS, 194 LoadJSArrayElementsMap(PACKED_ELEMENTS, LoadNativeContext(context)), 195 SmiConstant(0), SmiConstant(0))); 196 } 197 198 TF_BUILTIN(ListFormatPrototypeFormat, IntlBuiltinsAssembler) { 199 ListFormatCommon( 200 CAST(Parameter(Descriptor::kContext)), 201 UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)), 202 Runtime::kFormatList, "Intl.ListFormat.prototype.format"); 203 } 204 205 TF_BUILTIN(ListFormatPrototypeFormatToParts, IntlBuiltinsAssembler) { 206 ListFormatCommon( 207 CAST(Parameter(Descriptor::kContext)), 208 UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)), 209 Runtime::kFormatListToParts, "Intl.ListFormat.prototype.formatToParts"); 210 } 211 212 } // namespace internal 213 } // namespace v8 214