Skip to content

Commit 5198b95

Browse files
authored
Merge pull request #31 from ubolonton/call-unprotected
Allow selectively enabling no-GC-protection optimization
2 parents a0bdb6d + 80f8c1b commit 5198b95

File tree

11 files changed

+126
-22
lines changed

11 files changed

+126
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
## [Unreleased]
77
- Reworked `Vector`, making it iterable.
88
- Changed `Env::vector` to return a `Value`.
9+
- Added `call_unprotected` variants to `Value::call`, `Env::call`, `GlobalRef::call`, to enable certain optimizations.
910

1011
## [0.13.0] - 2020-03-11
1112
- Added `GlobalRef`, which allows keeping Lisp values around without an `Env`.

src/call.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,25 @@ impl<'e> Value<'e> {
3333
/// ```
3434
///
3535
/// [`IntoLisp`]: trait.IntoLisp.html
36+
#[inline]
3637
pub fn call<A>(self, args: A) -> Result<Value<'e>> where A: IntoLispArgs<'e> {
38+
// Safety: The returned value is explicitly protected.
39+
unsafe { self.call_unprotected(args).map(|v| v.protect()) }
40+
}
41+
42+
/// Like [`call`], except that the returned `Value` is not protected against
43+
/// Emacs GC's [bug #31238], which caused [issue #2].
44+
///
45+
/// # Safety
46+
///
47+
/// This can be used as an optimization, in situations when the returned `Value` is unused,
48+
/// or when its usage is shorter than the lifespan of the underlying Lisp object.
49+
///
50+
/// [`call`]: #method.call
51+
/// [bug #31238]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31238
52+
/// [issue #2]: https://github.com/ubolonton/emacs-module-rs/issues/2
53+
#[allow(unused_unsafe)]
54+
pub unsafe fn call_unprotected<A>(self, args: A) -> Result<Value<'e>> where A: IntoLispArgs<'e> {
3755
let env = self.env;
3856
let mut lisp_args = args.into_lisp_args(env)?;
3957
let lisp_args: &mut [emacs_value] = lisp_args.borrow_mut();
@@ -42,7 +60,7 @@ impl<'e> Value<'e> {
4260
// Safety:
4361
// - ptr comes from a locally-owned value.
4462
// - length is ensured to be valid by IntoLispArgs implementation.
45-
unsafe_raw_call_value!(env, funcall, self.raw, length, ptr)
63+
unsafe_raw_call_value_unprotected!(env, funcall, self.raw, length, ptr)
4664
}
4765
}
4866

@@ -78,12 +96,33 @@ impl Env {
7896
/// [`IntoLisp`]: trait.IntoLisp.html
7997
#[inline]
8098
pub fn call<'e, F, A>(&'e self, func: F, args: A) -> Result<Value<'_>>
81-
where
82-
F: IntoLispCallable<'e>,
83-
A: IntoLispArgs<'e>,
99+
where
100+
F: IntoLispCallable<'e>,
101+
A: IntoLispArgs<'e>,
84102
{
85103
func.into_lisp_callable(self)?.call(args)
86104
}
105+
106+
/// Like [`call`], except that the returned [`Value`] is not protected against
107+
/// Emacs GC's [bug #31238], which caused [issue #2].
108+
///
109+
/// # Safety
110+
///
111+
/// This can be used as an optimization, in situations when the returned [`Value`] is unused,
112+
/// or when its usage is shorter than the lifespan of the underlying Lisp object.
113+
///
114+
/// [`call`]: #method.call
115+
/// [`Value`]: struct.Value.html
116+
/// [bug #31238]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31238
117+
/// [issue #2]: https://github.com/ubolonton/emacs-module-rs/issues/2
118+
#[inline]
119+
pub unsafe fn call_unprotected<'e, F, A>(&'e self, func: F, args: A) -> Result<Value<'_>>
120+
where
121+
F: IntoLispCallable<'e>,
122+
A: IntoLispArgs<'e>,
123+
{
124+
func.into_lisp_callable(self)?.call_unprotected(args)
125+
}
87126
}
88127

