about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-06-03 20:28:29 +0200
committerbptato <nincsnevem662@gmail.com>2024-06-03 20:28:29 +0200
commitdfb03387691e72f8832734255765ddfdd38db372 (patch)
tree31f2c489859aa8bd7d7bc2b3b1854f11708b45f2
parent34b0abd1a5811afa88b16442c0dc327881456d8f (diff)
downloadchawan-dfb03387691e72f8832734255765ddfdd38db372.tar.gz
js: fix runtime cleanup
This is a minefield.

Intuitively, you would think that just clearing the opaque and manually
freeing registered object should be enough. Unfortunately, this is
not true; we do not store whether we are actually holding a reference to
registered JS objects, so this approach leads to double frees.

Instead, we add a QJS callback that is called right after the final
GC cleanup, but before the list_free assertion. This way, we can be sure
that any object still in our registry is referenced by us, and can
therefore unreference them safely.
-rw-r--r--lib/quickjs/quickjs.c8
-rw-r--r--lib/quickjs/quickjs.h3
-rw-r--r--src/bindings/quickjs.nim4
-rw-r--r--src/js/javascript.nim47
4 files changed, 44 insertions, 18 deletions
diff --git a/lib/quickjs/quickjs.c b/lib/quickjs/quickjs.c
index af7ea852..427b36df 100644
--- a/lib/quickjs/quickjs.c
+++ b/lib/quickjs/quickjs.c
@@ -306,6 +306,7 @@ struct JSRuntime {
     uint32_t operator_count;
 #endif
     void *user_opaque;
+    JSRuntimeCleanUpFunc *user_cleanup;
 };
 
 struct JSClass {
@@ -1703,6 +1704,11 @@ void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque)
     rt->user_opaque = opaque;
 }
 
+void JS_SetRuntimeCleanUpFunc(JSRuntime *rt, JSRuntimeCleanUpFunc cleanup_func)
+{
+    rt->user_cleanup = cleanup_func;
+}
+
 /* default memory allocation functions with memory limitation */
 static size_t js_def_malloc_usable_size(const void *ptr)
 {
@@ -1960,6 +1966,8 @@ void JS_FreeRuntime(JSRuntime *rt)
 
     JS_RunGC(rt);
 
+    rt->user_cleanup(rt);
+
 #ifdef DUMP_LEAKS
     /* leaking objects */
     {
diff --git a/lib/quickjs/quickjs.h b/lib/quickjs/quickjs.h
index 8c81be47..366f341b 100644
--- a/lib/quickjs/quickjs.h
+++ b/lib/quickjs/quickjs.h
@@ -331,6 +331,8 @@ typedef struct JSMallocFunctions {
 
 typedef struct JSGCObjectHeader JSGCObjectHeader;
 
+typedef void JSRuntimeCleanUpFunc(JSRuntime *rt);
+
 JSRuntime *JS_NewRuntime(void);
 /* info lifetime must exceed that of rt */
 void JS_SetRuntimeInfo(JSRuntime *rt, const char *info);
@@ -345,6 +347,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque);
 void JS_FreeRuntime(JSRuntime *rt);
 void *JS_GetRuntimeOpaque(JSRuntime *rt);
 void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque);
+void JS_SetRuntimeCleanUpFunc(JSRuntime *rt, JSRuntimeCleanUpFunc cleanup_func);
 typedef void JS_MarkFunc(JSRuntime *rt, JSGCObjectHeader *gp);
 void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func);
 void JS_RunGC(JSRuntime *rt);
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index 836bedac..20dd048b 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -132,6 +132,8 @@ type
   JSClassExoticMethodsConst* {.importc: "const JSClassExoticMethods *",
     header: qjsheader.} = ptr JSClassExoticMethods
 
+  JSRuntimeCleanUpFunc* {.importc.} = proc(rt: JSRuntime) {.cdecl.}
+
   JSClassDef* {.importc, header: qjsheader.} = object
     class_name*: cstring
     finalizer*: JSClassFinalizer
