blob: 8e7a29bd0bed3e5c07c727d77f7956bf40a14219 [file] [log] [blame]
// Copyright 2022 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-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/codegen/code-stub-assembler-inl.h"
#include "src/objects/descriptor-array.h"
#include "src/objects/js-shadow-realm.h"
#include "src/objects/module.h"
namespace v8 {
namespace internal {
#include "src/codegen/define-code-stub-assembler-macros.inc"
class ShadowRealmBuiltinsAssembler : public CodeStubAssembler {
public:
explicit ShadowRealmBuiltinsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
enum ImportValueFulfilledFunctionContextSlot {
kEvalContextSlot = Context::MIN_CONTEXT_SLOTS,
kSpecifierSlot,
kExportNameSlot,
kContextLength,
};
protected:
TNode<JSObject> AllocateJSWrappedFunction(TNode<Context> context,
TNode<Object> target);
void CheckAccessor(TNode<DescriptorArray> array, TNode<IntPtrT> index,
TNode<Name> name, Label* bailout);
TNode<Object> ImportValue(TNode<NativeContext> caller_context,
TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name);
TNode<Context> CreateImportValueFulfilledFunctionContext(
TNode<NativeContext> caller_context, TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name);
TNode<JSFunction> AllocateImportValueFulfilledFunction(
TNode<NativeContext> caller_context, TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name);
void ShadowRealmThrow(TNode<Context> context,
MessageTemplate fallback_message,
TNode<Object> exception);
};
TNode<JSObject> ShadowRealmBuiltinsAssembler::AllocateJSWrappedFunction(
TNode<Context> context, TNode<Object> target) {
TNode<NativeContext> native_context = LoadNativeContext(context);
TNode<Map> map = CAST(
LoadContextElement(native_context, Context::WRAPPED_FUNCTION_MAP_INDEX));
TNode<JSObject> wrapped = AllocateJSObjectFromMap(map);
StoreObjectFieldNoWriteBarrier(
wrapped, JSWrappedFunction::kWrappedTargetFunctionOffset, target);
StoreObjectFieldNoWriteBarrier(wrapped, JSWrappedFunction::kContextOffset,
context);
return wrapped;
}
TNode<Context>
ShadowRealmBuiltinsAssembler::CreateImportValueFulfilledFunctionContext(
TNode<NativeContext> caller_context, TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name) {
const TNode<Context> context = AllocateSyntheticFunctionContext(
caller_context, ImportValueFulfilledFunctionContextSlot::kContextLength);
StoreContextElementNoWriteBarrier(
context, ImportValueFulfilledFunctionContextSlot::kEvalContextSlot,
eval_context);
StoreContextElementNoWriteBarrier(
context, ImportValueFulfilledFunctionContextSlot::kSpecifierSlot,
specifier);
StoreContextElementNoWriteBarrier(
context, ImportValueFulfilledFunctionContextSlot::kExportNameSlot,
export_name);
return context;
}
TNode<JSFunction>
ShadowRealmBuiltinsAssembler::AllocateImportValueFulfilledFunction(
TNode<NativeContext> caller_context, TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name) {
const TNode<Context> function_context =
CreateImportValueFulfilledFunctionContext(caller_context, eval_context,
specifier, export_name);
return AllocateRootFunctionWithContext(
RootIndex::kShadowRealmImportValueFulfilledSharedFun, function_context,
{});
}
void ShadowRealmBuiltinsAssembler::CheckAccessor(TNode<DescriptorArray> array,
TNode<IntPtrT> index,
TNode<Name> name,
Label* bailout) {
TNode<Name> key = LoadKeyByDescriptorEntry(array, index);
GotoIfNot(TaggedEqual(key, name), bailout);
TNode<Object> value = LoadValueByDescriptorEntry(array, index);
GotoIfNot(IsAccessorInfo(CAST(value)), bailout);
}
void ShadowRealmBuiltinsAssembler::ShadowRealmThrow(
TNode<Context> context, MessageTemplate fallback_message,
TNode<Object> exception) {
TNode<Smi> template_index = SmiConstant(static_cast<int>(fallback_message));
CallRuntime(Runtime::kShadowRealmThrow, context, template_index, exception);
Unreachable();
}
// https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue
TF_BUILTIN(ShadowRealmGetWrappedValue, ShadowRealmBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto creation_context = Parameter<Context>(Descriptor::kCreationContext);
auto target_context = Parameter<Context>(Descriptor::kTargetContext);
auto value = Parameter<Object>(Descriptor::kValue);
Label if_primitive(this), if_callable(this), unwrap(this), wrap(this),
slow_wrap(this, Label::kDeferred), bailout(this, Label::kDeferred);
// 2. Return value.
GotoIf(TaggedIsSmi(value), &if_primitive);
GotoIfNot(JSAnyIsNotPrimitive(CAST(value)), &if_primitive);
// 1. If Type(value) is Object, then
// 1a. If IsCallable(value) is false, throw a TypeError exception.
// 1b. Return ? WrappedFunctionCreate(callerRealm, value).
Branch(IsCallable(CAST(value)), &if_callable, &bailout);
BIND(&if_primitive);
Return(value);
BIND(&if_callable);
TVARIABLE(Object, target);
target = value;
// WrappedFunctionCreate
// https://tc39.es/proposal-shadowrealm/#sec-wrappedfunctioncreate
Branch(IsJSWrappedFunction(CAST(value)), &unwrap, &wrap);
BIND(&unwrap);
// The intermediate wrapped functions are not user-visible. And calling a
// wrapped function won't cause a side effect in the creation realm.
// Unwrap here to avoid nested unwrapping at the call site.
TNode<JSWrappedFunction> target_wrapped_function = CAST(value);
target = LoadObjectField(target_wrapped_function,
JSWrappedFunction::kWrappedTargetFunctionOffset);
Goto(&wrap);
BIND(&wrap);
// Disallow wrapping of slow-mode functions. We need to figure out
// whether the length and name property are in the original state.
TNode<Map> map = LoadMap(CAST(target.value()));
GotoIf(IsDictionaryMap(map), &slow_wrap);
// Check whether the length and name properties are still present as
// AccessorInfo objects. If so, their value can be recomputed even if
// the actual value on the object changes.
TNode<Uint32T> bit_field3 = LoadMapBitField3(map);
TNode<IntPtrT> number_of_own_descriptors = Signed(
DecodeWordFromWord32<Map::Bits3::NumberOfOwnDescriptorsBits>(bit_field3));
GotoIf(IntPtrLessThan(
number_of_own_descriptors,
IntPtrConstant(JSFunction::kMinDescriptorsForFastBindAndWrap)),
&slow_wrap);
// We don't need to check the exact accessor here because the only case
// custom accessor arise is with function templates via API, and in that
// case the object is in dictionary mode
TNode<DescriptorArray> descriptors = LoadMapInstanceDescriptors(map);
CheckAccessor(
descriptors,
IntPtrConstant(
JSFunctionOrBoundFunctionOrWrappedFunction::kLengthDescriptorIndex),
LengthStringConstant(), &slow_wrap);
CheckAccessor(
descriptors,
IntPtrConstant(
JSFunctionOrBoundFunctionOrWrappedFunction::kNameDescriptorIndex),
NameStringConstant(), &slow_wrap);
// Verify that prototype matches the function prototype of the target
// context.
TNode<Object> prototype = LoadMapPrototype(map);
TNode<Object> function_map =
LoadContextElement(target_context, Context::WRAPPED_FUNCTION_MAP_INDEX);
TNode<Object> function_prototype = LoadMapPrototype(CAST(function_map));
GotoIf(TaggedNotEqual(prototype, function_prototype), &slow_wrap);
// 1. Let internalSlotsList be the internal slots listed in Table 2, plus
// [[Prototype]] and [[Extensible]].
// 2. Let wrapped be ! MakeBasicObject(internalSlotsList).
// 3. Set wrapped.[[Prototype]] to
// callerRealm.[[Intrinsics]].[[%Function.prototype%]].
// 4. Set wrapped.[[Call]] as described in 2.1.
// 5. Set wrapped.[[WrappedTargetFunction]] to Target.
// 6. Set wrapped.[[Realm]] to callerRealm.
// 7. Let result be CopyNameAndLength(wrapped, Target, "wrapped").
// 8. If result is an Abrupt Completion, throw a TypeError exception.
// Installed with default accessors.
TNode<JSObject> wrapped =
AllocateJSWrappedFunction(creation_context, target.value());
// 9. Return wrapped.
Return(wrapped);
BIND(&slow_wrap);
{
Return(CallRuntime(Runtime::kShadowRealmWrappedFunctionCreate, context,
creation_context, target.value()));
}
BIND(&bailout);
ThrowTypeError(context, MessageTemplate::kNotCallable, value);
}
// https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist
TF_BUILTIN(CallWrappedFunction, ShadowRealmBuiltinsAssembler) {
auto argc = UncheckedParameter<Int32T>(Descriptor::kActualArgumentsCount);
TNode<IntPtrT> argc_ptr = ChangeInt32ToIntPtr(argc);
auto wrapped_function = Parameter<JSWrappedFunction>(Descriptor::kFunction);
auto context = Parameter<Context>(Descriptor::kContext);
PerformStackCheck(context);
Label call_exception(this, Label::kDeferred),
target_not_callable(this, Label::kDeferred);
// 1. Let target be F.[[WrappedTargetFunction]].
TNode<JSReceiver> target = CAST(LoadObjectField(
wrapped_function, JSWrappedFunction::kWrappedTargetFunctionOffset));
// 2. Assert: IsCallable(target) is true.
CSA_DCHECK(this, IsCallable(target));
// 4. Let callerRealm be ? GetFunctionRealm(F).
TNode<Context> caller_context = LoadObjectField<Context>(
wrapped_function, JSWrappedFunction::kContextOffset);
// 3. Let targetRealm be ? GetFunctionRealm(target).
TNode<Context> target_context =
GetFunctionRealm(caller_context, target, &target_not_callable);
// 5. NOTE: Any exception objects produced after this point are associated
// with callerRealm.
CodeStubArguments args(this, argc_ptr);
TNode<Object> receiver = args.GetReceiver();
// 6. Let wrappedArgs be a new empty List.
TNode<FixedArray> wrapped_args =
CAST(AllocateFixedArray(ElementsKind::PACKED_ELEMENTS, argc_ptr));
// Fill the fixed array so that heap verifier doesn't complain about it.
FillFixedArrayWithValue(ElementsKind::PACKED_ELEMENTS, wrapped_args,
IntPtrConstant(0), argc_ptr,
RootIndex::kUndefinedValue);
// 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm, thisArgument).
// Create wrapped value in the target realm.
TNode<Object> wrapped_receiver =
CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
target_context, caller_context, receiver);
StoreFixedArrayElement(wrapped_args, 0, wrapped_receiver);
// 7. For each element arg of argumentsList, do
BuildFastLoop<IntPtrT>(
IntPtrConstant(0), args.GetLengthWithoutReceiver(),
[&](TNode<IntPtrT> index) {
// 7a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg).
// Create wrapped value in the target realm.
TNode<Object> wrapped_value =
CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
target_context, caller_context, args.AtIndex(index));
// 7b. Append wrappedValue to wrappedArgs.
StoreFixedArrayElement(
wrapped_args, IntPtrAdd(index, IntPtrConstant(1)), wrapped_value);
},
1, LoopUnrollingMode::kNo, IndexAdvanceMode::kPost);
TVARIABLE(Object, var_exception);
TNode<Object> result;
{
compiler::ScopedExceptionHandler handler(this, &call_exception,
&var_exception);
TNode<Int32T> args_count = Int32Constant(0); // args already on the stack
// 9. Let result be the Completion Record of Call(target,
// wrappedThisArgument, wrappedArgs).
result = CallBuiltin(Builtin::kCallVarargs, target_context, target,
args_count, argc, wrapped_args);
}
// 10. If result.[[Type]] is normal or result.[[Type]] is return, then
// 10a. Return ? GetWrappedValue(callerRealm, result.[[Value]]).
TNode<Object> wrapped_result =
CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
caller_context, target_context, result);
args.PopAndReturn(wrapped_result);
// 11. Else,
BIND(&call_exception);
// 11a. Throw a TypeError exception.
ShadowRealmThrow(context, MessageTemplate::kCallWrappedFunctionThrew,
var_exception.value());
BIND(&target_not_callable);
// A wrapped value should not be non-callable.
Unreachable();
}
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue
TF_BUILTIN(ShadowRealmPrototypeImportValue, ShadowRealmBuiltinsAssembler) {
const char* const kMethodName = "ShadowRealm.prototype.importValue";
TNode<Context> context = Parameter<Context>(Descriptor::kContext);
// 1. Let O be this value.
TNode<Object> O = Parameter<Object>(Descriptor::kReceiver);
// 2. Perform ? ValidateShadowRealmObject(O).
ThrowIfNotInstanceType(context, O, JS_SHADOW_REALM_TYPE, kMethodName);
// 3. Let specifierString be ? ToString(specifier).
TNode<Object> specifier = Parameter<Object>(Descriptor::kSpecifier);
TNode<String> specifier_string = ToString_Inline(context, specifier);
// 4. Let exportNameString be ? ToString(exportName).
TNode<Object> export_name = Parameter<Object>(Descriptor::kExportName);
TNode<String> export_name_string = ToString_Inline(context, export_name);
// 5. Let callerRealm be the current Realm Record.
TNode<NativeContext> caller_context = LoadNativeContext(context);
// 6. Let evalRealm be O.[[ShadowRealm]].
// 7. Let evalContext be O.[[ExecutionContext]].
TNode<NativeContext> eval_context =
CAST(LoadObjectField(CAST(O), JSShadowRealm::kNativeContextOffset));
// 8. Return ? ShadowRealmImportValue(specifierString, exportNameString,
// callerRealm, evalRealm, evalContext).
TNode<Object> result = ImportValue(caller_context, eval_context,
specifier_string, export_name_string);
Return(result);
}
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue
TNode<Object> ShadowRealmBuiltinsAssembler::ImportValue(
TNode<NativeContext> caller_context, TNode<NativeContext> eval_context,
TNode<String> specifier, TNode<String> export_name) {
// 1. Assert: evalContext is an execution context associated to a ShadowRealm
// instance's [[ExecutionContext]].
// 2. Let innerCapability be ! NewPromiseCapability(%Promise%).
// 3. Let runningContext be the running execution context.
// 4. If runningContext is not already suspended, suspend runningContext.
// 5. Push evalContext onto the execution context stack; evalContext is now
// the running execution context.
// 6. Perform ! HostImportModuleDynamically(null, specifierString,
// innerCapability).
// 7. Suspend evalContext and remove it from the execution context stack.
// 8. Resume the context that is now on the top of the execution context stack
// as the running execution context.
TNode<Object> inner_capability =
CallRuntime(Runtime::kShadowRealmImportValue, eval_context, specifier);
// 9. Let steps be the steps of an ExportGetter function as described below.
// 10. Let onFulfilled be ! CreateBuiltinFunction(steps, 1, "", «
// [[ExportNameString]] », callerRealm).
// 11. Set onFulfilled.[[ExportNameString]] to exportNameString.
TNode<JSFunction> on_fulfilled = AllocateImportValueFulfilledFunction(
caller_context, eval_context, specifier, export_name);
TNode<JSFunction> on_rejected = CAST(LoadContextElement(
caller_context, Context::SHADOW_REALM_IMPORT_VALUE_REJECTED_INDEX));
// 12. Let promiseCapability be ! NewPromiseCapability(%Promise%).
TNode<JSPromise> promise = NewJSPromise(caller_context);
// 13. Return ! PerformPromiseThen(innerCapability.[[Promise]], onFulfilled,
// callerRealm.[[Intrinsics]].[[%ThrowTypeError%]], promiseCapability).
return CallBuiltin(Builtin::kPerformPromiseThen, caller_context,
inner_capability, on_fulfilled, on_rejected, promise);
}
// ExportGetter of
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue
TF_BUILTIN(ShadowRealmImportValueFulfilled, ShadowRealmBuiltinsAssembler) {
// An ExportGetter function is an anonymous built-in function with a
// [[ExportNameString]] internal slot. When an ExportGetter function is called
// with argument exports, it performs the following steps:
// 8. Let realm be f.[[Realm]].
TNode<Context> context = Parameter<Context>(Descriptor::kContext);
TNode<Context> eval_context = CAST(LoadContextElement(
context, ImportValueFulfilledFunctionContextSlot::kEvalContextSlot));
Label get_export_exception(this, Label::kDeferred);
// 2. Let f be the active function object.
// 3. Let string be f.[[ExportNameString]].
// 4. Assert: Type(string) is String.
TNode<String> export_name_string = CAST(LoadContextElement(
context, ImportValueFulfilledFunctionContextSlot::kExportNameSlot));
// 1. Assert: exports is a module namespace exotic object.
TNode<JSModuleNamespace> exports =
Parameter<JSModuleNamespace>(Descriptor::kExports);
// 5. Let hasOwn be ? HasOwnProperty(exports, string).
// 6. If hasOwn is false, throw a TypeError exception.
// 7. Let value be ? Get(exports, string).
// The only exceptions thrown by Runtime::kGetModuleNamespaceExport are
// either the export is not found or the module is not initialized.
TVARIABLE(Object, var_exception);
TNode<Object> value;
{
compiler::ScopedExceptionHandler handler(this, &get_export_exception,
&var_exception);
value = CallRuntime(Runtime::kGetModuleNamespaceExport, eval_context,
exports, export_name_string);
}
// 9. Return ? GetWrappedValue(realm, value).
TNode<NativeContext> caller_context = LoadNativeContext(context);
TNode<Object> wrapped_result =
CallBuiltin(Builtin::kShadowRealmGetWrappedValue, caller_context,
caller_context, eval_context, value);
Return(wrapped_result);
BIND(&get_export_exception);
{
TNode<String> specifier_string = CAST(LoadContextElement(
context, ImportValueFulfilledFunctionContextSlot::kSpecifierSlot));
ThrowTypeError(context, MessageTemplate::kUnresolvableExport,
specifier_string, export_name_string);
}
}
TF_BUILTIN(ShadowRealmImportValueRejected, ShadowRealmBuiltinsAssembler) {
TNode<Context> context = Parameter<Context>(Descriptor::kContext);
TNode<Object> exception = Parameter<Object>(Descriptor::kException);
ShadowRealmThrow(context, MessageTemplate::kImportShadowRealmRejected,
exception);
}
#include "src/codegen/undef-code-stub-assembler-macros.inc"
} // namespace internal
} // namespace v8