Skip to content

Commit 892a972

Browse files
authored
Pointer: Add utility functions (#1012)
* Pointer: Add utility functions This commit adds a few more utility functions to `Pointer.carp`. - Pointer.set-unsafe: Sets the value of a pointer to some arbitrary Carp value, without type checking. Users need to ensure this operation is safe. - Pointer.set: Sets the value of a pointer to a value of type t to a value that has the same type. - Pointer.cast: Casts a pointer to a value of type t to a pointer to a value of type `a`--the argument passed is ignored, it is only used to determine the type to cast to. - Pointer.leak: Copies a Carp reference to a new pointer to the same value. This creates a leak since Carp will not automatically clean up this memory. - Pointer.free: Frees a pointer p. Users need to ensure calls to this function are safe and do not produce errors like a double free. Intended for use with leak. Here's an example of some of these functions in action: ``` (defn foo [] (let-do [p (Pointer.leak "leaky")] ;; create a new pointer (ignore (Pointer.set p @"foo")) ;; set the pointer to "foo" (println* (Pointer.to-value p)) ;; convert to a Carp val to print (Pointer.free p) ;; finally, free it. 0)) (foo) => Compiled to 'out/Untitled' (executable) foo 0 ``` And the C of interest: ``` int foo() { int _35; /* let */ { static String _6 = "leaky"; String *_6_ref = &_6; String* _7 = Pointer_leak__String(_6_ref); String* p = _7; /* let */ { static String _15 = "foo"; String *_15_ref = &_15; String _16 = String_copy(_15_ref); String* _17 = Pointer_set__String(p, _16); String* _ = _17; /* () */ } String _26 = Pointer_to_MINUS_value__String(p); String _27 = StringCopy_str(_26); String* _28 = &_27; // ref IO_println(_28); Pointer_free__String(p); int _34 = 0; _35 = _34; String_delete(_27); } return _35; } ``` As mentioned, and as w/ other Pointer functions users need to ensure the safety of these operations themselves. For example, calling `free` on `p` twice in the example above produces the expected double free: ``` (defn foo [] (let-do [p (Pointer.leak "leaky")] ;; create a new pointer (ignore (Pointer.set p @"foo")) ;; set the pointer to "foo" (println* (Pointer.to-value p)) ;; convert to a Carp val to print (Pointer.free p) ;; finally, free it. (Pointer.free p) ;; !Double free! 0)) (foo) Compiled to 'out/Untitled' (executable) foo Untitled(38328,0x10d9a1dc0) malloc: *** error for object 0x7feb86c01790: pointer being freed was not allocated Untitled(38328,0x10d9a1dc0) malloc: *** set a breakpoint in malloc_error_break to debug [RUNTIME ERROR] '"out/Untitled"' exited with return value -6. ``` Still, these should come in handy in rare cases in which users need to circumvent the type checker or borrow checker. diff --git a/core/Pointer.carp b/core/Pointer.carp index a662c63..4f29d58 100644 --- a/core/Pointer.carp +++ b/core/Pointer.carp @@ -20,6 +20,29 @@ The user will have to ensure themselves that this is a safe operation.") (doc from-long "converts a long integer to a pointer.") (deftemplate from-long (Fn [Long] (Ptr p)) "$p* $NAME(Long p)" " $DECL { return ($p*)p; }") + (doc set-unsafe + "Sets the value of a pointer." + "The user will have to ensure this operation is safe.") + (deftemplate set-unsafe (Fn [(Ptr p) (Ref a b)] (Ptr p)) "$p* $NAME($p* p, void* a)" "$DECL { *p = *($p*)a; return p;}") + + (doc cast + "Cast a pointer to type p to a pointer to type a." + "The value of the `a` argument is ignored.") + (deftemplate cast (Fn [(Ptr p) a] (Ptr a)) "$a* $NAME($p* p, $a a)" "$DECL { *($a*)p = CARP_MALLOC(sizeof($a)); return ($a*)p;}") + + (doc leak + "Allocate a new pointer that's a copy of the value of `Ref`" + "The Carp borrow checker won't delete this pointer. You will need to delete it manually by calling `Pointer.free`.") + (deftemplate leak (Fn [(Ref a b)] (Ptr a)) "$a* $NAME($a* r)" "$DECL { void *leak = CARP_MALLOC(sizeof($a)); memcpy(leak, r, sizeof($a)); return ($a*)leak;}") + + (doc free + "Free a pointer." + "Users need to manually verify that this operation is safe.") + (deftemplate free (Fn [(Ptr p)] Unit) "void $NAME($p* p)" "$DECL {CARP_FREE(p);}") + + (doc set "Sets the value of a pointer.") + (deftemplate set (Fn [(Ptr p) p] (Ptr p)) "$p* $NAME($p* p, $p a)" "$DECL { *p = a; return p;}") + (defn inc [a] (Pointer.add a 1l)) (implements inc Pointer.inc) (defn dec [a] (Pointer.sub a 1l)) * Pointer: Change signature of leak to make it more sensible Instead of `leak` copying a previously allocated value, it now takes (unmanaged) ownership of a fresh value and allocates. This makes more sense semantically, as we're just instantiating a new pointer that won't be managed by Carp and will leak unless freed explicitly. Thanks to @TimDeve for the suggestion! * Pointer: Improve apis on set and alloc - Rename set-unsafe to align w/ naming conventions Most unsafe functions are prefixed with `unsafe`, not suffixed. - Rename leak to `unsafe-alloc` to better convey its semantics (leak also already exists as `Unsafe.leak`. - Remove `cast` since its use is covered by `Unsafe.coerce`. Thanks to TimDeve and hellerve for the suggestions! * Pointer: Make unsafe-set take ownership * Pointer: Correctly cast in unsafe-alloc; add unsafe-realloc Here's a short illustration of why we need `realloc` even though we already have `Pointer.add`: ``` (defn foo [] (let-do [p (Pointer.unsafe-alloc 2)] (set! p (Pointer.add p (Pointer.width (Pointer.unsafe-alloc @"foo")))) (ignore (Pointer.unsafe-set p @"foo")) (println* (Pointer.to-value (the (Ptr String) (Unsafe.coerce p)))) (Pointer.free p) 0)) ``` This function seems fine at first glance, but since `add` returns a new pointer, `p` is reset to the new pointer, the reference to the original is lost, and `free` is called on a value that was never actually allocated since `add` does not malloc. Using unsafe-realloc, we can avoid the additional allocation: ``` (defn foo [] (let-do [p (Pointer.unsafe-alloc 2)] (Pointer.unsafe-realloc p @"foo") (ignore (Pointer.unsafe-set p @"foo")) (println* (Pointer.to-value (the (Ptr String) (Unsafe.coerce p)))) (Pointer.free p) 0)) ``` The allocation is what we care about here. One still needs to use `Unsafe.coerce` since as far as the Carp compiler is concerned, `p` is still a (Ptr Int) even though the corresponding c has cast it silently to a `String` in order to reallocate. * Pointer: Change signature of unsafe-set to align with set! * Pointer: Change signature of `set` to align with `set!` * Pointer: Remove unsafe-realloc * Pointer: Update docs for unsafe-alloc and free * System: Remove System.free Pointer.free serves a similar function, and is more restrictive, so we'll remove System.free. One can use `delete` or cast to a pointer and free that way.See PR #1012 for further discussion.
1 parent ae2310b commit 892a972

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

core/Pointer.carp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,45 @@ The user will have to ensure themselves that this is a safe operation.")
2020
(doc from-long "converts a long integer to a pointer.")
2121
(deftemplate from-long (Fn [Long] (Ptr p)) "$p* $NAME(Long p)" " $DECL { return ($p*)p; }")
2222

23+
(doc unsafe-set
24+
"Sets the value of a pointer to a value of any type."
25+
"The user will have to ensure this operation is safe.")
26+
(deftemplate unsafe-set (Fn [(Ptr p) a] Unit) "void $NAME($p* p, $a a)" "$DECL { *p = ($p)a; }")
27+
28+
(doc unsafe-alloc
29+
"Allocate a new pointer to the value `a`."
30+
("This pointer won't be managed by Carp's borrow checker and will cause " false)
31+
"memory leaks unless explicitly freed using `Pointer.free`."
32+
""
33+
("See `Unsafe.leak` if you want to prevent Carp's automatic memory management " false)
34+
"from deallocating a value without performing an allocation."
35+
""
36+
"```"
37+
"(let-do [x (Pointer.unsafe-alloc @\"c\")]"
38+
" (Pointer.set x @\"carp\")"
39+
" (println* (Pointer.to-value x))"
40+
" (Pointer.free x))"
41+
"```")
42+
(deftemplate unsafe-alloc (Fn [a] (Ptr a)) "$a* $NAME($a r)" "$DECL { void *leak = CARP_MALLOC(sizeof($a)); *($a*)leak= r; return ($a*)leak;}")
43+
44+
(doc free
45+
"Free a pointer, deallocating the memory associated with it."
46+
("Carp's borrow checker handles deallocation automatically for managed types. " false)
47+
("The Ptr type is unmanaged, so pointers allocated with functions " false)
48+
"such as `Pointer.unsafe-alloc` need to be deallocated manually using this function."
49+
"Users need to manually verify that this operation is safe."
50+
""
51+
"```"
52+
"(let-do [x (Pointer.unsafe-alloc @\"c\")]"
53+
" (Pointer.set x @\"carp\")"
54+
" (println* (Pointer.to-value x))"
55+
" (Pointer.free x))"
56+
"```")
57+
(deftemplate free (Fn [(Ptr p)] Unit) "void $NAME($p* p)" "$DECL {CARP_FREE(p);}")
58+
59+
(doc set "Sets the value of a pointer.")
60+
(deftemplate set (Fn [(Ptr p) p] Unit) "void $NAME($p* p, $p a)" "$DECL { *p = a; }")
61+
2362
(defn inc [a] (Pointer.add a 1l))
2463
(implements inc Pointer.inc)
2564
(defn dec [a] (Pointer.sub a 1l))

core/System.carp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
(defmodule System
55
(doc carp-init-globals "Initializes all global variables (in correct order, based on interdependencies). Called automatically by `main` if the project is compiled as an executable. Client code needs to call this function manually when using a library written in Carp.")
66
(register carp-init-globals (Fn [Int Int] ()) "carp_init_globals")
7-
(doc free "Frees an object. Should not be called except in direst circumstances.")
8-
(register free (Fn [t] ()))
97
(doc time "Gets the current system time as an integer.")
108
(register time (Fn [] Int))
119
(doc nanotime "Gets the current system time in nanoseconds as a long.")

0 commit comments

Comments
 (0)