@@ -555,6 +557,8 @@ proc JS_ExecutePendingJob*(rt: JSRuntime; pctx: ptr JSContext): cint
 
 proc JS_GetRuntimeOpaque*(rt: JSRuntime): pointer
 proc JS_SetRuntimeOpaque*(rt: JSRuntime; p: pointer)
+proc JS_SetRuntimeCleanUpFunc*(rt: JSRuntime;
+  cleanup_func: JSRuntimeCleanUpFunc)
 
 proc JS_SetContextOpaque*(ctx: JSContext; opaque: pointer)
 proc JS_GetContextOpaque*(ctx: JSContext): pointer
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index e2e91a4f..39330361 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -121,6 +121,27 @@ proc bindRealloc(s: ptr JSMallocState; p: pointer; size: csize_t): pointer
     {.cdecl.} =
   return realloc(p, size)
 
+proc jsRuntimeCleanUp(rt: JSRuntime) {.cdecl.} =
+  let rtOpaque = rt.getOpaque()
+  GC_unref(rtOpaque)
+  assert rtOpaque.destroying == nil
+  var ps: seq[pointer] = @[]
+  for p in rtOpaque.plist.values:
+    ps.add(p)
+  rtOpaque.plist.clear()
+  var unrefs: seq[proc() {.closure.}] = @[]
+  for (_, unref) in rtOpaque.refmap.values:
+    unrefs.add(unref)
+  rtOpaque.refmap.clear()
+  for unref in unrefs:
+    unref()
+  for p in ps:
+    #TODO maybe finalize?
+    let val = JS_MKPTR(JS_TAG_OBJECT, p)
+    JS_SetOpaque(val, nil)
+    JS_FreeValueRT(rt, val)
+  JS_RunGC(rt)
+
 proc newJSRuntime*(): JSRuntime =
   var mf {.global.} = JSMallocFunctions(
     js_malloc: bindMalloc,
@@ -132,6 +153,7 @@ proc newJSRuntime*(): JSRuntime =
   let opaque = JSRuntimeOpaque()
   GC_ref(opaque)
   JS_SetRuntimeOpaque(rt, cast[pointer](opaque))
+  JS_SetRuntimeCleanUpFunc(rt, jsRuntimeCleanUp)
   # Must be added after opaque is set, or there is a chance of
   # nim_finalize_for_js dereferencing it (at the new call).
   runtimes.add(rt)
@@ -170,17 +192,6 @@ proc free*(ctx: JSContext) =
   JS_FreeContext(ctx)
 
 proc free*(rt: JSRuntime) =
-  let opaque = rt.getOpaque()
-  GC_unref(opaque)
-  var ps: seq[pointer] = @[]
-  for p in opaque.plist.values:
-    ps.add(p)
-  opaque.plist.clear()
-  for p in ps:
-    #TODO maybe finalize?
-    let val = JS_MKPTR(JS_TAG_OBJECT, p)
-    JS_SetOpaque(val, nil)
-    JS_FreeValueRT(rt, val)
   JS_FreeRuntime(rt)
   runtimes.del(runtimes.find(rt))
 
@@ -1503,8 +1514,8 @@ proc bindGetSet(stmts: NimNode; info: RegistryInfo) =
         JS_CGETSET_DEF_NOCONF(`k`, `get`, `set`))
 
 proc bindExtraGetSet(stmts: NimNode; info: var RegistryInfo;
-    extra_getset: openArray[TabGetSet]) =
-  for x in extra_getset:
+    extraGetSet: openArray[TabGetSet]) =
+  for x in extraGetSet:
     let k = x.name
     let g = x.get
     let s = x.set
@@ -1604,8 +1615,8 @@ proc bindEndStmts(endstmts: NimNode; info: RegistryInfo) =
 macro registerType*(ctx: JSContext; t: typed; parent: JSClassID = 0;
     asglobal: static bool = false; globalparent: static bool = false;
     nointerface = false; name: static string = "";
-    has_extra_getset: static bool = false;
-    extra_getset: static openArray[TabGetSet] = []; namespace = JS_NULL;
+    hasExtraGetSet: static bool = false;
+    extraGetSet: static openArray[TabGetSet] = []; namespace = JS_NULL;
     errid = opt(JSErrorEnum); ishtmldda = false): JSClassID =
   var stmts = newStmtList()
   var info = newRegistryInfo(t, name)
