Skip to content

Commit f0a2834

Browse files
committed
Implement handle scope API in jerry-ext
JerryScript-DCO-1.0-Signed-off-by: legendecas legendecas@gmail.com
1 parent f22eea5 commit f0a2834

13 files changed

+1521
-0
lines changed

docs/14.EXT-REFERENCE-HANDLE-SCOPE.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Handle Scope
2+
3+
## jerryx_handle_scope
4+
5+
**Summary**
6+
It is often necessary to make the lifespan of handles shorter than the lifespan of a native method. Even though the native code could only use the most recent handle, all of the associated objects would also be kept alive since they all share the same scope.
7+
8+
To handle this case, JerryScript HandleScope extension provides the ability to establish a new 'scope' to which newly created handles will be associated. Once those handles are no longer required, the scope can be 'closed' and any handles associated with the scope are invalidated. The methods available to open/close scopes are `jerryx_open_handle_scope` and `jerryx_close_handle_scope`.
9+
10+
JerryScript only supports a single nested hierarchy of scopes. There is only one active scope at any time, and all new handles will be associated with that scope while it is active. Scopes must be closed in the reverse order from which they are opened. In addition, all scopes created within a native method must be closed before returning from that method.
11+
12+
**Example**
13+
14+
[doctest]: # (test="compile")
15+
16+
```c
17+
#include "jerryscript.h"
18+
#include "jerryscript-ext/handle-scope.h"
19+
20+
static jerry_value_t
21+
create_object (void)
22+
{
23+
jerry_value_t obj = jerry_create_object ();
24+
return obj;
25+
} /* create_object */
26+
27+
static void
28+
test_handle_scope_val (void)
29+
{
30+
jerryx_handle_scope scope;
31+
jerryx_open_handle_scope (&scope);
32+
jerry_value_t obj = jerryx_create_handle (create_object ());
33+
34+
jerryx_close_handle_scope (scope);
35+
// now obj has been released
36+
} /* test_handle_scope_val */
37+
38+
int
39+
main (void)
40+
{
41+
jerry_init (JERRY_INIT_EMPTY);
42+
43+
test_handle_scope_val ();
44+
jerry_gc (JERRY_GC_SEVERITY_LOW);
45+
46+
jerry_cleanup ();
47+
} /* main */
48+
```
49+
50+
## jerryx_escapable_handle_scope
51+
52+
**Summary**
53+
54+
It is necessary in common cases that a handle has to be promote to outer scope and prevent from been garbage collected. To handle this case, a escapable handle scope has been proposed from which one object can be promoted to the outer scope. The method available to escape an object from been release at current scope is `jerryx_escape_handle`.
55+
56+
**Example**
57+
58+
[doctest]: # (test="compile")
59+
60+
```c
61+
#include "jerryscript.h"
62+
#include "jerryscript-ext/handle-scope.h"
63+
64+
static jerry_value_t
65+
create_object (void)
66+
{
67+
jerryx_escapable_handle_scope scope;
68+
jerryx_open_escapable_handle_scope (&scope);
69+
jerry_value_t obj = jerryx_create_handle (jerry_create_object ());
70+
71+
jerry_value_t escaped_obj;
72+
jerryx_escape_handle(scope, obj, &escaped_obj);
73+
jerryx_close_handle_scope (scope);
74+
// escaped_obj has now been escaped to outer scope, thus not released at this point
75+
76+
return escaped_obj;
77+
} /* create_object */
78+
79+
static void
80+
test_handle_scope_val (void)
81+
{
82+
jerryx_handle_scope scope;
83+
jerryx_open_handle_scope (&scope);
84+
jerry_value_t obj = create_object ();
85+
86+
jerryx_close_handle_scope (scope);
87+
// now obj has been released
88+
} /* test_handle_scope_val */
89+
90+
int
91+
main (void)
92+
{
93+
jerry_init (JERRY_INIT_EMPTY);
94+
95+
test_handle_scope_val ();
96+
jerry_gc (JERRY_GC_SEVERITY_LOW);
97+
98+
jerry_cleanup ();
99+
} /* main */
100+
```
101+
102+
**See also**
103+
104+
- [jerry_value_t](../docs/02.API-REFERENCE.md#jerry_value_t)
105+
- [jerry_acquire_value](../docs/02.API-REFERENCE.md#jerry_acquire_value)
106+
- [jerry_release_value](../docs/02.API-REFERENCE.md#jerry_release_value)
107+
108+
## Pre-allocated list of handle scopes and handles
109+
110+
To prevent trapping into system calls frequently, a pre-allocated dedicated list mechanism has been introduced to the implementation of JerryX handle scope.
111+
112+
To change the size of pre-allocation list, use build definition `JERRYX_HANDLE_PRELIST_SIZE` and `JERRYX_SCOPE_PRELIST_SIZE` to alter the default value of 20.

jerry-ext/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ file(GLOB SOURCE_EXT
3737
arg/*.c
3838
common/*.c
3939
debugger/*.c
40+
handle-scope/*.c
4041
handler/*.c
4142
module/*.c)
4243

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
#include <stdlib.h>
16+
#include "handle-scope-internal.h"
17+
18+
static jerryx_handle_scope_t kJerryXHandleScopeRoot =
19+
{
20+
.handle_count = 0,
21+
.handle_ptr = NULL,
22+
};
23+
static jerryx_handle_scope_t *kJerryXHandleScopeCurrent = &kJerryXHandleScopeRoot;
24+
static jerryx_handle_scope_pool_t kJerryXHandleScopePool =
25+
{
26+
.count = 0,
27+
.start = NULL,
28+
};
29+
30+
#define kJerryXHandleScopePoolPrelistLast \
31+
kJerryXHandleScopePool.prelist + JERRYX_SCOPE_PRELIST_SIZE - 1
32+
33+
#define JerryXHandleScopePrelistIdx(scope) (scope - kJerryXHandleScopePool.prelist)
34+
35+
36+
/**
37+
* Get current handle scope top of stack.
38+
*/
39+
inline
40+
jerryx_handle_scope_t *
41+
jerryx_handle_scope_get_current (void)
42+
{
43+
return kJerryXHandleScopeCurrent;
44+
} /* jerryx_handle_scope_get_current */
45+
46+
47+
/**
48+
* Get root handle scope.
49+
*/
50+
inline
51+
jerryx_handle_scope_t *
52+
jerryx_handle_scope_get_root (void)
53+
{
54+
return &kJerryXHandleScopeRoot;
55+
} /* jerryx_handle_scope_get_root */
56+
57+
58+
/**
59+
* Determines if given handle scope is located in pre-allocated list.
60+
*
61+
* @param scope - the one to be determined.
62+
*/
63+
static
64+
inline
65+
bool
66+
jerryx_handle_scope_is_in_prelist (jerryx_handle_scope_t *scope)
67+
{
68+
return (kJerryXHandleScopePool.prelist <= scope)
69+
&& (scope <= (kJerryXHandleScopePool.prelist + JERRYX_SCOPE_PRELIST_SIZE - 1));
70+
} /** jerryx_handle_scope_is_in_prelist */
71+
72+
73+
/**
74+
* Get the parent of given handle scope.
75+
* If given handle scope is in prelist, the parent must be in prelist too;
76+
* if given is the first item of heap chain list, the parent must be the last one of prelist;
77+
* the parent must be in chain list otherwise.
78+
*
79+
* @param scope - the one to be permformed on.
80+
* @returns - the parent of the given scope.
81+
*/
82+
jerryx_handle_scope_t *
83+
jerryx_handle_scope_get_parent (jerryx_handle_scope_t *scope)
84+
{
85+
if (scope == &kJerryXHandleScopeRoot)
86+
{
87+
return NULL;
88+
}
89+
if (!jerryx_handle_scope_is_in_prelist (scope))
90+
{
91+
jerryx_handle_scope_dynamic_t *dy_scope = (jerryx_handle_scope_dynamic_t *) scope;
92+
if (dy_scope == kJerryXHandleScopePool.start)
93+
{
94+
return kJerryXHandleScopePoolPrelistLast;
95+
}
96+
jerryx_handle_scope_dynamic_t *parent = dy_scope->parent;
97+
return (jerryx_handle_scope_t *) parent;
98+
}
99+
if (scope == kJerryXHandleScopePool.prelist)
100+
{
101+
return &kJerryXHandleScopeRoot;
102+
}
103+
return kJerryXHandleScopePool.prelist + JerryXHandleScopePrelistIdx (scope) - 1;
104+
} /** jerryx_handle_scope_get_parent */
105+
106+
107+
/**
108+
* Get the child of given handle scope.
109+
* If the given handle scope is in heap chain list, its child must be in heap chain list too;
110+
* if the given handle scope is the last one of prelist, its child must be the first item of chain list;
111+
* the children are in prelist otherwise.
112+
*
113+
* @param scope - the one to be permformed on.
114+
* @returns the child of the given scope.
115+
*/
116+
jerryx_handle_scope_t *
117+
jerryx_handle_scope_get_child (jerryx_handle_scope_t *scope)
118+
{
119+
if (scope == &kJerryXHandleScopeRoot)
120+
{
121+
if (kJerryXHandleScopePool.count > 0)
122+
{
123+
return kJerryXHandleScopePool.prelist;
124+
}
125+
return NULL;
126+
}
127+
if (!jerryx_handle_scope_is_in_prelist (scope))
128+
{
129+
jerryx_handle_scope_dynamic_t *child = ((jerryx_handle_scope_dynamic_t *) scope)->child;
130+
return (jerryx_handle_scope_t *) child;
131+
}
132+
if (scope == kJerryXHandleScopePoolPrelistLast)
133+
{
134+
return (jerryx_handle_scope_t *) kJerryXHandleScopePool.start;
135+
}
136+
long idx = JerryXHandleScopePrelistIdx (scope);
137+
if (idx < 0)
138+
{
139+
return NULL;
140+
}
141+
if ((unsigned long) idx >= kJerryXHandleScopePool.count - 1)
142+
{
143+
return NULL;
144+
}
145+
return kJerryXHandleScopePool.prelist + idx + 1;
146+
} /** jerryx_handle_scope_get_child */
147+
148+
149+
/**
150+
* Claims a handle scope either from prelist or allocating a new memory block,
151+
* and increment pool's scope count by 1, and set current scope to the newly claimed one.
152+
* If there are still available spaces in prelist, claims a block in prelist;
153+
* otherwise allocates a new memory block from heap and sets its fields to default values,
154+
* and link it to previously dynamically allocated scope, or link it to pool's start pointer.
155+
*
156+
* @returns the newly claimed handle scope pointer.
157+
*/
158+
jerryx_handle_scope_t *
159+
jerryx_handle_scope_alloc (void)
160+
{
161+
jerryx_handle_scope_t *scope;
162+
if (kJerryXHandleScopePool.count < JERRYX_SCOPE_PRELIST_SIZE)
163+
{
164+
scope = kJerryXHandleScopePool.prelist + kJerryXHandleScopePool.count;
165+
goto deferred;
166+
}
167+
168+
do
169+
{
170+
jerryx_handle_scope_dynamic_t *dy_scope = malloc (sizeof (jerryx_handle_scope_dynamic_t));
171+
JERRYX_HANDLE_SCOPE_ASSERT (dy_scope != NULL);
172+
dy_scope->child = NULL;
173+
174+
if (kJerryXHandleScopePool.count != JERRYX_SCOPE_PRELIST_SIZE)
175+
{
176+
jerryx_handle_scope_dynamic_t *dy_current = (jerryx_handle_scope_dynamic_t *) kJerryXHandleScopeCurrent;
177+
dy_scope->parent = dy_current;
178+
dy_current->child = dy_scope;
179+
}
180+
else
181+
{
182+
kJerryXHandleScopePool.start = dy_scope;
183+
dy_scope->parent = NULL;
184+
}
185+
186+
scope = (jerryx_handle_scope_t *) dy_scope;
187+
}
188+
while (0);
189+
190+
deferred:
191+
scope->handle_count = 0;
192+
scope->escaped = false;
193+
scope->handle_ptr = NULL;
194+
195+
kJerryXHandleScopeCurrent = scope;
196+
kJerryXHandleScopePool.count += 1;
197+
return (jerryx_handle_scope_t *) scope;
198+
} /** jerryx_handle_scope_alloc */
199+
200+
201+
/**
202+
* Deannounce a previously claimed handle scope, return it to pool
203+
* or free the allocated memory block.
204+
*
205+
* @param scope - the one to be freed.
206+
*/
207+
void
208+
jerryx_handle_scope_free (jerryx_handle_scope_t *scope)
209+
{
210+
if (scope == &kJerryXHandleScopeRoot)
211+
{
212+
return;
213+
}
214+
215+
kJerryXHandleScopePool.count -= 1;
216+
if (scope == kJerryXHandleScopeCurrent)
217+
{
218+
kJerryXHandleScopeCurrent = jerryx_handle_scope_get_parent (scope);
219+
}
220+
221+
if (!jerryx_handle_scope_is_in_prelist (scope))
222+
{
223+
jerryx_handle_scope_dynamic_t *dy_scope = (jerryx_handle_scope_dynamic_t *) scope;
224+
if (dy_scope == kJerryXHandleScopePool.start)
225+
{
226+
kJerryXHandleScopePool.start = dy_scope->child;
227+
}
228+
else if (dy_scope->parent != NULL)
229+
{
230+
dy_scope->parent->child = dy_scope->child;
231+
}
232+
free (dy_scope);
233+
return;
234+
}
235+
/**
236+
* Nothing to do with scopes in prelist
237+
*/
238+
} /** jerryx_handle_scope_free */

0 commit comments

Comments
 (0)