[api] Add a dedicated UnboundModuleScript type

Module and script SharedFunctionInfos can't be used interchangeably
(e.g.: it should not be possible to bind a Module's SFI to a Context).

The dedicated type disambiguates the two.

This also adds an overload for CreateCodeCache which takes an unbound
module script instead of an unbound script. Both are just a SFI
underneath, so their behavior is identical.

Bug: v8:7685
Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: Iab519d0d50b6b41c95abdb6397f5622e292da4d8
Reviewed-on: https://chromium-review.googlesource.com/1047107
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53150}
diff --git a/include/v8.h b/include/v8.h
index e8d31bb..727e2d7 100644
--- a/include/v8.h
+++ b/include/v8.h
@@ -1213,6 +1213,13 @@
 };
 
 /**
+ * A compiled JavaScript module, not yet tied to a Context.
+ */
+class V8_EXPORT UnboundModuleScript {
+  // Only used as a container for code caching.
+};
+
+/**
  * A location in JavaScript source.
  */
 class V8_EXPORT Location {
@@ -1313,12 +1320,12 @@
   Local<Value> GetModuleNamespace();
 
   /**
-   * Returns the corresponding context-unbound script.
+   * Returns the corresponding context-unbound module script.
    *
    * The module must be unevaluated, i.e. its status must not be kEvaluating,
    * kEvaluated or kErrored.
    */
-  Local<UnboundScript> GetUnboundScript();
+  Local<UnboundModuleScript> GetUnboundModuleScript();
 };
 
 /**
@@ -1677,6 +1684,14 @@
    */
   static CachedData* CreateCodeCache(Local<UnboundScript> unbound_script);
 
+  /**
+   * Creates and returns code cache for the specified unbound_module_script.
+   * This will return nullptr if the script cannot be serialized. The
+   * CachedData returned by this function should be owned by the caller.
+   */
+  static CachedData* CreateCodeCache(
+      Local<UnboundModuleScript> unbound_module_script);
+
   V8_DEPRECATED("Source string is no longer required",
                 static CachedData* CreateCodeCache(
                     Local<UnboundScript> unbound_script, Local<String> source));
diff --git a/src/api.cc b/src/api.cc
index 2344bb3..47d1b04 100644
--- a/src/api.cc
+++ b/src/api.cc
@@ -2323,12 +2323,12 @@
   return ToApiHandle<Value>(module_namespace);
 }
 
