%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/js-to-wasm.tq |
// Copyright 2023 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/wasm/wasm-linkage.h' namespace runtime { extern runtime WasmGenericJSToWasmObject( Context, WasmInstanceObject|Undefined, JSAny, Smi): JSAny; extern runtime WasmGenericWasmToJSObject(Context, Object): JSAny; extern runtime WasmCompileWrapper(NoContext, WasmExportedFunctionData): JSAny; extern runtime WasmAllocateSuspender(Context): JSAny; } // namespace runtime namespace wasm { extern builtin JSToWasmWrapperAsm(RawPtr<intptr>, WasmInstanceObject, JSAny): JSAny; extern builtin WasmReturnPromiseOnSuspendAsm( RawPtr<intptr>, WasmInstanceObject, JSAny): JSAny; extern macro UniqueIntPtrConstant(constexpr intptr): intptr; const kWasmExportedFunctionDataSignatureOffset: constexpr int32 generates 'WasmExportedFunctionData::kSigOffset'; const kWasmReturnCountOffset: constexpr intptr generates 'wasm::FunctionSig::kReturnCountOffset'; const kWasmParameterCountOffset: constexpr intptr generates 'wasm::FunctionSig::kParameterCountOffset'; const kWasmSigTypesOffset: constexpr intptr generates 'wasm::FunctionSig::kRepsOffset'; // This constant should only be loaded as a `UniqueIntPtrConstant` to avoid // problems with PGO. // `- 1` because of the instance parameter. const kNumGPRegisterParameters: constexpr intptr generates 'arraysize(wasm::kGpParamRegisters) - 1'; // This constant should only be loaded as a `UniqueIntPtrConstant` to avoid // problems with PGO. const kNumFPRegisterParameters: constexpr intptr generates 'arraysize(wasm::kFpParamRegisters)'; const kNumGPRegisterReturns: constexpr intptr generates 'arraysize(wasm::kGpReturnRegisters)'; const kNumFPRegisterReturns: constexpr intptr generates 'arraysize(wasm::kFpReturnRegisters)'; const kWasmI32Type: constexpr int32 generates 'wasm::kWasmI32.raw_bit_field()'; const kWasmI64Type: constexpr int32 generates 'wasm::kWasmI64.raw_bit_field()'; const kWasmF32Type: constexpr int32 generates 'wasm::kWasmF32.raw_bit_field()'; const kWasmF64Type: constexpr int32 generates 'wasm::kWasmF64.raw_bit_field()'; extern enum ValueKind extends int32 constexpr 'wasm::ValueKind' { kRef, kRefNull, ... } extern enum HeapType extends int32 constexpr 'wasm::HeapType::Representation' { kExtern, kNoExtern, kString, kEq, kI31, kStruct, kArray, kAny, kNone, kNoFunc, ... } const kWrapperBufferReturnCount: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferReturnCount'; const kWrapperBufferRefReturnCount: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferRefReturnCount'; const kWrapperBufferSigRepresentationArray: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferSigRepresentationArray' ; const kWrapperBufferStackReturnBufferSize: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferStackReturnBufferSize' ; const kWrapperBufferCallTarget: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferCallTarget'; const kWrapperBufferParamStart: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferParamStart'; const kWrapperBufferParamEnd: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferParamEnd'; const kWrapperBufferStackReturnBufferStart: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferStackReturnBufferStart' ; const kWrapperBufferFPReturnRegister1: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferFPReturnRegister1' ; const kWrapperBufferFPReturnRegister2: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferFPReturnRegister2' ; const kWrapperBufferGPReturnRegister1: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferGPReturnRegister1' ; const kWrapperBufferGPReturnRegister2: constexpr intptr generates 'JSToWasmWrapperFrameConstants::kWrapperBufferGPReturnRegister2' ; const kWrapperBufferSize: constexpr int32 generates 'JSToWasmWrapperFrameConstants::kWrapperBufferSize'; const kValueTypeKindBits: constexpr int32 generates 'wasm::ValueType::kKindBits'; const kValueTypeKindBitsMask: constexpr int32 generates 'wasm::kWasmValueKindBitsMask'; const kValueTypeHeapTypeMask: constexpr int32 generates 'wasm::kWasmHeapTypeBitsMask'; macro Bitcast<To: type, From: type>(i: From): To { return i; } extern macro BitcastFloat32ToInt32(float32): uint32; Bitcast<uint32, float32>(v: float32): uint32 { return BitcastFloat32ToInt32(v); } macro RefCast<To: type>(i: &intptr): &To { return torque_internal::unsafe::NewReference<To>(i.object, i.offset); } macro TruncateBigIntToI64(context: Context, input: JSAny): intptr { // This is only safe to use on 64-bit platforms. dcheck(Is64()); const bigint = ToBigInt(context, input); if (bigint::ReadBigIntLength(bigint) == 0) { return 0; } const digit = bigint::LoadBigIntDigit(bigint, 0); if (bigint::ReadBigIntSign(bigint) == bigint::kPositiveSign) { // Note that even though the bigint is positive according to its sign, the // result of `Signed(digit)` can be negative if the most significant bit is // set. This is intentional and follows the specification of `ToBigInt64()`. return Signed(digit); } return 0 - Signed(digit); } @export struct Int64AsInt32Pair { low: uintptr; high: uintptr; } // This is only safe to use on 32-bit platforms. extern macro BigIntToRawBytes(BigInt): Int64AsInt32Pair; extern macro PopAndReturn(intptr, JSAny): never; // The ReturnSlotAllocator calculates the size of the space needed on the stack // for return values. struct ReturnSlotAllocator { macro AllocStack(): void { if constexpr (Is64()) { this.stackSlots++; } else { if (this.hasSmallSlot) { this.hasSmallSlot = false; this.smallSlotLast = false; } else { this.stackSlots += 2; this.hasSmallSlot = true; this.smallSlotLast = true; } } return; } macro AllocGP(): void { if (this.remainingGPRegs > 0) { this.remainingGPRegs--; return; } this.AllocStack(); } macro AllocFP32(): void { if (this.remainingFPRegs > 0) { this.remainingFPRegs--; return; } this.AllocStack(); } macro AllocFP64(): void { if (this.remainingFPRegs > 0) { this.remainingFPRegs--; return; } if constexpr (Is64()) { this.stackSlots++; } else { this.stackSlots += 2; this.smallSlotLast = false; } } macro GetSize(): intptr { if (this.smallSlotLast) { return this.stackSlots - 1; } else { return this.stackSlots; } } remainingGPRegs: intptr; remainingFPRegs: intptr; // Even on 32-bit platforms we always allocate 64-bit stack space at a time to // preserve alignment. If we allocate a 64-bit slot for a 32-bit type, then we // remember the second half of the 64-bit slot as `smallSlot` so that it can // be used for the next 32-bit type. hasSmallSlot: bool; // If the {smallSlot} is in the middle of the whole allocated stack space, // then it is part of the overall stack space size. However, if the hole is at // the border of the whole allocated stack space, then we have to subtract it // from the overall stack space size. This flag keeps track of whether the // hole is in the middle (false) or at the border (true). smallSlotLast: bool; stackSlots: intptr; } macro NewReturnSlotAllocator(): ReturnSlotAllocator { let result: ReturnSlotAllocator; result.remainingGPRegs = kNumGPRegisterReturns; result.remainingFPRegs = kNumFPRegisterReturns; result.stackSlots = 0; result.hasSmallSlot = false; result.smallSlotLast = false; return result; } struct LocationAllocator { macro GetStackSlot(): &intptr { if constexpr (Is64()) { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextStack); this.nextStack += torque_internal::SizeOf<intptr>(); return result; } else { if (this.smallSlot != 0) { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.smallSlot); this.smallSlot = 0; this.smallSlotLast = false; return result; } const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextStack); this.smallSlot = this.nextStack + torque_internal::SizeOf<intptr>(); this.nextStack = this.smallSlot + torque_internal::SizeOf<intptr>(); this.smallSlotLast = true; return result; } } macro GetGPSlot(): &intptr { if (this.remainingGPRegs-- > 0) { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextGPReg); this.nextGPReg += torque_internal::SizeOf<intptr>(); return result; } return this.GetStackSlot(); } macro GetFP32Slot(): &intptr { if (this.remainingFPRegs-- > 0) { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextFPReg); this.nextFPReg += torque_internal::SizeOf<float64>(); return result; } return this.GetStackSlot(); } macro GetFP64Slot(): &intptr { if (this.remainingFPRegs-- > 0) { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextFPReg); this.nextFPReg += torque_internal::SizeOf<float64>(); return result; } if constexpr (Is64()) { return this.GetStackSlot(); } else { const result = torque_internal::unsafe::NewReference<intptr>( this.object, this.nextStack); this.nextStack = this.nextStack + 2 * torque_internal::SizeOf<intptr>(); this.smallSlotLast = false; return result; } } // For references we start a new section on the stack, no old slots are // filled. macro StartRefs(): void { if (!this.smallSlotLast) { this.smallSlot = 0; } } macro GetStackEnd(): RawPtr { let offset = this.nextStack; if (this.smallSlotLast) { offset -= torque_internal::SizeOf<intptr>(); } return torque_internal::unsafe::GCUnsafeReferenceToRawPtr( this.object, offset); } macro GetAlignedStackEnd(alignment: intptr): RawPtr { let offset = this.nextStack; if (this.smallSlotLast) { offset -= torque_internal::SizeOf<intptr>(); } const stackSize = offset - this.stackStart; if (stackSize % alignment != 0) { offset += alignment - (stackSize % alignment); } return torque_internal::unsafe::GCUnsafeReferenceToRawPtr( this.object, offset); } object: HeapObject|TaggedZeroPattern; remainingGPRegs: intptr; remainingFPRegs: intptr; nextGPReg: intptr; nextFPReg: intptr; nextStack: intptr; stackStart: intptr; // Even on 32-bit platforms we always allocate 64-bit stack space at a time to // preserve alignment. If we allocate a 64-bit slot for a 32-bit type, then we // remember the second half of the 64-bit slot as `smallSlot` so that it can // be used for the next 32-bit type. smallSlot: intptr; // If the {smallSlot} is in the middle of the whole allocated stack space, // then it is part of the overall stack space size. However, if the hole is at // the border of the whole allocated stack space, then we have to subtract it // from the overall stack space size. This flag keeps track of whether the // hole is in the middle (false) or at the border (true). smallSlotLast: bool; } macro LocationAllocatorForParams(paramBuffer: &intptr): LocationAllocator { let result: LocationAllocator; result.object = paramBuffer.object; result.remainingGPRegs = UniqueIntPtrConstant(kNumGPRegisterParameters); result.remainingFPRegs = UniqueIntPtrConstant(kNumFPRegisterParameters); result.nextGPReg = paramBuffer.offset; result.nextFPReg = result.remainingGPRegs * torque_internal::SizeOf<intptr>(); if constexpr (!Is64()) { // Add padding to provide 8-byte alignment for float64 values. result.nextFPReg += (result.nextFPReg & torque_internal::SizeOf<intptr>()); } dcheck(result.nextFPReg % 8 == 0); result.nextFPReg += paramBuffer.offset; result.nextStack = result.nextFPReg + result.remainingFPRegs * torque_internal::SizeOf<float64>(); result.stackStart = result.nextStack; result.smallSlot = 0; result.smallSlotLast = false; return result; } macro LocationAllocatorForReturns( gpRegs: RawPtr, fpRegs: RawPtr, stack: RawPtr): LocationAllocator { let result: LocationAllocator; result.object = kZeroBitPattern; result.remainingGPRegs = kNumGPRegisterReturns; result.remainingFPRegs = kNumFPRegisterReturns; result.nextGPReg = Convert<intptr>(gpRegs) + kHeapObjectTag; result.nextFPReg = Convert<intptr>(fpRegs) + kHeapObjectTag; result.nextStack = Convert<intptr>(stack) + kHeapObjectTag; result.stackStart = result.nextStack; result.smallSlot = 0; result.smallSlotLast = false; return result; } macro JSToWasmObject( context: NativeContext, instanceOrUndefined: WasmInstanceObject|Undefined, targetType: int32, value: JSAny): Object { const heapType = (targetType >> kValueTypeKindBits) & kValueTypeHeapTypeMask; const kind = targetType & kValueTypeKindBitsMask; if (heapType == HeapType::kExtern || heapType == HeapType::kNoExtern) { if (kind == ValueKind::kRef && value == Null) { ThrowTypeError(MessageTemplate::kWasmTrapJSTypeError); } return value; } if (heapType == HeapType::kString) { if (TaggedIsSmi(value)) { ThrowTypeError(MessageTemplate::kWasmTrapJSTypeError); } if (IsString(UnsafeCast<HeapObject>(value))) { return value; } if (value == Null) { if (kind == ValueKind::kRef) { ThrowTypeError(MessageTemplate::kWasmTrapJSTypeError); } else { return kWasmNull; } } ThrowTypeError(MessageTemplate::kWasmTrapJSTypeError); } return runtime::WasmGenericJSToWasmObject( context, instanceOrUndefined, value, Convert<Smi>(targetType)); } macro JSToWasmWrapperHelper( context: NativeContext, _receiver: JSAny, target: JSFunction, arguments: Arguments, switchStack: constexpr bool): never { const functionData = UnsafeCast<WasmExportedFunctionData>( target.shared_function_info.function_data); // Trigger a wrapper tier-up when this function got called often enough. if constexpr (!switchStack) { functionData.wrapper_budget = functionData.wrapper_budget - 1; if (functionData.wrapper_budget == 0) { runtime::WasmCompileWrapper(kNoContext, functionData); } } const sig = functionData.sig_ptr; const paramCount = *GetRefAt<int32>(sig, kWasmParameterCountOffset); const returnCount = *GetRefAt<int32>(sig, kWasmReturnCountOffset); const reps = *GetRefAt<RawPtr>(sig, kWasmSigTypesOffset); const sigTypes = torque_internal::unsafe::NewOffHeapConstSlice( %RawDownCast<RawPtr<int32>>(reps), Convert<intptr>(paramCount + returnCount)); // If the return count is greater than 1, then the return values are returned // as a JSArray. After returning from the call to wasm, the return values are // stored on an area of the stack the GC does not know about. To avoid a GC // while references are still stored in this area of the stack, we allocate // the result JSArray already now before the call to wasm. let resultArray: JSAny = Undefined; let returnSize: intptr = 0; let hasRefReturns: bool = false; if (returnCount > 1) { resultArray = WasmAllocateJSArray(Convert<Smi>(returnCount)); // We have to calculate the size of the stack area where the wasm function // will store the return values for multi-return. const returnTypes = Subslice(sigTypes, Convert<intptr>(0), Convert<intptr>(returnCount)) otherwise unreachable; let retIt = returnTypes.Iterator(); let allocator = NewReturnSlotAllocator(); while (!retIt.Empty()) { const retType = retIt.NextNotEmpty(); if (retType == kWasmI32Type) { allocator.AllocGP(); } else if (retType == kWasmI64Type) { allocator.AllocGP(); if constexpr (!Is64()) { // On 32-bit platforms I64 values are stored as two I32 values. allocator.AllocGP(); } } else if (retType == kWasmF32Type) { allocator.AllocFP32(); } else if (retType == kWasmF64Type) { allocator.AllocFP64(); } else { // Also check if there are any reference return values, as this allows // us to skip code when we process return values. hasRefReturns = true; allocator.AllocGP(); } } returnSize = allocator.GetSize(); } const paramTypes = Subslice( sigTypes, Convert<intptr>(returnCount), Convert<intptr>(paramCount)) otherwise unreachable; let paramBuffer: &intptr; // 10 here is an arbitrary number. The analysis of signatures of exported // functions of big modules showed that most signatures have a low number of // I32 parameters. We picked a cutoff point where for most signatures the // pre-allocated stack slots are sufficient without making these stack slots // overly big. if (paramCount <= 10) { // Performance optimization: we pre-allocate a stack area with 18 // 8-byte slots, and use this area when it is sufficient for all // parameters. If the stack area is too small, we allocate a byte array // below. The stack area is big enough for 10 parameters. The 10 parameters // need 18 * 8 bytes because some segments of the stack area are reserved // for register parameters, and there may e.g. be no FP parameters passed // by register, so all 8 FP register slots would remain empty. const stackSlots = %RawDownCast<RawPtr<intptr>>( StackSlotPtr(144, torque_internal::SizeOf<float64>())); paramBuffer = torque_internal::unsafe::NewOffHeapReference(stackSlots); } else { // We have to estimate the size of the byte array such that it can store // all converted parameters. The size is the sum of sizes of the segments // for the gp registers, fp registers, and stack slots. The sizes of // the register segments are fixed, but for the size of the stack segment // we have to guess the number of parameters on the stack. On ia32 it can // happen that only a single parameter fits completely into a register, and // all other parameters end up at least partially on the stack (e.g. for a // signature with only I64 parameters). To make the calculation simpler, we // just assume that all parameters are on the stack. const kSlotSize: intptr = torque_internal::SizeOf<float64>(); const bufferSize = UniqueIntPtrConstant(kNumGPRegisterParameters) * Convert<intptr>(torque_internal::SizeOf<intptr>()) + UniqueIntPtrConstant(kNumFPRegisterParameters) * kSlotSize + Convert<intptr>(paramCount) * kSlotSize; const slice = &AllocateByteArray(Convert<uintptr>(bufferSize)).bytes; paramBuffer = torque_internal::unsafe::NewReference<intptr>( slice.object, slice.offset); } let locationAllocator = LocationAllocatorForParams(paramBuffer); let hasRefParam: bool = false; let paramTypeIndex: int32 = 0; // For stack switching paramTypeIndex and paramIndex diverges, // because a suspender is not passed to wrapper as param. if constexpr (switchStack) { paramTypeIndex++; hasRefParam = true; } for (let paramIndex: int32 = 0; paramTypeIndex < paramCount; paramIndex++) { const param = arguments[Convert<intptr>(paramIndex)]; const paramType = *paramTypes.UncheckedAtIndex( Convert<intptr>(paramTypeIndex++)); if (paramType == kWasmI32Type) { let toRef = locationAllocator.GetGPSlot(); typeswitch (param) { case (smiParam: Smi): { *toRef = Convert<intptr>(Unsigned(SmiToInt32(smiParam))); } case (heapParam: JSAnyNotSmi): { *toRef = Convert<intptr>(Unsigned(WasmTaggedNonSmiToInt32(heapParam))); } } } else if (paramType == kWasmF32Type) { let toRef = locationAllocator.GetFP32Slot(); *toRef = Convert<intptr>(Bitcast<uint32>(WasmTaggedToFloat32(param))); } else if (paramType == kWasmF64Type) { let toRef = locationAllocator.GetFP64Slot(); *RefCast<float64>(toRef) = ChangeTaggedToFloat64(param); } else if (paramType == kWasmI64Type) { if constexpr (Is64()) { let toRef = locationAllocator.GetGPSlot(); const v = TruncateBigIntToI64(context, param); *toRef = v; } else { let toLowRef = locationAllocator.GetGPSlot(); let toHighRef = locationAllocator.GetGPSlot(); const bigIntVal = ToBigInt(context, param); const pair = BigIntToRawBytes(bigIntVal); *toLowRef = Signed(pair.low); *toHighRef = Signed(pair.high); } } else { // The byte array where we store converted parameters is not GC-safe. // Therefore we can only copy references into this array once no GC can // happen anymore. Any conversion of a primitive type can execute // arbitrary JavaScript code and therefore also trigger GC. Therefore // references get copied into the array only after all parameters of // primitive types are finished. For now we write the converted parameter // back to the stack. hasRefParam = true; arguments[Convert<intptr>(paramIndex)] = JSToWasmObject(context, functionData.instance, paramType, param); } } if (hasRefParam) { // Iterate over all parameters again and handle all those with ref types. let k: int32 = 0; // For stack switching k and paramIndex diverges, // because a suspender is not passed to wrapper as param. let paramIndex: int32 = 0; locationAllocator.StartRefs(); if constexpr (switchStack) { const suspender = runtime::WasmAllocateSuspender(context); const toRef = locationAllocator.GetGPSlot(); *toRef = BitcastTaggedToWord(suspender); // First param is suspender, so skip it in the signature loop. k++; } // We are not using a `for` loop here because Torque does not support // `continue` in `for` loops. while (k < paramCount) { const paramType = *paramTypes.UncheckedAtIndex(Convert<intptr>(k)); const paramKind = paramType & kValueTypeKindBitsMask; if (paramKind != ValueKind::kRef && paramKind != ValueKind::kRefNull) { k++; paramIndex++; continue; } const param = arguments[Convert<intptr>(paramIndex++)]; let toRef = locationAllocator.GetGPSlot(); *toRef = BitcastTaggedToWord(param); k++; } } const paramStart = paramBuffer.GCUnsafeRawPtr(); const paramEnd = locationAllocator.GetStackEnd(); const internal: WasmInternalFunction = functionData.internal; const callTarget = internal.call_target_ptr; const instance: WasmInstanceObject = functionData.instance; // We construct a state that will be passed to `JSToWasmWrapperAsm` // and `JSToWasmHandleReturns`. There are too many parameters to pass // everything through registers. The stack area also contains slots for // values that get passed from `JSToWasmWrapperAsm` and // `WasmReturnPromiseOnSuspendAsm` to `JSToWasmHandleReturns`. const wrapperBuffer = %RawDownCast<RawPtr<intptr>>( StackSlotPtr(kWrapperBufferSize, torque_internal::SizeOf<intptr>())); *GetRefAt<int32>(wrapperBuffer, kWrapperBufferReturnCount) = returnCount; *GetRefAt<bool>(wrapperBuffer, kWrapperBufferRefReturnCount) = hasRefReturns; *GetRefAt<RawPtr>(wrapperBuffer, kWrapperBufferSigRepresentationArray) = reps; *GetRefAt<intptr>(wrapperBuffer, kWrapperBufferStackReturnBufferSize) = returnSize; *GetRefAt<RawPtr>(wrapperBuffer, kWrapperBufferCallTarget) = callTarget; *GetRefAt<RawPtr<intptr>>(wrapperBuffer, kWrapperBufferParamStart) = paramStart; *GetRefAt<RawPtr>(wrapperBuffer, kWrapperBufferParamEnd) = paramEnd; // Both `instance` and `resultArray` get passed separately as parameters to // make them GC-safe. They get passed over the stack so that they get scanned // by the GC as part of the outgoing parameters of this Torque builtin. let result: JSAny; if constexpr (switchStack) { result = WasmReturnPromiseOnSuspendAsm(wrapperBuffer, instance, resultArray); } else { result = JSToWasmWrapperAsm(wrapperBuffer, instance, resultArray); } // The normal return sequence of Torque-generated JavaScript builtins does not // consider the case where the caller may push additional "undefined" // parameters on the stack, and therefore does not generate code to pop these // additional parameters. Here we calculate the actual number of parameters on // the stack. This number is the number of actual parameters provided by the // caller, which is `arguments.length`, or the number of declared arguments, // if not enough actual parameters were provided, i.e. // `SharedFunctionInfo::length`. let popCount = arguments.length; const declaredArgCount = Convert<intptr>(Convert<int32>(target.shared_function_info.length)); if (declaredArgCount > popCount) { popCount = declaredArgCount; } // Also pop the receiver. PopAndReturn(popCount + 1, result); } transitioning javascript builtin JSToWasmWrapper( js-implicit context: NativeContext, receiver: JSAny, target: JSFunction)( ...arguments): JSAny { JSToWasmWrapperHelper(context, receiver, target, arguments, false); } transitioning javascript builtin WasmReturnPromiseOnSuspend( js-implicit context: NativeContext, receiver: JSAny, target: JSFunction)( ...arguments): JSAny { JSToWasmWrapperHelper(context, receiver, target, arguments, true); } macro WasmToJSObject(context: NativeContext, value: Object, retType: int32): JSAny { const paramKind = retType & kValueTypeKindBitsMask; const heapType = (retType >> kValueTypeKindBits) & kValueTypeHeapTypeMask; if (paramKind == ValueKind::kRef) { if (heapType == HeapType::kEq || heapType == HeapType::kI31 || heapType == HeapType::kStruct || heapType == HeapType::kArray || heapType == HeapType::kAny || heapType == HeapType::kExtern || heapType == HeapType::kString || heapType == HeapType::kNone || heapType == HeapType::kNoFunc || heapType == HeapType::kNoExtern) { return UnsafeCast<JSAny>(value); } // TODO(ahaas): This is overly pessimistic: all module-defined struct and // array types can be passed to JS as-is as well; and for function types we // could at least support the fast path where the WasmExternalFunction has // already been created. return runtime::WasmGenericWasmToJSObject(context, value); } else { dcheck(paramKind == ValueKind::kRefNull); if (heapType == HeapType::kExtern || heapType == HeapType::kNoExtern) { return UnsafeCast<JSAny>(value); } if (value == kWasmNull) { return Null; } if (heapType == HeapType::kEq || heapType == HeapType::kStruct || heapType == HeapType::kArray || heapType == HeapType::kString || heapType == HeapType::kI31 || heapType == HeapType::kAny) { return UnsafeCast<JSAny>(value); } // TODO(ahaas): This is overly pessimistic: all module-defined struct and // array types can be passed to JS as-is as well; and for function types we // could at least support the fast path where the WasmExternalFunction has // already been created. return runtime::WasmGenericWasmToJSObject(context, value); } } builtin JSToWasmHandleReturns( instance: WasmInstanceObject, resultArray: JSArray, wrapperBuffer: RawPtr<intptr>): JSAny { const returnCount = *GetRefAt<int32>( wrapperBuffer, kWrapperBufferReturnCount); if (returnCount == 0) { return Undefined; } if (returnCount == 1) { const reps = *GetRefAt<RawPtr>( wrapperBuffer, kWrapperBufferSigRepresentationArray); const retType = *GetRefAt<int32>(reps, 0); if (retType == kWasmI32Type) { const ret = *GetRefAt<int32>( wrapperBuffer, kWrapperBufferGPReturnRegister1); const result = Convert<Number>(ret); return result; } else if (retType == kWasmF32Type) { const resultRef = GetRefAt<float32>(wrapperBuffer, kWrapperBufferFPReturnRegister1); return Convert<Number>(*resultRef); } else if (retType == kWasmF64Type) { const resultRef = GetRefAt<float64>(wrapperBuffer, kWrapperBufferFPReturnRegister1); return Convert<Number>(*resultRef); } else if (retType == kWasmI64Type) { if constexpr (Is64()) { const ret = *GetRefAt<intptr>( wrapperBuffer, kWrapperBufferGPReturnRegister1); return I64ToBigInt(ret); } else { const lowWord = *GetRefAt<intptr>( wrapperBuffer, kWrapperBufferGPReturnRegister1); const highWord = *GetRefAt<intptr>( wrapperBuffer, kWrapperBufferGPReturnRegister2); return I32PairToBigInt(lowWord, highWord); } } else { const ptr = %RawDownCast<RawPtr<uintptr>>( wrapperBuffer + kWrapperBufferGPReturnRegister1); const rawRef = *GetRefAt<uintptr>(ptr, 0); const value = BitcastWordToTagged(rawRef); return WasmToJSObject(LoadContextFromInstance(instance), value, retType); } } // Multi return; const fixedArray: FixedArray = UnsafeCast<FixedArray>(resultArray.elements); const returnBuffer = *GetRefAt<RawPtr>( wrapperBuffer, kWrapperBufferStackReturnBufferStart); let locationAllocator = LocationAllocatorForReturns( wrapperBuffer + kWrapperBufferGPReturnRegister1, wrapperBuffer + kWrapperBufferFPReturnRegister1, returnBuffer); const reps = *GetRefAt<RawPtr>( wrapperBuffer, kWrapperBufferSigRepresentationArray); const retTypes = torque_internal::unsafe::NewOffHeapConstSlice( %RawDownCast<RawPtr<int32>>(reps), Convert<intptr>(returnCount)); const hasRefReturns = *GetRefAt<bool>( wrapperBuffer, kWrapperBufferRefReturnCount); if (hasRefReturns) { // We first process all references and copy them in the the result array to // put them into a location that is known to the GC. The processing of // references does not trigger a GC, but the allocation of HeapNumbers and // BigInts for primitive types may trigger a GC. for (let k: intptr = 0; k < Convert<intptr>(returnCount); k++) { const retType = *retTypes.UncheckedAtIndex(Convert<intptr>(k)); if (retType == kWasmI32Type) { locationAllocator.GetGPSlot(); } else if (retType == kWasmF32Type) { locationAllocator.GetFP32Slot(); } else if (retType == kWasmI64Type) { locationAllocator.GetGPSlot(); if constexpr (!Is64()) { locationAllocator.GetGPSlot(); } } else if (retType == kWasmF64Type) { locationAllocator.GetFP64Slot(); } else { let value: Object; const slot = locationAllocator.GetGPSlot(); const rawRef = *slot; value = BitcastWordToTagged(rawRef); // Store the wasm object in the JSArray to make it GC safe. The // transformation will happen later in a second loop. fixedArray.objects[k] = value; } } } locationAllocator = LocationAllocatorForReturns( wrapperBuffer + kWrapperBufferGPReturnRegister1, wrapperBuffer + kWrapperBufferFPReturnRegister1, returnBuffer); for (let k: intptr = 0; k < Convert<intptr>(returnCount); k++) { const retType = *retTypes.UncheckedAtIndex(Convert<intptr>(k)); if (retType == kWasmI32Type) { const slot = locationAllocator.GetGPSlot(); const val = *RefCast<int32>(slot); fixedArray.objects[k] = Convert<Number>(val); } else if (retType == kWasmF32Type) { const slot = locationAllocator.GetFP32Slot(); const val = *RefCast<float32>(slot); fixedArray.objects[k] = Convert<Number>(val); } else if (retType == kWasmI64Type) { if constexpr (Is64()) { const slot = locationAllocator.GetGPSlot(); const val = *slot; fixedArray.objects[k] = I64ToBigInt(val); } else { const lowWordSlot = locationAllocator.GetGPSlot(); const highWordSlot = locationAllocator.GetGPSlot(); const lowWord = *lowWordSlot; const highWord = *highWordSlot; fixedArray.objects[k] = I32PairToBigInt(lowWord, highWord); } } else if (retType == kWasmF64Type) { const slot = locationAllocator.GetFP64Slot(); const val = *RefCast<float64>(slot); fixedArray.objects[k] = Convert<Number>(val); } else { locationAllocator.GetGPSlot(); const value = fixedArray.objects[k]; fixedArray.objects[k] = WasmToJSObject(LoadContextFromInstance(instance), value, retType); } } return resultArray; } } // namespace wasm