%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/builtins/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/builtins/wasm.tq |
// Copyright 2020 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. #include 'src/builtins/builtins-wasm-gen.h' namespace runtime { extern runtime WasmMemoryGrow(Context, WasmInstanceObject, Smi, Smi): Smi; extern runtime WasmRefFunc(Context, WasmInstanceObject, Smi): JSAny; extern runtime WasmInternalFunctionCreateExternal( Context, WasmInternalFunction): JSFunction; extern runtime WasmTableInit( Context, WasmInstanceObject, Object, Object, Smi, Smi, Smi): JSAny; extern runtime WasmTableCopy( Context, WasmInstanceObject, Object, Object, Smi, Smi, Smi): JSAny; extern runtime WasmTableFill( Context, WasmInstanceObject, Smi, Smi, Object, Smi): JSAny; extern runtime WasmTableGrow(Context, WasmInstanceObject, Smi, Object, Smi): Smi; extern runtime WasmFunctionTableGet(Context, WasmInstanceObject, Smi, Smi): JSAny; extern runtime WasmFunctionTableSet( Context, WasmInstanceObject, Smi, Smi, Object): JSAny; extern runtime ThrowRangeError(Context, Smi): never; extern runtime ThrowWasmError(Context, Smi): never; extern runtime WasmThrowRangeError(Context, Smi): never; extern runtime WasmThrowTypeError(Context, Smi, JSAny): never; extern runtime WasmThrowTypeErrorTwoArgs(Context, Smi, JSAny, JSAny): never; extern runtime WasmThrow(Context, Object, FixedArray): JSAny; extern runtime WasmReThrow(Context, Object): JSAny; extern runtime WasmTriggerTierUp(Context, WasmInstanceObject): JSAny; extern runtime WasmStackGuard(Context): JSAny; extern runtime ThrowWasmStackOverflow(Context): JSAny; extern runtime WasmTraceMemory(Context, Smi): JSAny; extern runtime WasmTraceEnter(Context): JSAny; extern runtime WasmTraceExit(Context, Smi): JSAny; extern runtime WasmAtomicNotify( Context, WasmInstanceObject, Smi, Number, Number): Smi; extern runtime WasmI32AtomicWait( Context, WasmInstanceObject, Smi, Number, Number, BigInt): Smi; extern runtime WasmI64AtomicWait( Context, WasmInstanceObject, Smi, Number, BigInt, BigInt): Smi; extern runtime WasmArrayCopy(Context, WasmArray, Smi, WasmArray, Smi, Smi): JSAny; extern runtime WasmArrayNewSegment( Context, WasmInstanceObject, Smi, Smi, Smi, Map): Object; extern runtime WasmStringNewSegmentWtf8( Context, WasmInstanceObject, Smi, Smi, Smi): String; extern runtime WasmArrayInitSegment( Context, WasmInstanceObject, Smi, WasmArray, Smi, Smi, Smi): JSAny; extern runtime WasmStringNewWtf8( Context, WasmInstanceObject, Smi, Smi, Number, Number): String|Null; extern runtime WasmStringNewWtf8Array(Context, Smi, WasmArray, Smi, Smi): String |Null; extern runtime WasmStringNewWtf16( Context, WasmInstanceObject, Smi, Number, Number): String; extern runtime WasmStringNewWtf16Array(Context, WasmArray, Smi, Smi): String; extern runtime WasmStringConst(Context, WasmInstanceObject, Smi): String; extern runtime WasmStringMeasureUtf8(Context, String): Number; extern runtime WasmStringMeasureWtf8(Context, String): Number; extern runtime WasmStringEncodeWtf8( Context, WasmInstanceObject, Smi, Smi, String, Number): Number; extern runtime WasmStringEncodeWtf8Array( Context, Smi, String, WasmArray, Number): Number; extern runtime WasmStringEncodeWtf16( Context, WasmInstanceObject, Smi, String, Number, Smi, Smi): JSAny; extern runtime WasmStringAsWtf8(Context, String): ByteArray; extern runtime WasmStringViewWtf8Encode( Context, WasmInstanceObject, Smi, ByteArray, Number, Number, Number): JSAny; extern runtime WasmStringViewWtf8Slice(Context, ByteArray, Number, Number): String; extern runtime WasmStringFromCodePoint(Context, Number): String; extern runtime WasmStringHash(NoContext, String): Smi; extern runtime WasmSubstring(Context, String, Smi, Smi): String; extern runtime WasmJSToWasmObject(Context, JSAny, Smi): JSAny; } namespace unsafe { extern macro Allocate(intptr): HeapObject; extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject; } namespace wasm { const kAnyType: constexpr int31 generates 'wasm::kWasmAnyRef.raw_bit_field()'; const kMaxPolymorphism: constexpr int31 generates 'wasm::kMaxPolymorphism'; extern macro WasmBuiltinsAssembler::LoadInstanceFromFrame(): WasmInstanceObject; // WasmInstanceObject has a field layout that Torque can't handle yet. // TODO(bbudge) Eliminate these functions when Torque is ready. extern macro WasmBuiltinsAssembler::LoadContextFromInstance( WasmInstanceObject): NativeContext; extern macro WasmBuiltinsAssembler::LoadTablesFromInstance(WasmInstanceObject): FixedArray; extern macro WasmBuiltinsAssembler::LoadInternalFunctionsFromInstance( WasmInstanceObject): FixedArray; extern macro WasmBuiltinsAssembler::LoadManagedObjectMapsFromInstance( WasmInstanceObject): FixedArray; extern macro WasmBuiltinsAssembler::LoadContextFromWasmOrJsFrame(): NativeContext; extern macro WasmBuiltinsAssembler::StringToFloat64(String): float64; macro LoadContextFromFrame(): NativeContext { return LoadContextFromInstance(LoadInstanceFromFrame()); } builtin WasmInt32ToHeapNumber(val: int32): HeapNumber { return AllocateHeapNumberWithValue(Convert<float64>(val)); } builtin WasmFuncRefToJS( implicit context: Context)(val: WasmInternalFunction|WasmNull): JSFunction |Null { typeswitch (val) { case (WasmNull): { return Null; } case (func: WasmInternalFunction): { const maybeExternal: Object = func.external; if (maybeExternal != Undefined) { return %RawDownCast<JSFunction>(maybeExternal); } tail runtime::WasmInternalFunctionCreateExternal(context, func); } } } builtin WasmTaggedNonSmiToInt32(implicit context: Context)(val: HeapObject): int32 { return ChangeTaggedNonSmiToInt32(val); } builtin WasmTaggedToFloat64(implicit context: Context)(val: JSAny): float64 { return ChangeTaggedToFloat64(val); } builtin WasmTaggedToFloat32(implicit context: Context)(val: JSAny): float32 { return TruncateFloat64ToFloat32(ChangeTaggedToFloat64(val)); } builtin WasmMemoryGrow(memIndex: int32, numPages: int32): int32 { dcheck(IsValidPositiveSmi(ChangeInt32ToIntPtr(memIndex))); if (!IsValidPositiveSmi(ChangeInt32ToIntPtr(numPages))) return Int32Constant(-1); const instance: WasmInstanceObject = LoadInstanceFromFrame(); const context: NativeContext = LoadContextFromInstance(instance); const result: Smi = runtime::WasmMemoryGrow( context, instance, SmiFromInt32(memIndex), SmiFromInt32(numPages)); return SmiToInt32(result); } builtin WasmTableInit( dstRaw: uint32, srcRaw: uint32, sizeRaw: uint32, tableIndex: Smi, segmentIndex: Smi): JSAny { try { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const dst: Smi = Convert<PositiveSmi>(dstRaw) otherwise TableOutOfBounds; const src: Smi = Convert<PositiveSmi>(srcRaw) otherwise TableOutOfBounds; const size: Smi = Convert<PositiveSmi>(sizeRaw) otherwise TableOutOfBounds; tail runtime::WasmTableInit( LoadContextFromInstance(instance), instance, tableIndex, segmentIndex, dst, src, size); } label TableOutOfBounds deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableCopy( dstRaw: uint32, srcRaw: uint32, sizeRaw: uint32, dstTable: Smi, srcTable: Smi): JSAny { try { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const dst: Smi = Convert<PositiveSmi>(dstRaw) otherwise TableOutOfBounds; const src: Smi = Convert<PositiveSmi>(srcRaw) otherwise TableOutOfBounds; const size: Smi = Convert<PositiveSmi>(sizeRaw) otherwise TableOutOfBounds; tail runtime::WasmTableCopy( LoadContextFromInstance(instance), instance, dstTable, srcTable, dst, src, size); } label TableOutOfBounds deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableFill( table: Smi, startRaw: uint32, countRaw: uint32, value: Object): JSAny { try { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const start: Smi = Convert<PositiveSmi>(startRaw) otherwise TableOutOfBounds; const count: Smi = Convert<PositiveSmi>(countRaw) otherwise TableOutOfBounds; tail runtime::WasmTableFill( LoadContextFromInstance(instance), instance, table, start, value, count); } label TableOutOfBounds deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableGrow(table: Smi, deltaRaw: uint32, value: Object): Smi { try { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const delta: Smi = Convert<PositiveSmi>(deltaRaw) otherwise TableOutOfBounds; tail runtime::WasmTableGrow( LoadContextFromInstance(instance), instance, table, value, delta); } label TableOutOfBounds deferred { return -1; } } builtin WasmTableGet(tableIndex: intptr, index: int32): Object { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const entryIndex: intptr = ChangeInt32ToIntPtr(index); try { dcheck(IsValidPositiveSmi(tableIndex)); if (!IsValidPositiveSmi(entryIndex)) goto IndexOutOfRange; const tables: FixedArray = LoadTablesFromInstance(instance); const table: WasmTableObject = %RawDownCast<WasmTableObject>( LoadFixedArrayElement(tables, tableIndex)); const entriesCount: intptr = Convert<intptr, Smi>(table.current_length); if (entryIndex >= entriesCount) goto IndexOutOfRange; const entries: FixedArray = table.entries; const entry: Object = LoadFixedArrayElement(entries, entryIndex); return entry; } label IndexOutOfRange deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableSet(tableIndex: intptr, index: int32, value: Object): Object { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const entryIndex: intptr = ChangeInt32ToIntPtr(index); try { dcheck(IsValidPositiveSmi(tableIndex)); if (!IsValidPositiveSmi(entryIndex)) goto IndexOutOfRange; const tables: FixedArray = LoadTablesFromInstance(instance); const table: WasmTableObject = %RawDownCast<WasmTableObject>( LoadFixedArrayElement(tables, tableIndex)); const entriesCount: intptr = Convert<intptr, Smi>(table.current_length); if (entryIndex >= entriesCount) goto IndexOutOfRange; const entries: FixedArray = table.entries; StoreFixedArrayElement(entries, entryIndex, value); return Undefined; } label IndexOutOfRange deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableGetFuncRef(tableIndex: intptr, index: int32): Object { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const entryIndex: intptr = ChangeInt32ToIntPtr(index); try { dcheck(IsValidPositiveSmi(tableIndex)); if (!IsValidPositiveSmi(entryIndex)) goto IndexOutOfRange; const tables: FixedArray = LoadTablesFromInstance(instance); const table: WasmTableObject = %RawDownCast<WasmTableObject>( LoadFixedArrayElement(tables, tableIndex)); const entriesCount: intptr = Convert<intptr, Smi>(table.current_length); if (entryIndex >= entriesCount) goto IndexOutOfRange; const entries: FixedArray = table.entries; const entry: Object = LoadFixedArrayElement(entries, entryIndex); try { const entryObject: HeapObject = TaggedToHeapObject<HeapObject>(entry) otherwise ReturnEntry; if (IsTuple2Map(entryObject.map)) goto CallRuntime; goto ReturnEntry; } label ReturnEntry { return entry; } } label CallRuntime deferred { tail runtime::WasmFunctionTableGet( LoadContextFromInstance(instance), instance, SmiFromIntPtr(tableIndex), SmiFromIntPtr(entryIndex)); } label IndexOutOfRange deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmTableSetFuncRef(tableIndex: intptr, index: int32, value: Object): Object { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const entryIndex: intptr = ChangeInt32ToIntPtr(index); try { dcheck(IsValidPositiveSmi(tableIndex)); if (!IsValidPositiveSmi(entryIndex)) goto IndexOutOfRange; const tables: FixedArray = LoadTablesFromInstance(instance); const table: WasmTableObject = %RawDownCast<WasmTableObject>( LoadFixedArrayElement(tables, tableIndex)); const entriesCount: intptr = Convert<intptr, Smi>(table.current_length); if (entryIndex >= entriesCount) goto IndexOutOfRange; tail runtime::WasmFunctionTableSet( LoadContextFromInstance(instance), instance, SmiFromIntPtr(tableIndex), SmiFromIntPtr(entryIndex), value); } label IndexOutOfRange deferred { tail ThrowWasmTrapTableOutOfBounds(); } } builtin WasmRefFunc(index: uint32): Object { const instance: WasmInstanceObject = LoadInstanceFromFrame(); try { const table: FixedArray = LoadInternalFunctionsFromInstance(instance); const functionIndex: intptr = Signed(ChangeUint32ToWord(index)); const result: Object = LoadFixedArrayElement(table, functionIndex); // {result} is either a funcref or nullptr. A Smi check is the fastest // way to distinguish these two cases. if (TaggedIsSmi(result)) goto CallRuntime; return result; } label CallRuntime deferred { tail runtime::WasmRefFunc( LoadContextFromInstance(instance), instance, SmiFromUint32(index)); } } builtin WasmInternalFunctionCreateExternal( context: Context, func: WasmInternalFunction): JSFunction { return runtime::WasmInternalFunctionCreateExternal(context, func); } builtin WasmAllocateZeroedFixedArray(size: intptr): FixedArray { if (size == 0) return kEmptyFixedArray; const result = UnsafeCast<FixedArray>(AllocateFixedArray( ElementsKind::PACKED_ELEMENTS, size, AllocationFlag::kNone)); FillEntireFixedArrayWithSmiZero(ElementsKind::PACKED_ELEMENTS, result, size); return result; } builtin WasmAllocateFixedArray(size: intptr): FixedArray { if (size == 0) return kEmptyFixedArray; return UnsafeCast<FixedArray>(AllocateFixedArray( ElementsKind::PACKED_ELEMENTS, size, AllocationFlag::kNone)); } builtin WasmThrow(tag: Object, values: FixedArray): JSAny { tail runtime::WasmThrow(LoadContextFromFrame(), tag, values); } builtin WasmRethrow(exception: Object): JSAny { if (exception == Null) tail ThrowWasmTrapRethrowNull(); tail runtime::WasmReThrow(LoadContextFromFrame(), exception); } // We need this for frames that do not have the instance in the parameters. // Currently, this is CapiCallWrapper frames. builtin WasmRethrowExplicitContext( exception: Object, explicitContext: Context): JSAny { if (exception == Null) tail ThrowWasmTrapRethrowNull(); tail runtime::WasmReThrow(explicitContext, exception); } builtin WasmTriggerTierUp(): JSAny { const instance: WasmInstanceObject = LoadInstanceFromFrame(); tail runtime::WasmTriggerTierUp(LoadContextFromFrame(), instance); } builtin WasmStackGuard(): JSAny { tail runtime::WasmStackGuard(LoadContextFromFrame()); } builtin WasmStackOverflow(): JSAny { tail runtime::ThrowWasmStackOverflow(LoadContextFromFrame()); } builtin WasmTraceMemory(info: Smi): JSAny { tail runtime::WasmTraceMemory(LoadContextFromFrame(), info); } builtin WasmTraceEnter(): JSAny { tail runtime::WasmTraceEnter(LoadContextFromFrame()); } builtin WasmTraceExit(info: Smi): JSAny { tail runtime::WasmTraceExit(LoadContextFromFrame(), info); } builtin WasmAllocateJSArray(implicit context: Context)(size: Smi): JSArray { const map: Map = GetFastPackedElementsJSArrayMap(); return AllocateJSArray(ElementsKind::PACKED_ELEMENTS, map, size, size); } builtin WasmAllocateStructWithRtt(rtt: Map, instanceSize: int32): HeapObject { const result: HeapObject = unsafe::Allocate(Convert<intptr>(instanceSize)); *UnsafeConstCast(&result.map) = rtt; // TODO(ishell): consider removing properties_or_hash field from WasmObjects. %RawDownCast<WasmStruct>(result).properties_or_hash = kEmptyFixedArray; return result; } builtin WasmAllocateArray_Uninitialized( rtt: Map, length: uint32, elementSize: uint32): WasmArray { // instanceSize = RoundUp(elementSize * length, kObjectAlignment) // + WasmArray::kHeaderSize const instanceSize: intptr = torque_internal::AlignTagged( Convert<intptr>(length) * Convert<intptr>(elementSize)) + Convert<intptr>(kWasmArrayHeaderSize); const result: HeapObject = unsafe::Allocate(instanceSize); *UnsafeConstCast(&result.map) = rtt; // TODO(ishell): consider removing properties_or_hash field from WasmObjects. %RawDownCast<WasmArray>(result).properties_or_hash = kEmptyFixedArray; %RawDownCast<WasmArray>(result).length = length; return %RawDownCast<WasmArray>(result); } builtin WasmArrayNewSegment( segmentIndex: uint32, offset: uint32, length: uint32, isElement: Smi, rtt: Map): Object { const instance = LoadInstanceFromFrame(); try { const smiOffset = Convert<PositiveSmi>(offset) otherwise SegmentOutOfBounds; const smiLength = Convert<PositiveSmi>(length) otherwise ArrayTooLarge; tail runtime::WasmArrayNewSegment( LoadContextFromInstance(instance), instance, SmiFromUint32(segmentIndex), smiOffset, smiLength, rtt); } label SegmentOutOfBounds { if (isElement == SmiConstant(0)) { tail ThrowWasmTrapDataSegmentOutOfBounds(); } else { tail ThrowWasmTrapElementSegmentOutOfBounds(); } } label ArrayTooLarge { tail ThrowWasmTrapArrayTooLarge(); } } // {segmentIndex} has to be tagged as a possible stack parameter. builtin WasmArrayInitSegment( arrayIndex: uint32, segmentOffset: uint32, length: uint32, segmentIndex: Smi, isElement: Smi, arrayRaw: HeapObject): JSAny { const instance = LoadInstanceFromFrame(); if (arrayRaw == kWasmNull) { tail ThrowWasmTrapNullDereference(); } const array = %RawDownCast<WasmArray>(arrayRaw); try { const smiArrayIndex = Convert<PositiveSmi>(arrayIndex) otherwise ArrayOutOfBounds; const smiOffset = Convert<PositiveSmi>(segmentOffset) otherwise SegmentOutOfBounds; const smiLength = Convert<PositiveSmi>(length) otherwise ArrayOutOfBounds; tail runtime::WasmArrayInitSegment( LoadContextFromInstance(instance), instance, segmentIndex, array, smiArrayIndex, smiOffset, smiLength); } label SegmentOutOfBounds { if (isElement == SmiConstant(0)) { tail ThrowWasmTrapDataSegmentOutOfBounds(); } else { tail ThrowWasmTrapElementSegmentOutOfBounds(); } } label ArrayOutOfBounds { tail ThrowWasmTrapArrayOutOfBounds(); } } // We put all uint32 parameters at the beginning so that they are assigned to // registers. builtin WasmArrayCopyWithChecks( dstIndex: uint32, srcIndex: uint32, length: uint32, dstObject: Object, srcObject: Object): JSAny { if (dstObject == kWasmNull) tail ThrowWasmTrapNullDereference(); if (srcObject == kWasmNull) tail ThrowWasmTrapNullDereference(); const dstArray = UnsafeCast<WasmArray>(dstObject); const srcArray = UnsafeCast<WasmArray>(srcObject); // Check that the end of the copying range is in-bounds and that the range // does not overflow. if (dstIndex + length > dstArray.length || dstIndex + length < dstIndex || srcIndex + length > srcArray.length || srcIndex + length < srcIndex) { tail ThrowWasmTrapArrayOutOfBounds(); } if (length == 0) return Undefined; tail runtime::WasmArrayCopy( LoadContextFromFrame(), dstArray, SmiFromUint32(dstIndex), srcArray, SmiFromUint32(srcIndex), SmiFromUint32(length)); } builtin WasmArrayCopy( dstIndex: uint32, srcIndex: uint32, length: uint32, dstObject: Object, srcObject: Object): JSAny { if (dstObject == kWasmNull) tail ThrowWasmTrapNullDereference(); if (srcObject == kWasmNull) tail ThrowWasmTrapNullDereference(); if (length == 0) return Undefined; tail runtime::WasmArrayCopy( LoadContextFromFrame(), UnsafeCast<WasmArray>(dstObject), SmiFromUint32(dstIndex), UnsafeCast<WasmArray>(srcObject), SmiFromUint32(srcIndex), SmiFromUint32(length)); } builtin WasmUint32ToNumber(value: uint32): Number { return ChangeUint32ToTagged(value); } builtin UintPtr53ToNumber(value: uintptr): Number { if (value <= kSmiMaxValue) return Convert<Smi>(Convert<intptr>(value)); const valueFloat = ChangeUintPtrToFloat64(value); // Values need to be within [0..2^53], such that they can be represented as // float64. dcheck(ChangeFloat64ToUintPtr(valueFloat) == value); return AllocateHeapNumberWithValue(valueFloat); } extern builtin I64ToBigInt(intptr): BigInt; extern builtin I32PairToBigInt(/*low*/ intptr, /*high*/ intptr): BigInt; builtin WasmAtomicNotify(memoryIndex: int32, offset: uintptr, count: uint32): uint32 { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const result: Smi = runtime::WasmAtomicNotify( LoadContextFromInstance(instance), instance, SmiFromInt32(memoryIndex), UintPtr53ToNumber(offset), WasmUint32ToNumber(count)); return Unsigned(SmiToInt32(result)); } builtin WasmI32AtomicWait( memIndex: int32, offset: uintptr, expectedValue: int32, timeout: BigInt): uint32 { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const result: Smi = runtime::WasmI32AtomicWait( LoadContextFromInstance(instance), instance, SmiFromInt32(memIndex), UintPtr53ToNumber(offset), ChangeInt32ToTagged(expectedValue), timeout); return Unsigned(SmiToInt32(result)); } builtin WasmI64AtomicWait( memIndex: int32, offset: uintptr, expectedValue: BigInt, timeout: BigInt): uint32 { const instance: WasmInstanceObject = LoadInstanceFromFrame(); const result: Smi = runtime::WasmI64AtomicWait( LoadContextFromInstance(instance), instance, SmiFromInt32(memIndex), UintPtr53ToNumber(offset), expectedValue, timeout); return Unsigned(SmiToInt32(result)); } // Type feedback collection support for `call_ref`. extern macro LoadCodeInstructionStart(Code): RawPtr; struct TargetAndInstance { target: RawPtr; instance: HeapObject; // WasmInstanceObject or WasmApiFunctionRef } macro GetTargetAndInstance(funcref: WasmInternalFunction): TargetAndInstance { const ref = funcref.ref; let target = funcref.call_target_ptr; if (Signed(target) == IntPtrConstant(0)) { target = LoadCodeInstructionStart(funcref.code); } return TargetAndInstance{target: target, instance: ref}; } // Vector format: // Two slots per call_ref instruction. These slots' values can be: // - uninitialized: (0, <unused>). Note: we use {0} as the sentinel because // it also works as default for vector slots used as counts. // - monomorphic: (funcref, count (smi)). The second slot is a counter for how // often the funcref in the first slot has been seen. // - polymorphic: (fixed_array, <unused>). In this case, the array // contains 2..kMaxPolymorphism pairs (funcref, count (smi)) // - megamorphic: ("megamorphic" sentinel, <unused>) // // TODO(rstz): The counter might overflow if it exceeds the range of a Smi. // This can lead to incorrect inlining decisions. builtin CallRefIC( vector: FixedArray, index: intptr, funcref: WasmInternalFunction): TargetAndInstance { const value = vector.objects[index]; if (value == funcref) { // Monomorphic hit. Check for this case first to maximize its performance. const count = UnsafeCast<Smi>(vector.objects[index + 1]) + SmiConstant(1); vector.objects[index + 1] = count; return GetTargetAndInstance(funcref); } // Check for polymorphic hit; its performance is second-most-important. if (Is<FixedArray>(value)) { const entries = UnsafeCast<FixedArray>(value); for (let i: intptr = 0; i < entries.length_intptr; i += 2) { if (entries.objects[i] == funcref) { // Polymorphic hit. const count = UnsafeCast<Smi>(entries.objects[i + 1]) + SmiConstant(1); entries.objects[i + 1] = count; return GetTargetAndInstance(funcref); } } } // All other cases are some sort of miss and must compute the target/ // instance. They all fall through to returning the computed data. const result = GetTargetAndInstance(funcref); if (TaggedEqual(value, SmiConstant(0))) { // Was uninitialized. vector.objects[index] = funcref; vector.objects[index + 1] = SmiConstant(1); } else if (Is<FixedArray>(value)) { // Polymorphic miss. const entries = UnsafeCast<FixedArray>(value); const kMaxSlots = kMaxPolymorphism * 2; // 2 slots per entry. if (entries.length == SmiConstant(kMaxSlots)) { vector.objects[index] = ic::kMegamorphicSymbol; vector.objects[index + 1] = ic::kMegamorphicSymbol; } else { const newEntries = UnsafeCast<FixedArray>(AllocateFixedArray( ElementsKind::PACKED_ELEMENTS, entries.length_intptr + 2, AllocationFlag::kNone)); for (let i: intptr = 0; i < entries.length_intptr; i++) { newEntries.objects[i] = entries.objects[i]; } const newIndex = entries.length_intptr; newEntries.objects[newIndex] = funcref; newEntries.objects[newIndex + 1] = SmiConstant(1); vector.objects[index] = newEntries; } } else if (Is<WasmInternalFunction>(value)) { // Monomorphic miss. const newEntries = UnsafeCast<FixedArray>(AllocateFixedArray( ElementsKind::PACKED_ELEMENTS, 4, AllocationFlag::kNone)); newEntries.objects[0] = value; newEntries.objects[1] = vector.objects[index + 1]; newEntries.objects[2] = funcref; newEntries.objects[3] = SmiConstant(1); vector.objects[index] = newEntries; // Clear the first entry's counter; the specific value we write doesn't // matter. vector.objects[index + 1] = Undefined; } // The "ic::IsMegamorphic(value)" case doesn't need to do anything. return result; } extern macro TryHasOwnProperty(HeapObject, Map, InstanceType, Name): never labels Found, NotFound, Bailout; type OnNonExistent constexpr 'OnNonExistent'; const kReturnUndefined: constexpr OnNonExistent generates 'OnNonExistent::kReturnUndefined'; extern macro SmiConstant(constexpr OnNonExistent): Smi; extern transitioning builtin GetPropertyWithReceiver( implicit context: Context)(JSAny, Name, JSAny, Smi): JSAny; transitioning builtin WasmGetOwnProperty( implicit context: Context)(object: Object, uniqueName: Name): JSAny { try { const heapObject: HeapObject = TaggedToHeapObject(object) otherwise NotFound; const receiver: JSReceiver = Cast<JSReceiver>(heapObject) otherwise NotFound; try { TryHasOwnProperty( receiver, receiver.map, receiver.instanceType, uniqueName) otherwise Found, NotFound, NotFound; } label Found { tail GetPropertyWithReceiver( receiver, uniqueName, receiver, SmiConstant(kReturnUndefined)); } } label NotFound deferred { return Undefined; } } // Trap builtins. builtin WasmTrap(error: Smi): JSAny { tail runtime::ThrowWasmError(LoadContextFromWasmOrJsFrame(), error); } builtin ThrowWasmTrapUnreachable(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapUnreachable)); } builtin ThrowWasmTrapMemOutOfBounds(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapMemOutOfBounds)); } builtin ThrowWasmTrapUnalignedAccess(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapUnalignedAccess)); } builtin ThrowWasmTrapDivByZero(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapDivByZero)); } builtin ThrowWasmTrapDivUnrepresentable(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapDivUnrepresentable)); } builtin ThrowWasmTrapRemByZero(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapRemByZero)); } builtin ThrowWasmTrapFloatUnrepresentable(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapFloatUnrepresentable)); } builtin ThrowWasmTrapFuncSigMismatch(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapFuncSigMismatch)); } builtin ThrowWasmTrapDataSegmentOutOfBounds(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapDataSegmentOutOfBounds)); } builtin ThrowWasmTrapElementSegmentOutOfBounds(): JSAny { tail WasmTrap( SmiConstant(MessageTemplate::kWasmTrapElementSegmentOutOfBounds)); } builtin ThrowWasmTrapTableOutOfBounds(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapTableOutOfBounds)); } builtin ThrowWasmTrapRethrowNull(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapRethrowNull)); } builtin ThrowWasmTrapNullDereference(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapNullDereference)); } builtin ThrowWasmTrapIllegalCast(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapIllegalCast)); } builtin ThrowWasmTrapArrayOutOfBounds(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapArrayOutOfBounds)); } builtin ThrowWasmTrapArrayTooLarge(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapArrayTooLarge)); } builtin ThrowWasmTrapStringOffsetOutOfBounds(): JSAny { tail WasmTrap(SmiConstant(MessageTemplate::kWasmTrapStringOffsetOutOfBounds)); } macro WasmTypeInfo(map: Map): WasmTypeInfo { return UnsafeCast<WasmTypeInfo>( map.constructor_or_back_pointer_or_native_context); } const kWasmI16ValueType: constexpr int32 generates 'wasm::ValueType::Primitive(wasm::kI16).raw_bit_field()'; const kWasmI8ValueType: constexpr int32 generates 'wasm::ValueType::Primitive(wasm::kI8).raw_bit_field()'; const kWasmArrayTypeRepOffset: constexpr intptr generates 'wasm::ArrayType::kRepOffset'; const kWasmValueTypeBitFieldOffset: constexpr intptr generates 'wasm::ValueType::kBitFieldOffset'; macro IsWord16WasmArrayMap(map: Map): bool { const arrayTypePtr: RawPtr<int32> = %RawDownCast<RawPtr<int32>>( WasmTypeInfo(map).native_type_ptr + kWasmArrayTypeRepOffset + kWasmValueTypeBitFieldOffset); const arrayTypeRef: &int32 = torque_internal::unsafe::NewOffHeapReference(arrayTypePtr); return *arrayTypeRef == kWasmI16ValueType; } macro IsWord8WasmArrayMap(map: Map): bool { const arrayTypePtr: RawPtr<int32> = %RawDownCast<RawPtr<int32>>( WasmTypeInfo(map).native_type_ptr + kWasmArrayTypeRepOffset + kWasmValueTypeBitFieldOffset); const arrayTypeRef: &int32 = torque_internal::unsafe::NewOffHeapReference(arrayTypePtr); return *arrayTypeRef == kWasmI8ValueType; } macro GetRefAt<T: type, From: type>(base: From, offset: intptr): &T { return torque_internal::unsafe::NewOffHeapReference<T>( %RawDownCast<RawPtr<T>>(base + offset)); } extern macro LoadPointerFromRootRegister(intptr): RawPtr; const kThreadInWasmFlagAddressOffset: constexpr intptr generates 'Isolate::thread_in_wasm_flag_address_offset()'; const kActiveSuspenderOffset: constexpr intptr generates 'IsolateData::root_slot_offset(RootIndex::kActiveSuspender)'; macro ModifyThreadInWasmFlag(newValue: int32): void { const threadInWasmFlagAddress = LoadPointerFromRootRegister(kThreadInWasmFlagAddressOffset); const threadInWasmFlagRef = GetRefAt<int32>(threadInWasmFlagAddress, 0); *threadInWasmFlagRef = newValue; } macro ModifyWasmToJSCounter(increment: int32): void { const rootSlot = LoadPointerFromRootRegister(kActiveSuspenderOffset); const rawSuspender = BitcastWordToTagged(rootSlot); typeswitch (rawSuspender) { case (activeSuspender: WasmSuspenderObject): { const current = Signed(activeSuspender.wasm_to_js_counter); activeSuspender.wasm_to_js_counter = Unsigned(current + increment); } case (Object): { // Nothing to do, but the case is needed by the Torque compiler. } } } builtin WasmStringNewWtf8( offset: uint32, size: uint32, memory: Smi, utf8Variant: Smi): String|Null { const instance = LoadInstanceFromFrame(); tail runtime::WasmStringNewWtf8( LoadContextFromInstance(instance), instance, memory, utf8Variant, WasmUint32ToNumber(offset), WasmUint32ToNumber(size)); } builtin WasmStringNewWtf8Array( start: uint32, end: uint32, array: WasmArray, utf8Variant: Smi): String |Null { // This can be called from Wasm and from WebAssembly.String.* JS builtins. const context = LoadContextFromWasmOrJsFrame(); try { if (array.length < end) goto OffsetOutOfRange; if (end < start) goto OffsetOutOfRange; tail runtime::WasmStringNewWtf8Array( context, utf8Variant, array, SmiFromUint32(start), SmiFromUint32(end)); } label OffsetOutOfRange deferred { const error = MessageTemplate::kWasmTrapArrayOutOfBounds; runtime::ThrowWasmError(context, SmiConstant(error)); } } builtin WasmStringNewWtf16(memory: uint32, offset: uint32, size: uint32): String { const instance = LoadInstanceFromFrame(); tail runtime::WasmStringNewWtf16( LoadContextFromInstance(instance), instance, SmiFromUint32(memory), WasmUint32ToNumber(offset), WasmUint32ToNumber(size)); } struct TwoByteToOneByteIterator { macro Next(): char8 labels NoMore { if (this.start == this.end) goto NoMore; const raw: char16 = *torque_internal::unsafe::NewReference<char16>( this.object, this.start); const result: char8 = %RawDownCast<char8>(raw & 0xFF); this.start += 2; return result; } object: HeapObject|TaggedZeroPattern; start: intptr; end: intptr; } macro StringFromTwoByteSlice(length: uint32, slice: ConstSlice<char16>): String { // Ideas for additional future improvements: // (1) We could add a fast path for very short strings, e.g. <= 8 chars, // and just allocate two-byte strings for them. That would save time // here, and would only waste a couple of bytes at most. A concern is // that such strings couldn't take one-byte fast paths later on, e.g. // in toLower/toUpper case conversions. // (2) We could load more than one array element at a time, e.g. using // intptr-wide loads, or possibly even wider SIMD instructions. We'd // have to make sure that non-aligned start offsets are handled, // and the implementation would become more platform-specific. // (3) We could shift the problem around by allocating two-byte strings // here and checking whether they're one-byte-compatible later, e.g. // when promoting them from new to old space. Drawback: rewriting // strings to different maps isn't great for optimized code that's // based on collected type feedback, or that wants to elide duplicate // map checks within the function. // (4) We could allocate space for a two-byte string, then optimistically // start writing one-byte characters into it, and then either restart // in two-byte mode if needed, or return the over-allocated bytes to // the allocator in the end. // (5) We could standardize a `string.new_ascii_array` instruction, which // could safely produce one-byte strings without checking characters. // See https://github.com/WebAssembly/stringref/issues/53. try { // To reduce the amount of branching, check 8 code units at a time. The // tradeoff for choosing 8 is that we want to check for early termination // of the loop often (to avoid unnecessary work) but not too often // (because each check has a cost). let i: intptr = 0; const intptrLength = slice.length; const eightElementLoopEnd = intptrLength - 8; while (i <= eightElementLoopEnd) { const bits = Convert<uint32>(*slice.UncheckedAtIndex(i)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 1)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 2)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 3)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 4)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 5)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 6)) | Convert<uint32>(*slice.UncheckedAtIndex(i + 7)); if (bits > 0xFF) goto TwoByte; i += 8; } let bits: uint32 = 0; while (i < intptrLength) { bits |= Convert<uint32>(*slice.UncheckedAtIndex(i)); i += 1; } if (bits > 0xFF) goto TwoByte; } label TwoByte { return AllocateSeqTwoByteString(length, slice.Iterator()); } const end = slice.offset + torque_internal::TimesSizeOf<char16>(slice.length); return AllocateNonEmptySeqOneByteString(length, TwoByteToOneByteIterator{ object: slice.object, start: slice.offset, end: end }); } builtin WasmStringNewWtf16Array(array: WasmArray, start: uint32, end: uint32): String { try { if (array.length < end) goto OffsetOutOfRange; if (end < start) goto OffsetOutOfRange; const length: uint32 = end - start; if (length == 0) return kEmptyString; if (length == 1) { const offset = kWasmArrayHeaderSize + torque_internal::TimesSizeOf<char16>(Convert<intptr>(start)); const code: char16 = *torque_internal::unsafe::NewReference<char16>( array, offset); // This makes sure we check the SingleCharacterStringTable. return StringFromSingleCharCode(code); } // Calling into the runtime has overhead, but once we're there it's faster, // so it pays off for long strings. The threshold has been determined // experimentally. if (length >= 32) goto Runtime; const intptrLength = Convert<intptr>(length); const arrayContent = torque_internal::unsafe::NewConstSlice<char16>( array, kWasmArrayHeaderSize, Convert<intptr>(array.length)); const substring = Subslice(arrayContent, Convert<intptr>(start), intptrLength) otherwise goto OffsetOutOfRange; return StringFromTwoByteSlice(length, substring); } label OffsetOutOfRange deferred { // This can be called from Wasm and from WebAssembly.String.* JS builtins. const context = LoadContextFromWasmOrJsFrame(); const error = MessageTemplate::kWasmTrapArrayOutOfBounds; runtime::ThrowWasmError(context, SmiConstant(error)); } label Runtime deferred { const context = LoadContextFromWasmOrJsFrame(); tail runtime::WasmStringNewWtf16Array( context, array, SmiFromUint32(start), SmiFromUint32(end)); } } // For imports based string constants. // Always returns a String if it didn't trap; typed "JSAny" to satisfy // Torque's type checker for tail calls. builtin WasmStringFromDataSegment( segmentLength: uint32, arrayStart: uint32, arrayEnd: uint32, segmentIndex: Smi, segmentOffset: Smi): JSAny { const instance = LoadInstanceFromFrame(); try { const segmentOffsetU: uint32 = Unsigned(SmiToInt32(segmentOffset)); if (segmentLength > Convert<uint32>(kSmiMax) - segmentOffsetU) { goto SegmentOOB; } if (arrayStart > segmentLength) goto ArrayOutOfBounds; if (arrayEnd < arrayStart) goto ArrayOutOfBounds; const arrayLength = arrayEnd - arrayStart; if (arrayLength > segmentLength - arrayStart) goto ArrayOutOfBounds; const smiOffset = Convert<PositiveSmi>(segmentOffsetU + arrayStart) otherwise SegmentOOB; const smiLength = Convert<PositiveSmi>(arrayLength) otherwise SegmentOOB; tail runtime::WasmStringNewSegmentWtf8( LoadContextFromInstance(instance), instance, segmentIndex, smiOffset, smiLength); } label SegmentOOB deferred { tail ThrowWasmTrapElementSegmentOutOfBounds(); } label ArrayOutOfBounds deferred { tail ThrowWasmTrapArrayOutOfBounds(); } } // Contract: input is any string, output is a string that the TF operator // "StringPrepareForGetCodeunit" can handle. builtin WasmStringAsWtf16(str: String): String { const cons = Cast<ConsString>(str) otherwise return str; return Flatten(cons); } builtin WasmStringConst(index: uint32): String { const instance = LoadInstanceFromFrame(); tail runtime::WasmStringConst( LoadContextFromInstance(instance), instance, SmiFromUint32(index)); } builtin WasmStringMeasureUtf8(string: String): int32 { const result = runtime::WasmStringMeasureUtf8(LoadContextFromFrame(), string); return Signed(ChangeNumberToUint32(result)); } builtin WasmStringMeasureWtf8(string: String): int32 { const result = runtime::WasmStringMeasureWtf8(LoadContextFromFrame(), string); return Signed(ChangeNumberToUint32(result)); } builtin WasmStringEncodeWtf8( string: String, offset: uint32, memory: Smi, utf8Variant: Smi): uint32 { const instance = LoadInstanceFromFrame(); const result = runtime::WasmStringEncodeWtf8( LoadContextFromInstance(instance), instance, memory, utf8Variant, string, WasmUint32ToNumber(offset)); return ChangeNumberToUint32(result); } builtin WasmStringEncodeWtf8Array( string: String, array: WasmArray, start: uint32, utf8Variant: Smi): uint32 { const instance = LoadInstanceFromFrame(); const result = runtime::WasmStringEncodeWtf8Array( LoadContextFromInstance(instance), utf8Variant, string, array, WasmUint32ToNumber(start)); return ChangeNumberToUint32(result); } builtin WasmStringEncodeWtf16(string: String, offset: uint32, memory: Smi): uint32 { const instance = LoadInstanceFromFrame(); runtime::WasmStringEncodeWtf16( LoadContextFromInstance(instance), instance, memory, string, WasmUint32ToNumber(offset), SmiConstant(0), SmiFromInt32(string.length)); return Unsigned(string.length); } builtin WasmStringEncodeWtf16Array( string: String, array: WasmArray, start: uint32): uint32 { try { if (start > array.length) goto OffsetOutOfRange; if (array.length - start < Unsigned(string.length)) goto OffsetOutOfRange; const byteOffset: intptr = kWasmArrayHeaderSize + torque_internal::TimesSizeOf<char16>(Convert<intptr>(start)); const arrayContent = torque_internal::unsafe::NewMutableSlice<char16>( array, byteOffset, string.length_intptr); try { StringToSlice(string) otherwise OneByte, TwoByte; } label OneByte(slice: ConstSlice<char8>) { let fromIt = slice.Iterator(); let toIt = arrayContent.Iterator(); while (true) { let toRef = toIt.NextReference() otherwise break; *toRef = %RawDownCast<char16>(Convert<uint16>(fromIt.NextNotEmpty())); } } label TwoByte(slice: ConstSlice<char16>) { let fromIt = slice.Iterator(); let toIt = arrayContent.Iterator(); while (true) { let toRef = toIt.NextReference() otherwise break; *toRef = fromIt.NextNotEmpty(); } } return Unsigned(string.length); } label OffsetOutOfRange deferred { const error = MessageTemplate::kWasmTrapArrayOutOfBounds; runtime::ThrowWasmError(LoadContextFromWasmOrJsFrame(), SmiConstant(error)); } } builtin ThrowToLowerCaseCalledOnNull(): JSAny { const context = LoadContextFromFrame(); const error = MessageTemplate::kCalledOnNullOrUndefined; const name = StringConstant('String.prototype.toLowerCase'); runtime::WasmThrowTypeError(context, SmiConstant(error), name); } builtin ThrowIndexOfCalledOnNull(): JSAny { const context = LoadContextFromFrame(); const error = MessageTemplate::kCalledOnNullOrUndefined; const name = StringConstant('String.prototype.indexOf'); runtime::WasmThrowTypeError(context, SmiConstant(error), name); } builtin ThrowDataViewGetInt32DetachedError(): JSAny { const context = LoadContextFromFrame(); const error = MessageTemplate::kDetachedOperation; const name = StringConstant('DataView.prototype.getInt32'); runtime::WasmThrowTypeError(context, SmiConstant(error), name); } builtin ThrowDataViewGetInt32OutOfBounds(): JSAny { const context = LoadContextFromFrame(); const error = MessageTemplate::kInvalidDataViewAccessorOffset; runtime::WasmThrowRangeError(context, SmiConstant(error)); } builtin ThrowDataViewGetInt32TypeError(value: JSAny): JSAny { const context = LoadContextFromFrame(); const error = MessageTemplate::kIncompatibleMethodReceiver; const name = StringConstant('DataView.prototype.getInt32'); runtime::WasmThrowTypeErrorTwoArgs(context, SmiConstant(error), name, value); } builtin WasmStringConcat(a: String, b: String): String { const context = LoadContextFromFrame(); tail StringAdd_CheckNone(a, b); } extern builtin StringEqual(NoContext, String, String, intptr): Boolean; builtin WasmStringEqual(a: String, b: String): int32 { if (TaggedEqual(a, b)) return 1; if (a.length != b.length) return 0; if (StringEqual(kNoContext, a, b, a.length_intptr) == True) { return 1; } return 0; } builtin WasmStringIsUSVSequence(str: String): int32 { if (IsOneByteStringInstanceType(str.instanceType)) return 1; const length = runtime::WasmStringMeasureUtf8(LoadContextFromFrame(), str); if (Signed(ChangeNumberToUint32(length)) < 0) return 0; return 1; } builtin WasmStringAsWtf8(str: String): ByteArray { tail runtime::WasmStringAsWtf8(LoadContextFromFrame(), str); } macro IsWtf8CodepointStart(view: ByteArray, pos: uint32): bool { // We're already at the start of a codepoint if the current byte // doesn't start with 0b10xxxxxx. return (view.bytes[Convert<uintptr>(pos)] & 0xc0) != 0x80; } macro AlignWtf8PositionForward(view: ByteArray, pos: uint32): uint32 { const length = Unsigned(SmiToInt32(view.length)); if (pos >= length) return length; if (IsWtf8CodepointStart(view, pos)) return pos; // Otherwise `pos` is part of a multibyte codepoint, and is not the // leading byte. The next codepoint will start at pos + 1, pos + 2, // or pos + 3. if (pos + 1 == length) return length; if (IsWtf8CodepointStart(view, pos + 1)) return pos + 1; if (pos + 2 == length) return length; if (IsWtf8CodepointStart(view, pos + 2)) return pos + 2; return pos + 3; } macro AlignWtf8PositionBackward(view: ByteArray, pos: uint32): uint32 { // Return the highest offset that starts a codepoint which is not // greater than pos. Preconditions: pos in [0, view.length), view // contains well-formed WTF-8. if (IsWtf8CodepointStart(view, pos)) return pos; if (IsWtf8CodepointStart(view, pos - 1)) return pos - 1; if (IsWtf8CodepointStart(view, pos - 2)) return pos - 2; return pos - 3; } builtin WasmStringViewWtf8Advance(view: ByteArray, pos: uint32, bytes: uint32): uint32 { const clampedPos = AlignWtf8PositionForward(view, pos); if (bytes == 0) return clampedPos; const length = Unsigned(SmiToInt32(view.length)); if (bytes >= length - clampedPos) return length; return AlignWtf8PositionBackward(view, clampedPos + bytes); } struct NewPositionAndBytesWritten { newPosition: uintptr; bytesWritten: uintptr; } builtin WasmStringViewWtf8Encode( addr: uint32, pos: uint32, bytes: uint32, view: ByteArray, memory: Smi, utf8Variant: Smi): NewPositionAndBytesWritten { const start = WasmStringViewWtf8Advance(view, pos, 0); const end = WasmStringViewWtf8Advance(view, start, bytes); const instance = LoadInstanceFromFrame(); const context = LoadContextFromInstance(instance); // kMaxArgs in code-assembler.cc:CallRunTimeImpl is currently limited // to 6 arguments when calling a runtime function. Throw away the // memory argument for now; when we need multi-memory we can bump // kMaxArgs. dcheck(memory == SmiFromInt32(0)); // Always call out to run-time, to catch invalid addr. runtime::WasmStringViewWtf8Encode( context, instance, utf8Variant, view, WasmUint32ToNumber(addr), WasmUint32ToNumber(start), WasmUint32ToNumber(end)); return NewPositionAndBytesWritten{ newPosition: Convert<uintptr>(end), bytesWritten: Convert<uintptr>(end - start) }; } builtin WasmStringViewWtf8Slice(view: ByteArray, start: uint32, end: uint32): String { const start = WasmStringViewWtf8Advance(view, start, 0); const end = WasmStringViewWtf8Advance(view, end, 0); if (end <= start) return kEmptyString; tail runtime::WasmStringViewWtf8Slice( LoadContextFromFrame(), view, WasmUint32ToNumber(start), WasmUint32ToNumber(end)); } transitioning builtin WasmStringViewWtf16GetCodeUnit( string: String, offset: uint32): uint32 { try { if (Unsigned(string.length) <= offset) goto OffsetOutOfRange; const code: char16 = StringCharCodeAt(string, Convert<uintptr>(offset)); return Convert<uint32>(code); } label OffsetOutOfRange deferred { const error = MessageTemplate::kWasmTrapStringOffsetOutOfBounds; runtime::ThrowWasmError(LoadContextFromFrame(), SmiConstant(error)); } } builtin WasmStringViewWtf16Encode( offset: uint32, start: uint32, length: uint32, string: String, memory: Smi): uint32 { const instance = LoadInstanceFromFrame(); const clampedStart = start < Unsigned(string.length) ? start : Unsigned(string.length); const maxLength = Unsigned(string.length) - clampedStart; const clampedLength = length < maxLength ? length : maxLength; runtime::WasmStringEncodeWtf16( LoadContextFromInstance(instance), instance, memory, string, WasmUint32ToNumber(offset), SmiFromUint32(clampedStart), SmiFromUint32(clampedLength)); return clampedLength; } transitioning builtin WasmStringViewWtf16Slice( string: String, start: uint32, end: uint32): String { const length = Unsigned(string.length); if (start >= length) return kEmptyString; if (end <= start) return kEmptyString; // On a high level, the intended logic is: // (1) If start == 0 && end == string.length, return string. // (2) If clampedLength == 1, use a cached single-character string. // (3) If clampedLength < SlicedString::kMinLength, make a copy. // (4) If clampedLength < string.length / 2, make a copy. // (5) Else, create a slice. // The reason for having case (4) is that case (5) has the risk of keeping // huge parent strings alive unnecessarily, and Wasm currently doesn't have a // way to control that behavior, so we have to be careful. // The reason for having case (5) is that case (4) would lead to quadratic // overall behavior if code repeatedly chops off a few characters of a long // string, which we want to avoid. // The string::SubString implementation can handle cases (1), (2), (3), // and (5). The inline code here handles case (4), and doesn't mind if it // also catches some of case (3). const clampedEnd = end <= length ? end : length; const clampedLength = clampedEnd - start; if (clampedLength > 1 && clampedLength < length / 2) { try { // Calling into the runtime has overhead, but once we're there it's // faster, so it pays off for long strings. if (clampedLength > 32) goto Runtime; StringToSlice(string) otherwise OneByte, TwoByte; } label OneByte(slice: ConstSlice<char8>) { let subslice = Subslice( slice, Convert<intptr>(start), Convert<intptr>(clampedLength)) otherwise unreachable; return AllocateNonEmptySeqOneByteString( clampedLength, subslice.Iterator()); } label TwoByte(slice: ConstSlice<char16>) { let subslice = Subslice( slice, Convert<intptr>(start), Convert<intptr>(clampedLength)) otherwise unreachable; return StringFromTwoByteSlice(clampedLength, subslice); } label Runtime deferred { const context = LoadContextFromWasmOrJsFrame(); tail runtime::WasmSubstring( context, string, SmiFromUint32(start), SmiFromUint32(clampedLength)); } } return string::SubString( string, Convert<uintptr>(start), Convert<uintptr>(clampedEnd)); } builtin WasmStringAsIter(string: String): WasmStringViewIter { return new WasmStringViewIter{string: string, offset: 0, optional_padding: 0}; } macro IsLeadSurrogate(code: char16): bool { return (code & 0xfc00) == 0xd800; } macro IsTrailSurrogate(code: char16): bool { return (code & 0xfc00) == 0xdc00; } macro CombineSurrogatePair(lead: char16, trail: char16): int32 { const lead32 = Convert<uint32>(lead); const trail32 = Convert<uint32>(trail); // Surrogate pairs encode codepoints in the range // [0x010000, 0x10FFFF]. Each surrogate has 10 bits of information in // the low bits. We can combine them together with a shift-and-add, // then add a bias of 0x010000 - 0xD800<<10 - 0xDC00 = 0xFCA02400. const surrogateBias: uint32 = 0xFCA02400; return Signed((lead32 << 10) + trail32 + surrogateBias); } builtin WasmStringCodePointAt(string: String, offset: uint32): uint32 { try { if (Unsigned(string.length) <= offset) goto OffsetOutOfRange; const lead: char16 = StringCharCodeAt(string, Convert<uintptr>(offset)); if (!IsLeadSurrogate(lead)) return Convert<uint32>(lead); const trailOffset = offset + 1; if (Unsigned(string.length) <= trailOffset) return Convert<uint32>(lead); const trail: char16 = StringCharCodeAt(string, Convert<uintptr>(trailOffset)); if (!IsTrailSurrogate(trail)) return Convert<uint32>(lead); return Unsigned(CombineSurrogatePair(lead, trail)); } label OffsetOutOfRange deferred { const error = MessageTemplate::kWasmTrapStringOffsetOutOfBounds; runtime::ThrowWasmError(LoadContextFromFrame(), SmiConstant(error)); } } builtin WasmStringViewIterNext(view: WasmStringViewIter): int32 { const string = view.string; const offset = view.offset; if (offset >= Unsigned(string.length)) return -1; const code: char16 = StringCharCodeAt(string, Convert<uintptr>(offset)); try { if (IsLeadSurrogate(code) && offset + 1 < Unsigned(string.length)) { goto CheckForSurrogatePair; } } label CheckForSurrogatePair deferred { const code2: char16 = StringCharCodeAt(string, Convert<uintptr>(offset + 1)); if (IsTrailSurrogate(code2)) { view.offset = offset + 2; return CombineSurrogatePair(code, code2); } } view.offset = offset + 1; return Signed(Convert<uint32>(code)); } builtin WasmStringViewIterAdvance( view: WasmStringViewIter, codepoints: uint32): uint32 { const string = view.string; let offset = view.offset; let advanced: uint32 = 0; while (advanced < codepoints) { if (offset == Unsigned(string.length)) break; advanced = advanced + 1; if (offset + 1 < Unsigned(string.length) && IsLeadSurrogate(StringCharCodeAt(string, Convert<uintptr>(offset))) && IsTrailSurrogate( StringCharCodeAt(string, Convert<uintptr>(offset + 1)))) { offset = offset + 2; } else { offset = offset + 1; } } view.offset = offset; return advanced; } builtin WasmStringViewIterRewind(view: WasmStringViewIter, codepoints: uint32): uint32 { const string = view.string; let offset = view.offset; let rewound: uint32 = 0; if (string.length == 0) return 0; while (rewound < codepoints) { if (offset == 0) break; rewound = rewound + 1; if (offset >= 2 && IsTrailSurrogate( StringCharCodeAt(string, Convert<uintptr>(offset - 1))) && IsLeadSurrogate( StringCharCodeAt(string, Convert<uintptr>(offset - 2)))) { offset = offset - 2; } else { offset = offset - 1; } } view.offset = offset; return rewound; } builtin WasmStringViewIterSlice(view: WasmStringViewIter, codepoints: uint32): String { const string = view.string; const start = view.offset; let end = view.offset; let advanced: uint32 = 0; while (advanced < codepoints) { if (end == Unsigned(string.length)) break; advanced = advanced + 1; if (end + 1 < Unsigned(string.length) && IsLeadSurrogate(StringCharCodeAt(string, Convert<uintptr>(end))) && IsTrailSurrogate(StringCharCodeAt(string, Convert<uintptr>(end + 1)))) { end = end + 2; } else { end = end + 1; } } return (start == end) ? kEmptyString : string::SubString(string, Convert<uintptr>(start), Convert<uintptr>(end)); } builtin WasmIntToString(x: int32, radix: int32): String { if (radix == 10) { const smi = SmiFromInt32(x); const untagged = SmiToInt32(smi); if (x == untagged) { // Queries and populates the NumberToStringCache, but needs tagged // inputs, so only call this for Smis. return NumberToString(smi); } return number::IntToDecimalString(x); } // Pretend that Number.prototype.toString was called. if (radix < 2 || radix > 36) { runtime::ThrowRangeError( LoadContextFromInstance(LoadInstanceFromFrame()), SmiConstant(MessageTemplate::kToRadixFormatRange)); } return number::IntToString(x, Unsigned(radix)); } builtin WasmStringToDouble(s: String): float64 { const hash: NameHash = s.raw_hash_field; if (IsIntegerIndex(hash) && hash.array_index_length < kMaxCachedArrayIndexLength) { const arrayIndex: int32 = Signed(hash.array_index_value); return Convert<float64>(arrayIndex); } return StringToFloat64(Flatten(s)); } builtin WasmStringFromCodePoint(codePoint: uint32): String { tail runtime::WasmStringFromCodePoint( LoadContextFromFrame(), WasmUint32ToNumber(codePoint)); } builtin WasmStringHash(string: String): int32 { const result = runtime::WasmStringHash(kNoContext, string); return SmiToInt32(result); } builtin WasmExternInternalize(externObject: JSAny): JSAny { const instance = LoadInstanceFromFrame(); const context = LoadContextFromInstance(instance); tail runtime::WasmJSToWasmObject( context, externObject, SmiConstant(kAnyType)); } } // namespace wasm