-Local<UnboundScript> Module::GetUnboundScript() {
+Local<UnboundModuleScript> Module::GetUnboundModuleScript() {
   Utils::ApiCheck(
       GetStatus() < kEvaluating, "v8::Module::GetUnboundScript",
       "v8::Module::GetUnboundScript must be used on an unevaluated module");
   i::Handle<i::Module> self = Utils::OpenHandle(this);
-  return ToApiHandle<UnboundScript>(
+  return ToApiHandle<UnboundModuleScript>(
       i::Handle<i::SharedFunctionInfo>(self->GetSharedFunctionInfo()));
 }
 
@@ -2673,6 +2673,16 @@
   return i::CodeSerializer::Serialize(shared);
 }
 
+// static
+ScriptCompiler::CachedData* ScriptCompiler::CreateCodeCache(
+    Local<UnboundModuleScript> unbound_module_script) {
+  i::Handle<i::SharedFunctionInfo> shared =
+      i::Handle<i::SharedFunctionInfo>::cast(
+          Utils::OpenHandle(*unbound_module_script));
+  DCHECK(shared->is_toplevel());
+  return i::CodeSerializer::Serialize(shared);
+}
+
 ScriptCompiler::CachedData* ScriptCompiler::CreateCodeCacheForFunction(
     Local<Function> function, Local<String> source) {
   return CreateCodeCacheForFunction(function);
diff --git a/src/api.h b/src/api.h
index d8b6f94..d1297c8 100644
--- a/src/api.h
+++ b/src/api.h
@@ -112,6 +112,7 @@
   V(String, String)                            \
   V(Symbol, Symbol)                            \
   V(Script, JSFunction)                        \
+  V(UnboundModuleScript, SharedFunctionInfo)   \
   V(UnboundScript, SharedFunctionInfo)         \
   V(Module, Module)                            \
   V(Function, JSReceiver)                      \
diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc
index 0cc24b1..a00fae1 100644
--- a/test/cctest/test-api.cc
+++ b/test/cctest/test-api.cc
@@ -25636,6 +25636,55 @@
   isolate2->Dispose();
 }
 
+v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(Local<Context> context,
+                                                       Local<String> specifier,
+                                                       Local<Module> referrer) {
+  CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
+}
+
+TEST(ModuleCodeCache) {
+  v8::Isolate::CreateParams create_params;
+  create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
+
+  const char* origin = "code cache test";
+  v8::ScriptCompiler::CachedData* cache;
+
+  v8::Isolate* isolate1 = v8::Isolate::New(create_params);
+  {
+    v8::Isolate::Scope iscope(isolate1);
+    v8::HandleScope scope(isolate1);
+    v8::Local<v8::Context> context = v8::Context::New(isolate1);
+    v8::Context::Scope cscope(context);
+
+    Local<String> source_text = v8_str(
+        "export default 5; export const a = 10; function f() { return 42; } "
+        "(function() { return f(); })();");
+    v8::ScriptOrigin script_origin(
+        v8_str(origin), Local<v8::Integer>(), Local<v8::Integer>(),
+        Local<v8::Boolean>(), Local<v8::Integer>(), Local<v8::Value>(),
+        Local<v8::Boolean>(), Local<v8::Boolean>(), True(isolate1));
+    v8::ScriptCompiler::Source source(source_text, script_origin);
+    Local<Module> module =
+        v8::ScriptCompiler::CompileModule(isolate1, &source).ToLocalChecked();
+    module->InstantiateModule(context, UnexpectedModuleResolveCallback)
+        .ToChecked();
+
+    // Fetch the shared function info before evaluation.
+    Local<v8::UnboundModuleScript> unbound_module_script =
+        module->GetUnboundModuleScript();
+
+    // Evaluate for possible lazy compilation.
+    Local<Value> completion_value = module->Evaluate(context).ToLocalChecked();
+    CHECK_EQ(42, completion_value->Int32Value(context).FromJust());
+
+    // Now create the cache.
+    cache = v8::ScriptCompiler::CreateCodeCache(unbound_module_script);
+  }
+  isolate1->Dispose();
+
+  // TODO(jgruber,v8:7685): Test module code cache consumption once implemented.
+  delete cache;
+}
 
 void TestInvalidCacheData(v8::ScriptCompiler::CompileOptions option) {
   const char* garbage = "garbage garbage garbage garbage garbage garbage";
@@ -27370,12 +27419,6 @@
   meta->CreateDataProperty(context, v8_str("foo"), v8_str("bar")).ToChecked();
 }
 
-v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(Local<Context> context,
-                                                       Local<String> specifier,
-                                                       Local<Module> referrer) {
-  CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
-}
-
 TEST(ImportMeta) {
   i::FLAG_harmony_dynamic_import = true;
   i::FLAG_harmony_import_meta = true;
@@ -27443,7 +27486,7 @@
             ->StrictEquals(v8::Number::New(isolate, 10)));
 }
 
-TEST(ModuleGetUnboundScript) {
+TEST(ModuleGetUnboundModuleScript) {
   LocalContext context;
   v8::Isolate* isolate = context->GetIsolate();
   v8::HandleScope scope(isolate);
@@ -27457,11 +27500,12 @@
   v8::ScriptCompiler::Source source(source_text, origin);
   Local<Module> module =
       v8::ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
-  Local<v8::UnboundScript> sfi_before_instantiation =
-      module->GetUnboundScript();
+  Local<v8::UnboundModuleScript> sfi_before_instantiation =
+      module->GetUnboundModuleScript();
   module->InstantiateModule(context.local(), UnexpectedModuleResolveCallback)
       .ToChecked();
-  Local<v8::UnboundScript> sfi_after_instantiation = module->GetUnboundScript();
+  Local<v8::UnboundModuleScript> sfi_after_instantiation =
+      module->GetUnboundModuleScript();
 
   // Check object identity.
   {
@@ -27469,11 +27513,6 @@
     i::Handle<i::Object> s2 = v8::Utils::OpenHandle(*sfi_after_instantiation);
     CHECK_EQ(*s1, *s2);
   }
-
-  // Check unbound script values.
-  Local<v8::UnboundScript> sfi = sfi_after_instantiation;
-  CHECK(ValueEqualsString(isolate, sfi->GetScriptName(), "www.google.com"));
-  CHECK_EQ(0, sfi->GetLineNumber(0));
 }
 
 TEST(GlobalTemplateWithDoubleProperty) {