| // 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' |
| #include 'src/builtins/builtins-call-gen.h' |
| |
| namespace runtime { |
| extern runtime WasmMemoryGrow(Context, WasmTrustedInstanceData, Smi, Smi): Smi; |
| extern runtime WasmRefFunc(Context, WasmTrustedInstanceData, Smi): JSAny; |
| extern runtime WasmInternalFunctionCreateExternal( |
| Context, WasmInternalFunction): JSFunction; |
| extern runtime WasmTableInit( |
| Context, WasmTrustedInstanceData, Object, Object, Smi, Smi, Smi): JSAny; |
| extern runtime WasmTableCopy( |
| Context, WasmTrustedInstanceData, Object, Object, Smi, Smi, Smi): JSAny; |
| extern runtime WasmTableFill( |
| Context, WasmTrustedInstanceData, Smi, Smi, Object, Smi): JSAny; |
| extern runtime WasmTableGrow( |
| Context, WasmTrustedInstanceData, Smi, Object, Smi): Smi; |
| extern runtime WasmFunctionTableGet(Context, WasmTrustedInstanceData, Smi, Smi): |
| JSAny; |
| extern runtime WasmFunctionTableSet( |
| Context, WasmTrustedInstanceData, Smi, Smi, Object): JSAny; |
| extern runtime ThrowRangeError(Context, Smi): never; |
| extern runtime ThrowWasmError(Context, Smi): never; |
| extern runtime WasmThrowRangeError(Context, Smi): never; |
| extern runtime TrapHandlerThrowWasmError(Context): never; |
| extern runtime WasmThrowTypeError(Context, Smi, JSAny): never; |
| extern runtime WasmThrowDataViewTypeError(Context, Smi, JSAny): never; |
| extern runtime WasmThrowDataViewDetachedError(Context, Smi): never; |
| extern runtime WasmThrow(Context, Object, FixedArray): JSAny; |
| extern runtime WasmReThrow(Context, Object): JSAny; |
| extern runtime WasmTriggerTierUp(Context, WasmTrustedInstanceData): JSAny; |
| extern runtime WasmStackGuard(Context, Smi): 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 WasmI32AtomicWait( |
| Context, WasmTrustedInstanceData, Smi, Number, Number, BigInt): Smi; |
| extern runtime WasmI64AtomicWait( |
| Context, WasmTrustedInstanceData, Smi, Number, BigInt, BigInt): Smi; |
| extern runtime WasmArrayCopy(Context, WasmArray, Smi, WasmArray, Smi, Smi): |
| JSAny; |
| extern runtime WasmArrayNewSegment( |
| Context, WasmTrustedInstanceData, Smi, Smi, Smi, Map): Object; |
| extern runtime WasmStringNewSegmentWtf8( |
| Context, WasmTrustedInstanceData, Smi, Smi, Smi, Smi): String|WasmNull; |
| extern runtime WasmArrayInitSegment( |
| Context, WasmTrustedInstanceData, Smi, WasmArray, Smi, Smi, Smi): JSAny; |
| extern runtime WasmStringNewWtf8( |
| Context, WasmTrustedInstanceData, Smi, Smi, Number, Number): String |
| |WasmNull; |
| extern runtime WasmStringNewWtf8Array(Context, Smi, WasmArray, Smi, Smi): String |
| |WasmNull; |
| extern runtime WasmStringNewWtf16( |
| Context, WasmTrustedInstanceData, Smi, Number, Number): String; |
| extern runtime WasmStringNewWtf16Array(Context, WasmArray, Smi, Smi): String; |
| extern runtime WasmStringConst(Context, WasmTrustedInstanceData, Smi): String; |
| extern runtime WasmStringMeasureUtf8(Context, String): Number; |
| extern runtime WasmStringMeasureWtf8(Context, String): Number; |
| extern runtime WasmStringEncodeWtf8( |
| Context, WasmTrustedInstanceData, Smi, Smi, String, Number): Number; |
| extern runtime WasmStringEncodeWtf8Array( |
| Context, Smi, String, WasmArray, Number): Number; |
| extern runtime WasmStringToUtf8Array(Context, String): WasmArray; |
| extern runtime WasmStringEncodeWtf16( |
| Context, WasmTrustedInstanceData, Smi, String, Number, Smi, Smi): JSAny; |
| extern runtime WasmStringAsWtf8(Context, String): ByteArray; |
| extern runtime WasmStringViewWtf8Encode( |
| Context, WasmTrustedInstanceData, Smi, ByteArray, Number, Number, Number, |
| Smi): 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; |
| extern runtime WasmLiftoffDeoptFinish(NoContext, WasmTrustedInstanceData): |
| Undefined; |
| |
| extern runtime PropagateException(NoContext): JSAny; |
| } |
| |
| extern operator '.wasm_exported_function_data' macro |
| LoadSharedFunctionInfoWasmExportedFunctionData(SharedFunctionInfo): |
| WasmExportedFunctionData; |
| extern operator '.wasm_js_function_data' macro |
| LoadSharedFunctionInfoWasmJSFunctionData(SharedFunctionInfo): |
| WasmJSFunctionData; |
| |
| namespace unsafe { |
| extern macro Allocate(intptr): HeapObject; |
| extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject; |
| } |
| |
| macro NumberToInt32(input: Number): int32 { |
| return Convert<int32>(input); |
| } |
| macro NumberToUint32(input: Number): uint32 { |
| return Unsigned(Convert<int32>(input)); |
| } |
| |
| namespace wasm { |
| const kAnyType: constexpr int31 |
| generates 'wasm::kWasmAnyRef.raw_bit_field()'; |
| const kMaxPolymorphism: |
| constexpr int31 generates 'wasm::kMaxPolymorphism'; |
| const kFixedFrameSizeAboveFp: constexpr int32 |
| generates 'CommonFrameConstants::kFixedFrameSizeAboveFp'; |
| |
| extern macro WasmCrossInstanceCallSymbolConstant(): Symbol; |
| |
| extern macro WasmBuiltinsAssembler::LoadTrustedDataFromInstance( |
| WasmInstanceObject): WasmTrustedInstanceData; |
| |
| extern macro WasmBuiltinsAssembler::LoadInstanceDataFromFrame(): |
| WasmTrustedInstanceData; |
| |
| // WasmTrustedInstanceData has a field layout that Torque can't handle yet. |
| // TODO(bbudge) Eliminate these functions when Torque is ready. |
| extern macro WasmBuiltinsAssembler::LoadContextFromInstanceData( |
| WasmTrustedInstanceData): NativeContext; |
| extern macro WasmBuiltinsAssembler::LoadSharedPartFromInstanceData( |
| WasmTrustedInstanceData): WasmTrustedInstanceData; |
| extern macro WasmBuiltinsAssembler::LoadTablesFromInstanceData( |
| WasmTrustedInstanceData): FixedArray; |
| extern macro WasmBuiltinsAssembler::LoadFuncRefsFromInstanceData( |
| WasmTrustedInstanceData): FixedArray; |
| extern macro WasmBuiltinsAssembler::LoadManagedObjectMapsFromInstanceData( |
| WasmTrustedInstanceData): FixedArray; |
| extern macro WasmBuiltinsAssembler::LoadContextFromWasmOrJsFrame(): |
| NativeContext; |
| |
| extern macro WasmBuiltinsAssembler::StringToFloat64(String): float64; |
| |
| extern macro SwitchToTheCentralStackIfNeeded(): RawPtr; |
| extern macro SwitchFromTheCentralStack(RawPtr): void; |
| |
| macro LoadContextFromFrame(): NativeContext { |
| return LoadContextFromInstanceData(LoadInstanceDataFromFrame()); |
| } |
| |
| macro LoadMaybeSharedInstanceDataFromFrame(extractSharedData: bool): |
| WasmTrustedInstanceData { |
| const trustedData = LoadInstanceDataFromFrame(); |
| if (extractSharedData) { |
| return LoadSharedPartFromInstanceData(trustedData); |
| } else { |
| return trustedData; |
| } |
| } |
| |
| macro LoadMaybeSharedInstanceDataFromFrame(extractSharedData: Smi): |
| WasmTrustedInstanceData { |
| const trustedData = LoadInstanceDataFromFrame(); |
| if (extractSharedData == SmiConstant(1)) { |
| return LoadSharedPartFromInstanceData(trustedData); |
| } else { |
| return trustedData; |
| } |
| } |
| |
| builtin WasmInt32ToHeapNumber(val: int32): HeapNumber { |
| return AllocateHeapNumberWithValue(Convert<float64>(val)); |
| } |
| |
| builtin WasmFuncRefToJS( |
| implicit context: Context)(val: WasmFuncRef|WasmNull): JSFunction|Null { |
| typeswitch (val) { |
| case (WasmNull): { |
| return Null; |
| } |
| case (func: WasmFuncRef): { |
| const internal: WasmInternalFunction = func.internal; |
| const maybeExternal: Object = internal.external; |
| if (maybeExternal != Undefined) { |
| return %RawDownCast<JSFunction>(maybeExternal); |
| } |
| tail runtime::WasmInternalFunctionCreateExternal(context, internal); |
| } |
| } |
| } |
| |
| 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 trustedData: WasmTrustedInstanceData = LoadInstanceDataFromFrame(); |
| const context: NativeContext = LoadContextFromInstanceData(trustedData); |
| const result: Smi = runtime::WasmMemoryGrow( |
| context, trustedData, SmiFromInt32(memIndex), SmiFromInt32(numPages)); |
| return SmiToInt32(result); |
| } |
| |
| builtin WasmTableInit( |
| dstRaw: intptr, srcRaw: uint32, sizeRaw: uint32, tableIndex: Smi, |
| segmentIndex: Smi, extractSharedData: Smi): JSAny { |
| try { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| 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( |
| LoadContextFromInstanceData(trustedData), trustedData, tableIndex, |
| segmentIndex, dst, src, size); |
| } label TableOutOfBounds deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| builtin WasmTableCopy( |
| dstRaw: intptr, srcRaw: intptr, sizeRaw: intptr, dstTable: Smi, |
| srcTable: Smi, extractSharedData: Smi): JSAny { |
| try { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| 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( |
| LoadContextFromInstanceData(trustedData), trustedData, dstTable, |
| srcTable, dst, src, size); |
| } label TableOutOfBounds deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| builtin WasmTableFill( |
| startRaw: intptr, countRaw: intptr, extractSharedData: bool, table: Smi, |
| value: Object): JSAny { |
| try { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| const start: Smi = |
| Convert<PositiveSmi>(startRaw) otherwise TableOutOfBounds; |
| const count: Smi = |
| Convert<PositiveSmi>(countRaw) otherwise TableOutOfBounds; |
| tail runtime::WasmTableFill( |
| LoadContextFromInstanceData(trustedData), trustedData, table, start, |
| value, count); |
| } label TableOutOfBounds deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| builtin WasmTableGrow( |
| table: Smi, deltaRaw: intptr, extractSharedData: bool, value: Object): Smi { |
| try { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| const delta: Smi = |
| Convert<PositiveSmi>(deltaRaw) otherwise TableOutOfBounds; |
| tail runtime::WasmTableGrow( |
| LoadContextFromInstanceData(trustedData), trustedData, table, value, |
| delta); |
| } label TableOutOfBounds deferred { |
| return -1; |
| } |
| } |
| |
| builtin WasmTableGet(tableIndex: intptr, index: intptr): Object { |
| const trustedData: WasmTrustedInstanceData = LoadInstanceDataFromFrame(); |
| try { |
| dcheck(IsValidPositiveSmi(tableIndex)); |
| |
| const tables: FixedArray = LoadTablesFromInstanceData(trustedData); |
| const table: WasmTableObject = %RawDownCast<WasmTableObject>( |
| LoadFixedArrayElement(tables, tableIndex)); |
| const entriesCount: uintptr = |
| Unsigned(ChangeInt32ToIntPtr(SmiToInt32(table.current_length))); |
| if (Unsigned(index) >= entriesCount) goto IndexOutOfRange; |
| |
| const entries: FixedArray = table.entries; |
| const entry: Object = LoadFixedArrayElement(entries, index); |
| return entry; |
| } label IndexOutOfRange deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| builtin WasmTableSet( |
| tableIndex: intptr, extractSharedData: bool, index: intptr, |
| value: Object): Object { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| try { |
| dcheck(IsValidPositiveSmi(tableIndex)); |
| |
| const tables: FixedArray = LoadTablesFromInstanceData(trustedData); |
| const table: WasmTableObject = %RawDownCast<WasmTableObject>( |
| LoadFixedArrayElement(tables, tableIndex)); |
| |
| const entriesCount: uintptr = |
| Unsigned(ChangeInt32ToIntPtr(SmiToInt32(table.current_length))); |
| if (Unsigned(index) >= entriesCount) goto IndexOutOfRange; |
| |
| const entries: FixedArray = table.entries; |
| StoreFixedArrayElement(entries, index, value); |
| return Undefined; |
| } label IndexOutOfRange deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| // Returns WasmFuncRef or WasmNull, or throws an exception. |
| builtin WasmTableGetFuncRef(tableIndex: intptr, index: intptr): Object { |
| const trustedData: WasmTrustedInstanceData = LoadInstanceDataFromFrame(); |
| try { |
| dcheck(IsValidPositiveSmi(tableIndex)); |
| |
| const tables: FixedArray = LoadTablesFromInstanceData(trustedData); |
| const table: WasmTableObject = %RawDownCast<WasmTableObject>( |
| LoadFixedArrayElement(tables, tableIndex)); |
| const entriesCount: uintptr = |
| Unsigned(ChangeInt32ToIntPtr(SmiToInt32(table.current_length))); |
| if (Unsigned(index) >= entriesCount) goto IndexOutOfRange; |
| |
| const entries: FixedArray = table.entries; |
| const entry: HeapObject = |
| UnsafeCast<HeapObject>(LoadFixedArrayElement(entries, index)); |
| |
| dcheck(Is<WasmFuncRef>(entry) || Is<WasmNull>(entry) || Is<Tuple2>(entry)); |
| if (IsTuple2Map(entry.map)) goto CallRuntime; |
| if (Is<WasmNull>(entry)) return entry; |
| dcheck(Is<WasmFuncRef>(entry)); |
| return entry; |
| } label CallRuntime deferred { |
| tail runtime::WasmFunctionTableGet( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromIntPtr(tableIndex), SmiFromIntPtr(index)); |
| } label IndexOutOfRange deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| // Stub to wrap around the slow path (runtime call) of table.get for funcref |
| // tables for which the reference is not yet initialized. |
| builtin WasmFunctionTableGet( |
| tableIndex: intptr, index: intptr, extractSharedData: bool): Object { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| dcheck(IsValidPositiveSmi(tableIndex)); |
| dcheck(IsValidPositiveSmi(index)); |
| tail runtime::WasmFunctionTableGet( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromIntPtr(tableIndex), SmiFromIntPtr(index)); |
| } |
| |
| builtin WasmTableSetFuncRef( |
| tableIndex: intptr, extractSharedData: bool, index: intptr, |
| value: WasmFuncRef): Object { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| dcheck(Is<WasmFuncRef>(value) || Is<WasmNull>(value)); |
| try { |
| dcheck(IsValidPositiveSmi(tableIndex)); |
| |
| const tables: FixedArray = LoadTablesFromInstanceData(trustedData); |
| const table: WasmTableObject = %RawDownCast<WasmTableObject>( |
| LoadFixedArrayElement(tables, tableIndex)); |
| |
| const entriesCount: uintptr = |
| Unsigned(ChangeInt32ToIntPtr(SmiToInt32(table.current_length))); |
| if (Unsigned(index) >= entriesCount) goto IndexOutOfRange; |
| |
| tail runtime::WasmFunctionTableSet( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromIntPtr(tableIndex), SmiFromIntPtr(index), value); |
| } label IndexOutOfRange deferred { |
| tail ThrowWasmTrapTableOutOfBounds(); |
| } |
| } |
| |
| builtin WasmRefFunc(index: uint32, extractSharedData: bool): Object { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| try { |
| const funcRefs: FixedArray = LoadFuncRefsFromInstanceData(trustedData); |
| const funcref: Object = funcRefs.objects[index]; |
| // {funcref} is either a WasmFuncRef or Smi::zero(). A Smi check is the |
| // fastest way to distinguish these two cases. |
| if (TaggedIsSmi(funcref)) goto CallRuntime; |
| dcheck(Is<WasmFuncRef>(funcref)); |
| return funcref; |
| } label CallRuntime deferred { |
| tail runtime::WasmRefFunc( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| 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 WasmLiftoffDeoptFinish(): Undefined { |
| const trustedData = LoadInstanceDataFromFrame(); |
| tail runtime::WasmLiftoffDeoptFinish(kNoContext, trustedData); |
| } |
| |
| builtin WasmThrow(tag: Object, values: FixedArray): JSAny { |
| tail runtime::WasmThrow(LoadContextFromFrame(), tag, values); |
| } |
| |
| builtin WasmRethrow(exception: Object): JSAny { |
| dcheck(exception != kWasmNull); |
| tail runtime::WasmReThrow(LoadContextFromFrame(), exception); |
| } |
| |
| builtin WasmThrowRef(exception: Object): JSAny { |
| if (exception == kWasmNull) 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 trustedData = LoadInstanceDataFromFrame(); |
| tail runtime::WasmTriggerTierUp(LoadContextFromFrame(), trustedData); |
| } |
| |
| extern builtin WasmHandleStackOverflow(RawPtr, uint32): JSAny; |
| |
| // {paramSlotsSize} is the size of the incoming stack parameters of the |
| // currently-executing function, which have to be copied along with its |
| // stack frame if the stack needs to be grown. |
| builtin WasmGrowableStackGuard(paramSlotsSize: intptr): JSAny { |
| tail WasmHandleStackOverflow( |
| LoadParentFramePointer() + paramSlotsSize + kFixedFrameSizeAboveFp, 0); |
| } |
| |
| builtin WasmStackGuard(): JSAny { |
| tail runtime::WasmStackGuard(LoadContextFromFrame(), SmiConstant(0)); |
| } |
| |
| 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, |
| extractSharedData: Smi, rtt: Map): Object { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| try { |
| const smiOffset = Convert<PositiveSmi>(offset) otherwise SegmentOutOfBounds; |
| const smiLength = Convert<PositiveSmi>(length) otherwise ArrayTooLarge; |
| tail runtime::WasmArrayNewSegment( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| 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, extractSharedData: Smi, |
| arrayRaw: HeapObject): JSAny { |
| const trustedData: WasmTrustedInstanceData = |
| LoadMaybeSharedInstanceDataFromFrame(extractSharedData); |
| 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( |
| LoadContextFromInstanceData(trustedData), trustedData, 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 WasmArrayCopy( |
| dstIndex: uint32, srcIndex: uint32, length: uint32, dstObject: Object, |
| srcObject: Object): JSAny { |
| // Check destination array. |
| if (dstObject == kWasmNull) tail ThrowWasmTrapNullDereference(); |
| const dstArray = UnsafeCast<WasmArray>(dstObject); |
| if (dstIndex + length > dstArray.length || dstIndex + length < dstIndex) { |
| tail ThrowWasmTrapArrayOutOfBounds(); |
| } |
| // Check source array. |
| if (srcObject == kWasmNull) tail ThrowWasmTrapNullDereference(); |
| const srcArray = UnsafeCast<WasmArray>(srcObject); |
| if (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 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); |
| } |
| |
| // Suitable for indexes/offsets into memory: while values >2^53 will get |
| // rounded off, they're all OOB anyway, and an OOB check after a conversion |
| // back to uintptr can still detect that. (Alternatively we could trap |
| // right here.) |
| macro UintPtrToNumberRounding(value: uintptr): Number { |
| if (value <= kSmiMaxValue) return Convert<Smi>(Convert<intptr>(value)); |
| return AllocateHeapNumberWithValue(ChangeUintPtrToFloat64(value)); |
| } |
| |
| extern builtin I64ToBigInt(intptr): BigInt; |
| extern builtin I32PairToBigInt(/*low*/ intptr, /*high*/ intptr): BigInt; |
| |
| builtin WasmI32AtomicWait( |
| memIndex: int32, offset: uintptr, expectedValue: int32, |
| timeout: BigInt): uint32 { |
| const trustedData: WasmTrustedInstanceData = LoadInstanceDataFromFrame(); |
| const result: Smi = runtime::WasmI32AtomicWait( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromInt32(memIndex), UintPtr53ToNumber(offset), |
| ChangeInt32ToTagged(expectedValue), timeout); |
| return Unsigned(SmiToInt32(result)); |
| } |
| |
| builtin WasmI64AtomicWait( |
| memIndex: int32, offset: uintptr, expectedValue: BigInt, |
| timeout: BigInt): uint32 { |
| const trustedData: WasmTrustedInstanceData = LoadInstanceDataFromFrame(); |
| const result: Smi = runtime::WasmI64AtomicWait( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromInt32(memIndex), UintPtr53ToNumber(offset), expectedValue, |
| timeout); |
| return Unsigned(SmiToInt32(result)); |
| } |
| |
| // Type feedback collection support for `call_ref` and `call_indirect`. |
| |
| // See {TransitiveTypeFeedbackProcessor::ProcessFunction} for the vector layout. |
| // |
| // TODO(rstz): The counter might overflow if it exceeds the range of a Smi. |
| // This can lead to incorrect inlining decisions. |
| macro UpdateCallRefOrIndirectIC( |
| vector: FixedArray, index: intptr, target: Object): void { |
| const firstSlot = vector.objects[index]; |
| const secondSlot = vector.objects[index + 1]; |
| if (TaggedEqual(firstSlot, target)) { |
| // Monomorphic hit. Check for this case first to maximize its performance. |
| const count = UnsafeCast<Smi>(secondSlot) + SmiConstant(1); |
| vector.objects[index + 1] = count; |
| return; |
| } |
| // Check for polymorphic hit; its performance is second-most-important. |
| if (Is<FixedArray>(firstSlot)) { |
| const entries = UnsafeCast<FixedArray>(firstSlot); |
| for (let i: intptr = 0; i < entries.length_intptr; i += 2) { |
| if (TaggedEqual(entries.objects[i], target)) { |
| // Polymorphic hit. |
| const count = UnsafeCast<Smi>(entries.objects[i + 1]) + SmiConstant(1); |
| entries.objects[i + 1] = count; |
| return; |
| } |
| } |
| } |
| // All other cases are some sort of miss. |
| if (TaggedEqual(secondSlot, SmiConstant(0))) { |
| // Was uninitialized. |
| // Note that we inspect the second slot (the call count, see feedback vector |
| // layout in {TransitiveTypeFeedbackProcessor::ProcessFunction}) for |
| // determining whether the entry is uninitialized, not the first slot (the |
| // call target), since the target may genuinely be zero for `call_indirect` |
| // if the `WasmDispatchTable` happens to start at an address with many zero |
| // least significant bits. |
| dcheck(TaggedEqual(firstSlot, SmiConstant(0))); |
| vector.objects[index] = target; |
| vector.objects[index + 1] = SmiConstant(1); |
| } else if (Is<FixedArray>(firstSlot)) { |
| // Polymorphic miss. |
| const entries = UnsafeCast<FixedArray>(firstSlot); |
| const kMaxSlots = kMaxPolymorphism * 2; // 2 slots per entry. |
| if (entries.length == SmiConstant(kMaxSlots)) { |
| // Polymorphic to megamorphic transition. |
| vector.objects[index] = ic::kMegamorphicSymbol; |
| // The second slot/counter has already been set to undefined below. |
| } else { |
| // Polymorphic(N) to polymorphic(N+1) transition. |
| 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] = target; |
| newEntries.objects[newIndex + 1] = SmiConstant(1); |
| vector.objects[index] = newEntries; |
| } |
| } else if (firstSlot == ic::kMegamorphicSymbol) { |
| // The "ic::IsMegamorphic(firstSlot)" case doesn't need to do anything. |
| } else { |
| // Monomorphic miss. |
| dcheck( |
| Is<WasmFuncRef>(firstSlot) || Is<Smi>(firstSlot) || |
| firstSlot == WasmCrossInstanceCallSymbolConstant()); |
| const newEntries = UnsafeCast<FixedArray>(AllocateFixedArray( |
| ElementsKind::PACKED_ELEMENTS, 4, AllocationFlag::kNone)); |
| newEntries.objects[0] = firstSlot; |
| newEntries.objects[1] = secondSlot; |
| newEntries.objects[2] = target; |
| 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; |
| } |
| } |
| |
| // This is the return type of "CallRefIC" and "CallIndirectIC". The type is not |
| // used anywhere else; Liftoff uses the two returned values directly. |
| struct TargetAndImplicitArg { |
| target: WasmCodePointer; |
| implicit_arg: WasmTrustedInstanceData|WasmImportData; |
| } |
| |
| builtin CallRefIC( |
| vector: FixedArray, vectorIndex: int32, |
| funcref: WasmFuncRef): TargetAndImplicitArg { |
| dcheck(Is<WasmFuncRef>(funcref)); |
| UpdateCallRefOrIndirectIC(vector, Convert<intptr>(vectorIndex), funcref); |
| |
| const internal = funcref.internal; |
| return TargetAndImplicitArg{ |
| target: internal.raw_call_target, |
| implicit_arg: internal.implicit_arg |
| }; |
| } |
| |
| builtin CallIndirectIC( |
| vector: FixedArray, vectorIndex: int32, target: WasmCodePointer, |
| implicitArg: WasmTrustedInstanceData|WasmImportData): TargetAndImplicitArg { |
| // If this is a cross-instance call, don't track the precise target (we can |
| // only correctly inline same-instance calls anyway). Instead mark it as such, |
| // so that we can prevent a deopt loop, see `TransitiveTypeFeedbackProcessor`. |
| const instance = LoadInstanceDataFromFrame(); |
| const truncatedTargetOrCrossInstance = TaggedEqual(implicitArg, instance) ? |
| SmiTag(Signed(Convert<uintptr>(target) & kSmiMaxValue)) : |
| WasmCrossInstanceCallSymbolConstant(); |
| UpdateCallRefOrIndirectIC( |
| vector, Convert<intptr>(vectorIndex), truncatedTargetOrCrossInstance); |
| |
| return TargetAndImplicitArg{target: target, implicit_arg: implicitArg}; |
| } |
| |
| 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 WasmTrapHandlerThrowTrap(): JSAny { |
| tail runtime::TrapHandlerThrowWasmError(LoadContextFromWasmOrJsFrame()); |
| } |
| |
| builtin WasmPropagateException(): JSAny { |
| tail runtime::PropagateException(kNoContext); |
| } |
| |
| 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 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; |
| } |
| |
| builtin WasmStringNewWtf8( |
| offset: uintptr, size: uint32, memory: uint32, utf8Variant: Smi): String |
| |WasmNull { |
| const trustedData = LoadInstanceDataFromFrame(); |
| tail runtime::WasmStringNewWtf8( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromUint32(memory), utf8Variant, UintPtrToNumberRounding(offset), |
| WasmUint32ToNumber(size)); |
| } |
| builtin WasmStringNewWtf8Array( |
| start: uint32, end: uint32, array: WasmArray, utf8Variant: Smi): String |
| |WasmNull { |
| // This can be called from Wasm and from "JS String 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: uintptr, size: uint32): |
| String { |
| const trustedData = LoadInstanceDataFromFrame(); |
| tail runtime::WasmStringNewWtf16( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromUint32(memory), UintPtrToNumberRounding(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 "JS String 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 or WasmNull 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, variant: Smi): JSAny|WasmNull { |
| const trustedData = LoadInstanceDataFromFrame(); |
| 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( |
| LoadContextFromInstanceData(trustedData), trustedData, segmentIndex, |
| smiOffset, smiLength, variant); |
| } 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 trustedData = LoadInstanceDataFromFrame(); |
| tail runtime::WasmStringConst( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromUint32(index)); |
| } |
| builtin WasmStringMeasureUtf8(string: String): int32 { |
| const result = runtime::WasmStringMeasureUtf8(LoadContextFromFrame(), string); |
| return NumberToInt32(result); |
| } |
| builtin WasmStringMeasureWtf8(string: String): int32 { |
| const result = runtime::WasmStringMeasureWtf8(LoadContextFromFrame(), string); |
| return NumberToInt32(result); |
| } |
| builtin WasmStringEncodeWtf8( |
| offset: uintptr, memory: uint32, utf8Variant: uint32, |
| string: String): uint32 { |
| const trustedData = LoadInstanceDataFromFrame(); |
| const result = runtime::WasmStringEncodeWtf8( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromUint32(memory), SmiFromUint32(utf8Variant), string, |
| UintPtrToNumberRounding(offset)); |
| return NumberToUint32(result); |
| } |
| builtin WasmStringEncodeWtf8Array( |
| string: String, array: WasmArray, start: uint32, utf8Variant: Smi): uint32 { |
| const trustedData = LoadInstanceDataFromFrame(); |
| const result = runtime::WasmStringEncodeWtf8Array( |
| LoadContextFromInstanceData(trustedData), utf8Variant, string, array, |
| WasmUint32ToNumber(start)); |
| return NumberToUint32(result); |
| } |
| builtin WasmStringToUtf8Array(string: String): WasmArray { |
| return runtime::WasmStringToUtf8Array(LoadContextFromFrame(), string); |
| } |
| builtin WasmStringEncodeWtf16(string: String, offset: uintptr, memory: uint32): |
| uint32 { |
| const trustedData = LoadInstanceDataFromFrame(); |
| runtime::WasmStringEncodeWtf16( |
| LoadContextFromInstanceData(trustedData), trustedData, |
| SmiFromUint32(memory), string, UintPtrToNumberRounding(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 ThrowDataViewTypeError(value: JSAny): JSAny { |
| const context = LoadContextFromFrame(); |
| const error = MessageTemplate::kIncompatibleMethodReceiver; |
| runtime::WasmThrowDataViewTypeError(context, SmiConstant(error), value); |
| } |
| |
| builtin ThrowDataViewDetachedError(): JSAny { |
| const context = LoadContextFromFrame(); |
| const error = MessageTemplate::kDetachedOperation; |
| runtime::WasmThrowDataViewDetachedError(context, SmiConstant(error)); |
| } |
| |
| builtin ThrowDataViewOutOfBounds(): JSAny { |
| const context = LoadContextFromFrame(); |
| const error = MessageTemplate::kInvalidDataViewAccessorOffset; |
| runtime::WasmThrowRangeError(context, SmiConstant(error)); |
| } |
| |
| 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 (IsOneByteStringMap(str.map)) return 1; |
| const length = runtime::WasmStringMeasureUtf8(LoadContextFromFrame(), str); |
| if (NumberToInt32(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.values[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: uint32; |
| bytesWritten: uint32; |
| } |
| builtin WasmStringViewWtf8Encode( |
| addr: uintptr, pos: uint32, bytes: uint32, view: ByteArray, memory: Smi, |
| utf8Variant: Smi): NewPositionAndBytesWritten { |
| const start = WasmStringViewWtf8Advance(view, pos, 0); |
| const end = WasmStringViewWtf8Advance(view, start, bytes); |
| const trustedData = LoadInstanceDataFromFrame(); |
| const context = LoadContextFromInstanceData(trustedData); |
| |
| // Always call out to run-time, to catch invalid addr. |
| runtime::WasmStringViewWtf8Encode( |
| context, trustedData, utf8Variant, view, UintPtrToNumberRounding(addr), |
| WasmUint32ToNumber(start), WasmUint32ToNumber(end), memory); |
| |
| return NewPositionAndBytesWritten{ |
| newPosition: end, |
| bytesWritten: 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: uintptr, start: uint32, length: uint32, string: String, |
| memory: Smi): uint32 { |
| const trustedData = LoadInstanceDataFromFrame(); |
| const clampedStart = |
| start < Unsigned(string.length) ? start : Unsigned(string.length); |
| const maxLength = Unsigned(string.length) - clampedStart; |
| const clampedLength = length < maxLength ? length : maxLength; |
| runtime::WasmStringEncodeWtf16( |
| LoadContextFromInstanceData(trustedData), trustedData, memory, string, |
| UintPtrToNumberRounding(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( |
| LoadContextFromInstanceData(LoadInstanceDataFromFrame()), |
| 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 WasmAnyConvertExtern(externObject: JSAny): JSAny { |
| const trustedData = LoadInstanceDataFromFrame(); |
| const context = LoadContextFromInstanceData(trustedData); |
| |
| tail runtime::WasmJSToWasmObject( |
| context, externObject, SmiConstant(kAnyType)); |
| } |
| |
| extern macro CallOrConstructBuiltinsAssembler::GetCompatibleReceiver( |
| JSReceiver, HeapObject, Context): JSReceiver; |
| |
| builtin WasmFastApiCallTypeCheckAndUpdateIC( |
| implicit context: Context)(data: WasmFastApiCallData, |
| receiver: JSAny): Smi { |
| try { |
| const rec = Cast<JSReceiver>(receiver) otherwise goto IllegalCast; |
| ModifyThreadInWasmFlag(0); |
| // We don't care about the actual compatible receiver; we just rely |
| // on this helper throwing an exception when there isn't one. |
| GetCompatibleReceiver(rec, data.signature, context); |
| ModifyThreadInWasmFlag(1); |
| data.cached_map = StrongToWeak(rec.map); |
| return 1; |
| } label IllegalCast { |
| const error = MessageTemplate::kIllegalInvocation; |
| runtime::WasmThrowTypeError(context, SmiConstant(error), Convert<Smi>(0)); |
| } |
| } |
| } // namespace wasm |