89128
impl GlobalRef {
@@ -101,6 +140,26 @@ impl GlobalRef {
101140
{
102141
self.bind(env).call(args)
103142
}
143+
144+
/// Like [`call`], except that the returned [`Value`] is not protected against
145+
/// Emacs GC's [bug #31238], which caused [issue #2].
146+
///
147+
/// # Safety
148+
///
149+
/// This can be used as an optimization, in situations when the returned [`Value`] is unused,
150+
/// or when its usage is shorter than the lifespan of the underlying Lisp object.
151+
///
152+
/// [`call`]: #method.call
153+
/// [`Value`]: struct.Value.html
154+
/// [bug #31238]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31238
155+
/// [issue #2]: https://github.com/ubolonton/emacs-module-rs/issues/2
156+
#[inline]
157+
pub unsafe fn call_unprotected<'e, A>(&'e self, env: &'e Env, args: A) -> Result<Value<'_>>
158+
where
159+
A: IntoLispArgs<'e>,
160+
{
161+
self.bind(env).call_unprotected(args)
162+
}
104163
}
105164

106165
// We can implement IntoLispArgs for IntoLisp types (after breaking up this implementation).

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ impl TempValue {
9595
///
9696
/// [`Env`]: struct.Env.html
9797
pub unsafe fn value<'e>(&self, env: &'e Env) -> Value<'e> {
98-
Value::new_protected(self.raw, env)
98+
Value::new(self.raw, env).protect()
9999
}
100100
}
101101

src/global.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ impl GlobalRef {
6363
/// [`Value`]: struct.Value.html
6464
#[inline]
6565
pub fn bind<'e, 'g: 'e>(&'g self, env: &'e Env) -> Value<'e> {
66+
// Safety: This global ref keeps the underlying Lisp object alive.
6667
unsafe { Value::new(self.raw, env) }
6768
}
6869

src/macros.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,33 @@ macro_rules! unsafe_raw_call {
3232
}
3333

3434
/// Calls a raw function that returns an emacs_value, then handles any pending non-local exit.
35+
/// Returns a [`Value`].
36+
///
37+
/// [`Value`]: struct.Value.html
3538
macro_rules! unsafe_raw_call_value {
39+
($env:expr, $name:ident $(, $args:expr)*) => {
40+
unsafe_raw_call_value_unprotected!($env, $name $(, $args)*).map(|v| v.protect())
41+
};
42+
}
43+
44+
/// Like [`unsafe_raw_call_value!`], except that the returned [`Value`] is not protected against
45+
/// Emacs GC's [bug #31238], which caused [issue #2].
46+
///
47+
/// # Safety
48+
///
49+
/// This can be used as an optimization, in situations when the returned [`Value`] is unused,
50+
/// or when its usage is shorter than the lifespan of the underlying Lisp object.
51+
///
52+
/// [`unsafe_raw_call_value!`]: macro.unsafe_raw_call_value.html
53+
/// [`Value`]: struct.Value.html
54+
/// [bug #31238]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31238
55+
/// [issue #2]: https://github.com/ubolonton/emacs-module-rs/issues/2
56+
macro_rules! unsafe_raw_call_value_unprotected {
3657
($env:expr, $name:ident $(, $args:expr)*) => {
3758
{
3859
let result: $crate::Result<$crate::raw::emacs_value> = unsafe_raw_call!($env, $name $(, $args)*);
3960
result.map(|raw| unsafe {
40-
// TODO: In some cases, we can get away without protection. Try optimizing them.
41-
$crate::Value::new_protected(raw, $env)
61+
$crate::Value::new(raw, $env)
4262
})
4363
}
4464
};

src/types/float.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ impl FromLisp<'_> for f64 {
88

99
impl IntoLisp<'_> for f64 {
1010
fn into_lisp(self, env: &Env) -> Result<Value<'_>> {
11-
unsafe_raw_call_value!(env, make_float, self)
11+
unsafe_raw_call_value_unprotected!(env, make_float, self)
1212
}
1313
}

src/types/integer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ int_from_lisp!(usize);
3939

