Adds external CodeObserver callback to the VM.

This enables external (such as by the embedder) accounting of the code objects for resolving stack traces.

Bug: b/117752777
Change-Id: I824fb9b27e9aa97373b155cf07e9e8f17722ba07
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/85360
Commit-Queue: Clement Skau <cskau@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/bin/run_vm_tests.cc b/runtime/bin/run_vm_tests.cc
index 70022e8..019522b 100644
--- a/runtime/bin/run_vm_tests.cc
+++ b/runtime/bin/run_vm_tests.cc
@@ -322,7 +322,7 @@
       dart::bin::DartUtils::OpenFile, dart::bin::DartUtils::ReadFile,
       dart::bin::DartUtils::WriteFile, dart::bin::DartUtils::CloseFile,
       nullptr /* entropy_source */, nullptr /* get_service_assets */,
-      start_kernel_isolate);
+      start_kernel_isolate, nullptr /* observer */);
   if (error != nullptr) {
     Syslog::PrintErr("Failed to initialize VM: %s\n", error);
     free(error);
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index 31a7d1b..16a5b80 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -714,7 +714,31 @@
  * The current version of the Dart_InitializeFlags. Should be incremented every
  * time Dart_InitializeFlags changes in a binary incompatible way.
  */
-#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000003)
+#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000004)
+
+/** Forward declaration */
+struct Dart_CodeObserver;
+
+/**
+ * Callback provided by the embedder that is used by the VM to notify on code
+ * object creation, *before* it is invoked the first time.
+ * This is useful for embedders wanting to e.g. keep track of PCs beyond
+ * the lifetime of the garbage collected code objects.
+ * Note that an address range may be used by more than one code object over the
+ * lifecycle of a process. Clients of this function should record timestamps for
+ * these compilation events and when collecting PCs to disambiguate reused
+ * address ranges.
+ */
+typedef void (*Dart_OnNewCodeCallback)(struct Dart_CodeObserver* observer,
+                                       const char* name,
+                                       uintptr_t base,
+                                       uintptr_t size);
+
+typedef struct Dart_CodeObserver {
+  void* data;
+
+  Dart_OnNewCodeCallback on_new_code;
+} Dart_CodeObserver;
 
 /**
  * Describes how to initialize the VM. Used with Dart_Initialize.
@@ -736,6 +760,8 @@
  * \param get_service_assets A function to be called by the service isolate when
  *    it requires the vmservice assets archive.
  *    See Dart_GetVMServiceAssetsArchive.
+ * \param code_observer An external code observer callback function.
+ *    The observer can be invoked as early as during the Dart_Initialize() call.
  */
 typedef struct {
   int32_t version;
@@ -752,6 +778,7 @@
   Dart_EntropySource entropy_source;
   Dart_GetVMServiceAssetsArchive get_service_assets;
   bool start_kernel_isolate;
+  Dart_CodeObserver* code_observer;
 } Dart_InitializeParams;
 
 /**
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index 78c8eb0..ee36e81 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -7,6 +7,7 @@
 #include "platform/assert.h"
 #include "vm/bootstrap.h"
 #include "vm/class_id.h"
+#include "vm/code_observers.h"
 #include "vm/compiler/backend/code_statistics.h"
 #include "vm/compiler/relocation.h"
 #include "vm/dart.h"
@@ -1572,6 +1573,19 @@
       code->ptr()->state_bits_ = d->Read<int32_t>();
     }
   }
+
+#if !(defined(DART_PRECOMPILED_RUNTIME) || defined(PRODUCT))
+  void PostLoad(const Array& refs, Snapshot::Kind kind, Zone* zone) {
+    if (!CodeObservers::AreActive()) return;
+    Code& code = Code::Handle(zone);
+    Function& function = Function::Handle(zone);
+    for (intptr_t id = start_index_; id < stop_index_; id++) {
+      code ^= refs.At(id);
+      function = code.function();
+      Code::NotifyCodeObservers(function, code, code.is_optimized());
+    }
+  }
+#endif  // !DART_PRECOMPILED_RUNTIME
 };
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/code_observers.cc b/runtime/vm/code_observers.cc
index 7832900..6aab6e7 100644
--- a/runtime/vm/code_observers.cc
+++ b/runtime/vm/code_observers.cc
@@ -15,6 +15,30 @@
 intptr_t CodeObservers::observers_length_ = 0;
 CodeObserver** CodeObservers::observers_ = NULL;
 
+class ExternalCodeObserverAdapter : public CodeObserver {
+ public:
+  explicit ExternalCodeObserverAdapter(Dart_CodeObserver* delegate)
+      : delegate_(delegate) {}
+
+  virtual bool IsActive() const { return true; }
+
+  virtual void Notify(const char* name,
+                      uword base,
+                      uword prologue_offset,
+                      uword size,
+                      bool optimized,
+                      const CodeComments* comments) {
+    return delegate_->on_new_code(delegate_, name, base, size);
+  }
+
+ private:
+  Dart_CodeObserver* delegate_;
+};
+
+void CodeObservers::RegisterExternal(Dart_CodeObserver* observer) {
+  if (observer != nullptr) Register(new ExternalCodeObserverAdapter(observer));
+}
+
 void CodeObservers::Register(CodeObserver* observer) {
   observers_length_++;
   observers_ = reinterpret_cast<CodeObserver**>(
diff --git a/runtime/vm/code_observers.h b/runtime/vm/code_observers.h
index aa6221d..3664a02 100644
--- a/runtime/vm/code_observers.h
+++ b/runtime/vm/code_observers.h
@@ -8,6 +8,8 @@
 #include "vm/allocation.h"
 #include "vm/globals.h"
 
+#include "include/dart_api.h"
+
 namespace dart {
 
 #ifndef PRODUCT
@@ -55,6 +57,8 @@
  public:
   static void Init();
 
+  static void RegisterExternal(Dart_CodeObserver* observer);
+
   static void Register(CodeObserver* observer);
 
   // Notify all active code observers about a newly created code object.
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index c0543df..e43381d 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -140,7 +140,8 @@
                  Dart_FileCloseCallback file_close,
                  Dart_EntropySource entropy_source,
                  Dart_GetVMServiceAssetsArchive get_service_assets,
-                 bool start_kernel_isolate) {
+                 bool start_kernel_isolate,
+                 Dart_CodeObserver* observer) {
   CheckOffsets();
   // TODO(iposva): Fix race condition here.
   if (vm_isolate_ != NULL || !Flags::Initialized()) {
@@ -194,6 +195,7 @@
   set_entropy_source_callback(entropy_source);
   OS::Init();
   NOT_IN_PRODUCT(CodeObservers::Init());
+  NOT_IN_PRODUCT(CodeObservers::RegisterExternal(observer));
   start_time_micros_ = OS::GetCurrentMonotonicMicros();
   VirtualMemory::Init();
   OSThread::Init();
diff --git a/runtime/vm/dart.h b/runtime/vm/dart.h
index aba4a28..64d10f9 100644
--- a/runtime/vm/dart.h
+++ b/runtime/vm/dart.h
@@ -38,7 +38,8 @@
                     Dart_FileCloseCallback file_close,
                     Dart_EntropySource entropy_source,
                     Dart_GetVMServiceAssetsArchive get_service_assets,
-                    bool start_kernel_isolate);
+                    bool start_kernel_isolate,
+                    Dart_CodeObserver* observer);
 
   // Returns null if cleanup succeeds, otherwise returns an error message
   // (caller owns error message and has to free it).
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 5e84bd0..caa2de9 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1009,7 +1009,7 @@
                     params->thread_exit, params->file_open, params->file_read,
                     params->file_write, params->file_close,
                     params->entropy_source, params->get_service_assets,
-                    params->start_kernel_isolate);
+                    params->start_kernel_isolate, params->code_observer);
 }
 
 DART_EXPORT char* Dart_Cleanup() {
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 4841f0d..d00f89b 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -57,6 +57,32 @@
   EXPECT(Dart_Cleanup() == NULL);
 }
 
+UNIT_TEST_CASE(DartAPI_DartInitializeCallsCodeObserver) {
+  EXPECT(Dart_SetVMFlags(TesterState::argc, TesterState::argv) == NULL);
+  Dart_InitializeParams params;
+  memset(&params, 0, sizeof(Dart_InitializeParams));
+  params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
+  params.vm_snapshot_data = TesterState::vm_snapshot_data;
+  params.create = TesterState::create_callback;
+  params.shutdown = TesterState::shutdown_callback;
+  params.cleanup = TesterState::cleanup_callback;
+  params.start_kernel_isolate = true;
+
+  bool was_called = false;
+  Dart_CodeObserver code_observer;
+  code_observer.data = &was_called;
+  code_observer.on_new_code = [](Dart_CodeObserver* observer, const char* name,
+                                 uintptr_t base, uintptr_t size) {
+    *static_cast<bool*>(observer->data) = true;
+  };
+  params.code_observer = &code_observer;
+
+  // Reinitialize and ensure we can execute Dart code.
+  EXPECT(Dart_Initialize(&params) == NULL);
+  EXPECT(was_called);
+  EXPECT(Dart_Cleanup() == NULL);
+}
+
 TEST_CASE(DartAPI_ErrorHandleBasics) {
   const char* kScriptChars =
       "void testMain() {\n"