@@ -1622,11 +1633,11 @@ macro registerType*(ctx: JSContext; t: typed; parent: JSClassID = 0;
   stmts.registerSetters(info, pragmas.jsset)
   stmts.bindFunctions(info)
   stmts.bindGetSet(info)
-  if has_extra_getset:
-    #HACK: for some reason, extra_getset gets weird contents when nothing is
+  if hasExtraGetSet:
+    #HACK: for some reason, extraGetSet gets weird contents when nothing is
     # passed to it. So we need an extra flag to signal if anything has
     # been passed to it at all.
-    stmts.bindExtraGetSet(info, extra_getset)
+    stmts.bindExtraGetSet(info, extraGetSet)
   let sctr = stmts.bindConstructor(info)
   if not asglobal:
     stmts.bindCheckDestroy(info)
cb16f'>^
4690ce81 ^
76755b28 ^
a654e4ec ^

76755b28 ^
4690ce81 ^
672e3e50 ^
4690ce81 ^

76755b28 ^
d5d908dd ^
672e3e50 ^


db1f56c8 ^
672e3e50 ^
32b8fac2 ^
672e3e50 ^

4690ce81 ^
76755b28 ^
4690ce81 ^
672e3e50 ^
4690ce81 ^
a654e4ec ^
65361948 ^
a654e4ec ^
65361948 ^
672e3e50 ^
a654e4ec ^
65361948 ^
672e3e50 ^




4690ce81 ^

d5d908dd ^
4690ce81 ^
c5ffb6e1 ^
672e3e50 ^
4690ce81 ^





672e3e50 ^


a654e4ec ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123


                                                                                          
                                   
                                                
                                  
                                                                                         
                       
                                                                                                 
                                                                                            
                                      
                             
                            


                                                                           
        




                               
       
                         


                                                                                                           








                                                                                                         
                                                                      
                                                                                 
                                                                                    
                                                              
                                                                                                                                                                                                  


                                                                    
                                                            



                                                                                   
                                                

                                              
                                      
                                                                                                                
                                                                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                                                                                
                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                 
                                                                                                                                                                                                                                                                     
                                                          
                                                                                                                                                                                                                                                                                                                                         

                                                                                            
                                                                                           
                                                                                                                                   
                                                   
                                
                                                                                                                     
                                                                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                               
                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                          
                                      
                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                       



                                      
                                                                                                                                                                                                                    
                                                                
                                                                                                                                                                                                                                                         
                                                                                                                                                                                             
                                




                                                                                                                                                      

       
                                     
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - 012transform.cc</title>
<meta name="Generator" content="Vim/7.4">
<meta name="plugin-version" content="vim7.4_v2">
<meta name="syntax" content="cpp">
<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
<meta name="colorscheme" content="minimal">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color: #080808; }
* { font-size: 12pt; font-size: 1em; }
.Constant { color: #00a0a0; }
.Comment { color: #9090ff; }
.Delimiter { color: #800080; }
.CommentedCode { color: #6c6c6c; }
.Normal { color: #eeeeee; background-color: #080808; padding-bottom: 1px; }
.Identifier { color: #fcb165; }
-->
</style>

<script type='text/javascript'>
<!--

-->
</script>
</head>
<body>
<pre id='vimCodeElement'>
<span class="Comment">//: Phase 2: Filter loaded recipes through an extensible list of 'transforms'.</span>
<span class="Comment">//:</span>
<span class="Comment">//: The hope is that this framework of transform tools will provide a</span>
<span class="Comment">//: deconstructed alternative to conventional compilers.</span>
<span class="Comment">//:</span>
<span class="Comment">//: We're going to have many transforms in mu, and getting their order right</span>
<span class="Comment">//: (not the same as ordering of layers) is a well-known problem. Some tips:</span>
<span class="Comment">//:   a) Design each layer to rely on as few previous layers as possible.</span>
<span class="Comment">//:</span>
<span class="Comment">//:   b) When positioning transforms, try to find the tightest constraint in</span>
<span class="Comment">//:   each transform relative to previous layers.</span>
<span class="Comment">//:</span>
<span class="Comment">//:   c) Even so you'll periodically need to try adjusting each transform</span>
<span class="Comment">//:   relative to those in previous layers to find a better arrangement.</span>

<span class="Delimiter">:(before &quot;End recipe Fields&quot;)</span>
<span class="Normal">int</span> transformed_until<span class="Delimiter">;</span>
<span class="Delimiter">:(before &quot;End recipe Constructor&quot;)</span>
transformed_until = -<span class="Constant">1</span><span class="Delimiter">;</span>

<span class="Delimiter">:(before &quot;End Types&quot;)</span>
<span class="Normal">typedef</span> <span class="Normal">void</span> <span class="Delimiter">(</span>*transform_fn<span class="Delimiter">)(</span>recipe_ordinal<span class="Delimiter">);</span>

<span class="Delimiter">:(before &quot;End Globals&quot;)</span>
vector&lt;transform_fn&gt; Transform<span class="Delimiter">;</span>

<span class="Delimiter">:(after &quot;int main&quot;)</span>
  <span class="Comment">// Begin Transforms</span>
    <span class="Comment">// Begin Instruction Inserting/Deleting Transforms</span>
    <span class="Comment">// End Instruction Inserting/Deleting Transforms</span>

    <span class="Comment">// Begin Instruction Modifying Transforms</span>
    <span class="Comment">// End Instruction Modifying Transforms</span>
  <span class="Comment">// End Transforms</span>

  <span class="Comment">// Begin Checks</span>
  <span class="Comment">// End Checks</span>

<span class="Delimiter">:(code)</span>
<span class="Normal">void</span> transform_all<span class="Delimiter">()</span> <span class="Delimiter">{</span>
  trace<span class="Delimiter">(</span><span class="Constant">9990</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;=== transform_all()&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
<span class="CommentedCode">//?   cerr &lt;&lt; &quot;=== transform_all\n&quot;;</span>
  <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> t = <span class="Constant">0</span><span class="Delimiter">;</span> t &lt; SIZE<span class="Delimiter">(</span>Transform<span class="Delimiter">);</span> ++t<span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span class="CommentedCode">//?     cerr &lt;&lt; &quot;transform &quot; &lt;&lt; t &lt;&lt; '\n';</span>
    <span class="Normal">for</span> <span class="Delimiter">(</span>map&lt;recipe_ordinal<span class="Delimiter">,</span> recipe&gt;::iterator p = Recipe<span class="Delimiter">.</span>begin<span class="Delimiter">();</span> p != Recipe<span class="Delimiter">.</span>end<span class="Delimiter">();</span> ++p<span class="Delimiter">)</span> <span class="Delimiter">{</span>
      recipe&amp; r = p<span class="Delimiter">-&gt;</span>second<span class="Delimiter">;</span>
      <span class="Normal">if</span> <span class="Delimiter">(</span>r<span class="Delimiter">.</span>steps<span class="Delimiter">.</span>empty<span class="Delimiter">())</span> <span class="Identifier">continue</span><span class="Delimiter">;</span>
      <span class="Normal">if</span> <span class="Delimiter">(</span>r<span class="Delimiter">.</span>transformed_until != t-<span class="Constant">1</span><span class="Delimiter">)</span> <span class="Identifier">continue</span><span class="Delimiter">;</span>
      <span class="Comment">// End Transform Checks</span>
      <span class="Delimiter">(</span>*Transform<span class="Delimiter">.</span>at<span class="Delimiter">(</span>t<span class="Delimiter">))(</span><span class="Comment">/*</span><span class="Comment">recipe_ordinal</span><span class="Comment">*/</span>p<span class="Delimiter">-&gt;</span>first<span class="Delimiter">);</span>
      r<span class="Delimiter">.</span>transformed_until = t<span class="Delimiter">;</span>
    <span class="Delimiter">}</span>
  <span class="Delimiter">}</span>
<span class="CommentedCode">//?   cerr &lt;&lt; &quot;wrapping up transform\n&quot;;</span>
  parse_int_reagents<span class="Delimiter">();</span>  <span class="Comment">// do this after all other transforms have run</span>
  <span class="Comment">// End transform_all</span>
<span class="Delimiter">}</span>

<span class="Normal">void</span> parse_int_reagents<span class="Delimiter">()</span> <span class="Delimiter">{</span>
  trace<span class="Delimiter">(</span><span class="Constant">9991</span><span class="Delimiter">,</span> <span class="Constant">&quot;transform&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;--- parsing any uninitialized reagents as integers&quot;</span> &lt;&lt; end<span class="Delimiter">();</span>
  <span class="Normal">for</span> <span class="Delimiter">(</span>map&lt;recipe_ordinal<span class="Delimiter">,</span> recipe&gt;::iterator p = Recipe<span class="Delimiter">.</span>begin<span class="Delimiter">();</span> p != Recipe<span class="Delimiter">.</span>end<span class="Delimiter">();</span> ++p<span class="Delimiter">)</span> <span class="Delimiter">{</span>
    recipe&amp; r = p<span class="Delimiter">-&gt;</span>second<span class="Delimiter">;</span>
    <span class="Normal">if</span> <span class="Delimiter">(</span>r<span class="Delimiter">.</span>steps<span class="Delimiter">.</span>empty<span class="Delimiter">())</span> <span class="Identifier">continue</span><span class="Delimiter">;</span>
    <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> index = <span class="Constant">0</span><span class="Delimiter">;</span> index &lt; SIZE<span class="Delimiter">(</span>r<span class="Delimiter">.</span>steps<span class="Delimiter">);</span> ++index<span class="Delimiter">)</span> <span class="Delimiter">{</span>
      instruction&amp; inst = r<span class="Delimiter">.</span>steps<span class="Delimiter">.</span>at<span class="Delimiter">(</span>index<span class="Delimiter">);</span>
      <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span> i &lt; SIZE<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">);</span> ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
        populate_value<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">));</span>
      <span class="Delimiter">}</span>
      <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span> i &lt; SIZE<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">);</span> ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
        populate_value<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">));</span>
      <span class="Delimiter">}</span>
    <span class="Delimiter">}</span>
  <span class="Delimiter">}</span>
<span class="Delimiter">}</span>

<span class="Normal">void</span> populate_value<span class="Delimiter">(</span>reagent&amp; r<span class="Delimiter">)</span> <span class="Delimiter">{</span>
  <span class="Normal">if</span> <span class="Delimiter">(</span>r<span class="Delimiter">.</span>initialized<span class="Delimiter">)</span> <span class="Identifier">return</span><span class="Delimiter">;</span>
  <span class="Comment">// End Reagent-parsing Exceptions</span>
  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_integer<span class="Delimiter">(</span>r<span class="Delimiter">.</span>name<span class="Delimiter">))</span> <span class="Identifier">return</span><span class="Delimiter">;</span>
  r<span class="Delimiter">.</span>set_value<span class="Delimiter">(</span>to_integer<span class="Delimiter">(</span>r<span class="Delimiter">.</span>name<span class="Delimiter">));</span>
<span class="Delimiter">}</span>

<span class="Comment">// helper for tests -- temporarily suppress run</span>
<span class="Normal">void</span> transform<span class="Delimiter">(</span>string form<span class="Delimiter">)</span> <span class="Delimiter">{</span>
  load<span class="Delimiter">(</span>form<span class="Delimiter">);</span>
  transform_all<span class="Delimiter">();</span>
<span class="Delimiter">}</span>
</pre>
</body>
</html>
<!-- vim: set foldmethod=manual : -->