Right way to run the same code twice in v8 (array out-of-bounds fails on second run - deoptimizer)
Asked Answered
H

1

8

The following program is based on the example in the v8 Getting Started page. I have made three changes to demonstrate a problem I am encountering:

  • I create an empty array put it into the global context.
  • The script being run references the zeroth element in the array, which should return undefined.
  • I run the compiled script twice.

The first run works fine. The second fails: v8 calls V8_Fatal() in Deoptimizer::DoComputeCompiledStubFrame() because descriptor->register_param_count_ == -1.

Am I doing something wrong here? How can I fix it?

Isolate* isolate = Isolate::New();
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
Local<Array> a = Array::New(isolate);
context->Global()->Set(String::NewFromUtf8(isolate, "a"), a);
Local<String> source = String::NewFromUtf8(isolate, "a[0];");
Local<Script> script = Script::Compile(source);
Local<Value> result = script->Run();
Local<Value> result2 = script->Run();
return 0;

NOTES:

  • This is the entire body of main().
  • Other fragments of JavaScript code run twice without a problem. Somehow this relates to the out-of-bound array reference, which is perhaps triggering deoptimization.
  • I do not want to recompile the script from scratch each time because I am typically running these scripts thousands of times, and sometimes millions of times.
  • I have tried compiling the script as an UnboundScript and then binding it for each execution, but the result is the same.
  • I have reported this as a v8 issue, but nobody has responded so I'm hoping that the StackOverflow community can help.
  • I am seeing this on VS2012 Update 4, but I also see it on VS2008, and in both x64 and x86 and in both Debug and Release builds.
Hardboard answered 16/7, 2014 at 1:59 Comment(1)
Problem seems to be related to snapshot support rather than deoptimization proper. If I link with v8_nosnapshot.lib rather than v8_snapshot.lib it all seems to do the right thing (including deoptimisation). Do you need snapshots?Fadein
F
4

OK, found it. The problem is an uninitialized code stub for dictionary loads - your use case triggers this as a failure as the stub isn't initialized through other means, eg compilation.

Below is a patch against v8 trunk revision 22629 that fixes the problem for me, tested on Windows with VS 2010 and Linux with g++ 4.9. Please let me know how you go with this:

Index: src/code-stubs.cc
===================================================================
--- src/code-stubs.cc   (revision 22629)
+++ src/code-stubs.cc   (working copy)
@@ -236,6 +236,8 @@
     CODE_STUB_LIST(DEF_CASE)
 #undef DEF_CASE
     case UninitializedMajorKey: return "<UninitializedMajorKey>Stub";
+    case NoCache:
+        return "<NoCache>Stub";
     default:
       if (!allow_unknown_keys) {
         UNREACHABLE();
@@ -939,6 +941,13 @@


 // static
+void KeyedLoadDictionaryElementStub::InstallDescriptors(Isolate* isolate) {
+  KeyedLoadDictionaryElementStub stub(isolate);
+  InstallDescriptor(isolate, &stub);
+}
+
+
+// static
 void KeyedLoadGenericElementStub::InstallDescriptors(Isolate* isolate) {
   KeyedLoadGenericElementStub stub(isolate);
   InstallDescriptor(isolate, &stub);
Index: src/code-stubs.h
===================================================================
--- src/code-stubs.h    (revision 22629)
+++ src/code-stubs.h    (working copy)
@@ -1862,6 +1862,8 @@
   virtual void InitializeInterfaceDescriptor(
       CodeStubInterfaceDescriptor* descriptor) V8_OVERRIDE;

+  static void InstallDescriptors(Isolate* isolate);
+
  private:
   Major MajorKey() const { return KeyedLoadElement; }
   int NotMissMinorKey() const { return DICTIONARY_ELEMENTS; }
Index: src/isolate.cc
===================================================================
--- src/isolate.cc  (revision 22629)
+++ src/isolate.cc  (working copy)
@@ -2000,6 +2000,7 @@
     NumberToStringStub::InstallDescriptors(this);
     StringAddStub::InstallDescriptors(this);
     RegExpConstructResultStub::InstallDescriptors(this);
+    KeyedLoadDictionaryElementStub::InstallDescriptors(this);
     KeyedLoadGenericElementStub::InstallDescriptors(this);
   }

As a workaround if you don't want to compile your own V8 for now, you could execute some code on each Isolate that uses the KeyedLoadDictionaryElementStub directly, prior to running your code --- this should initialize the stub. Something like the following works for me:

Isolate* isolate = Isolate::New();
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
Local<Array> a = Array::New(isolate);
context->Global()->Set(String::NewFromUtf8(isolate, "a"), a);

// Workaround code for initializing KeyedLoadDictionaryElementStub 
Local<String> workaround_source = String::NewFromUtf8(isolate, "Math.random()");
Local<Script> workaround_script = Script::Compile(workaround_source);
Local<Value>  workaround_value  = workaround_script->Run();
// End workaround

Local<String> source = String::NewFromUtf8(isolate, "a[0]");
Local<Script> script = Script::Compile(source);

// ...and so on
Fadein answered 27/7, 2014 at 10:42 Comment(2)
This fixes my problem on VS2012. You legend. I'm crossing my fingers and am going reconstruct the more complicated situation that led me to find that example.Hardboard
Yep, works for me. I noticed that you have posted the fix into v8 issue 3332.Hardboard

© 2022 - 2024 — McMap. All rights reserved.