4040
impl IntoLisp<'_> for i64 {
4141
fn into_lisp(self, env: &Env) -> Result<Value<'_>> {
42-
unsafe_raw_call_value!(env, make_integer, self)
42+
unsafe_raw_call_value_unprotected!(env, make_integer, self)
4343
}
4444
}
4545

src/types/vector.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ impl<'e> Vector<'e> {
4141
pub fn get<T: FromLisp<'e>>(&self, i: usize) -> Result<T> {
4242
let v = self.value;
4343
let env = v.env;
44-
// Safety: Same lifetime. Emacs does bound checking.
45-
unsafe_raw_call_value!(env, vec_get, v.raw, i as isize)?.into_rust()
44+
// Safety:
45+
// - Same lifetime.
46+
// - Emacs does bound checking.
47+
// - Value doesn't need protection because we are done with it while the vector still lives.
48+
unsafe_raw_call_value_unprotected!(env, vec_get, v.raw, i as isize)?.into_rust()
4649
}
4750

4851
pub fn set<T: IntoLisp<'e>>(&self, i: usize, value: T) -> Result<()> {

src/value.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,31 @@ impl<'e> Value<'e> {
2323
///
2424
/// # Safety
2525
///
26-
/// The raw value must not live longer than the given [`Env`].
26+
/// The raw value must be valid for as long as the returned `Value` is used. This usually means
27+
/// "as long as [`Env`] is alive", but can be shorter, depending on how the returned `Value` is
28+
/// used at call site.
2729
///
2830
/// [`Env`]: struct.Env.html
2931
#[doc(hidden)]
3032
pub unsafe fn new(raw: emacs_value, env: &'e Env) -> Self {
3133
Self { raw, env }
3234
}
3335

34-
/// Constructs a new `Value` and "roots" its underlying raw value (GC-managed) during the
35-
/// lifetime of the given [`Env`]. Module code should not call this directly. It is public only
36-
/// for some internal macros to use.
36+
/// Protects this value by registering with its [`Env`], effectively "rooting" the underlying
37+
/// Lisp object during the lifetime of the [`Env`].
3738
///
38-
/// # Safety
39-
///
40-
/// The raw value must still be alive. This function is needed to protect new values returned
41-
/// from Emacs runtime, due to [this issue](https://github.com/ubolonton/emacs-module-rs/issues/2).
39+
/// Module code should not call this directly. It is public only for certain internal macros and
40+
/// functions to work around Emacs GC's [bug #31238], which caused [issue #2].
4241
///
4342
/// [`Env`]: struct.Env.html
44-
#[allow(unused_unsafe)]
43+
/// [bug #31238]: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=31238
44+
/// [issue #2]: https://github.com/ubolonton/emacs-module-rs/issues/2
4545
#[doc(hidden)]
46-
pub unsafe fn new_protected(raw: emacs_value, env: &'e Env) -> Self {
46+
#[inline]
47+
pub fn protect(self) -> Self {
48+
let Self { env, raw } = self;
4749
env.protected.borrow_mut().push(unsafe_raw_call_no_exit!(env, make_global_ref, raw));
48-
Self::new(raw, env)
50+
self
4951
}
5052

5153
pub fn is_not_nil(&self) -> bool {

test-module/src/test_lifetime.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ fn gc_after_new_string(env: &Env) -> Result<Value<'_>> {
5757
}, print)
5858
}
5959

60+
// Primitive types supposedly have no issue.
61+
#[defun(mod_in_name = false)]
62+
fn gc_after_new_int(env: &Env) -> Result<Value<'_>> {
63+
create_collect_use(env, 2, || {
64+
5.into_lisp(env)
65+
}, print)
66+
}
67+
68+
// Primitive types supposedly have no issue.
69+
#[defun(mod_in_name = false)]
70+
fn gc_after_new_float(env: &Env) -> Result<Value<'_>> {
71+
create_collect_use(env, 2, || {
72+
5.8.into_lisp(env)
73+
}, print)
74+
}
75+
6076
// Before fixing:
6177
// - macOS: Segmentation fault
6278
// - Linux: Segmentation fault

0 commit comments

Comments
 (0)