2.5 Foreign Pointers
A pointer object encapsulates a memory address and a potentially empty sequence of symbolic tags. A pointer object may refer to an address that under the control of Racket’s memory manager and garbage collection, or it may refer to an address is managed externally.
The base type for pointers is ptr_t (equivalent to void_t*), and new pointer types can be created via foreign.type with either a name for an opaque content type, an existing pointer type, a struct type or a union type.
A pointer’s tags enable sanity checking that the right kind of pointer is provided to an operation. If an operation expects a pointer with a certain sequence of tags, it accepts a pointer with additional tags added to the end, so additional tags form a pointer subtype. A ptr_t representation has no tags, which means that a C conversion to Rhombus via ptr_t is not accepted by any context that expects some tag, whereas a pointer with any tags is accepted as a ptr_t representation to translate to C. Whether a pointer object represents an address managed by Rhombus’s garbage collector is independent of its tags.
foreign type | |
| |
foreign type | |
| |
annotation | |
The /gcable type operator (more precisely, a / operator that expects a subsequent literal gcable always) requires that the argument type is a pointer type, and it describes a type that is the same, but that represents an address within memory that is managed by Rhombus’s garbage collector. The Rhombus-to-C conversion of a /gcable pointer is no different that for the original pointer type (i.e., it is not required to refer to a garbage-collectable address), but it affects the handling of a C address representation to a Rhombus representation.
The GCable_ptr_t annotation is satisfied only by pointer objects that are allowed to reference memory that is managed Racket’s garbage collector. In contrast, foreign.type ptr_t/gcable as an annotation is satisified by any pointer object, since ptr_t/gcable accepts any pointer for conversion to C.
When an expression has the static information of the pointer type, then indexing via [] (i.e., #%index) or mem extracts a Rhombus representation for type, and indexing with [] or mem plus := assigns to the pointer by converting a Rhombus representation of type to a C representation to install. A pointer expression works with [] only via static information.
Describes a type that is represented by an array on the C side and a pointer object in the Rhombus side. In most type contexts, the array’s size_expr must be a literal nonnegative integer. When a type using [] is used with new, however, then size_expr can be any expression that produces a nonnegative integer.
The Rhombus-side pointer representation uses a tag formed by adding a * suffix on the name of elem_type, as long as it has a name. If elem_type is an immediate struct, union, or -> form, then it has no name, and the Rhombus-side representation is a generic pointer.
When an expression has the static information of an array type, then indexing via [] (i.e., #%index) or mem extracts a Rhombus representation for an element of the array, and indexing with [] or mem plus := assigns to the array by converting a Rhombus value to a C representation to install into the array. If size_expr is a literal integer, then the bounds checking prevents indexing with positions that are negative or not less than the size. An array-pointer expression works with [] only via static information.
24
> p
#<double_t*/gcable>
> p[0] := 0.0
> p[1] := 10.0
> p[2] := 20.0
> p[1]
10.0
> mem p[3]
mem: value does not satisfy annotation
annotation: Int.in(0 .. 3)
value: 3
expression | ||||||||||||||||||||||||
| ||||||||||||||||||||||||
expression | ||||||||||||||||||||||||
| ||||||||||||||||||||||||
| ||||||||||||||||||||||||
expression | ||||||||||||||||||||||||
| ||||||||||||||||||||||||
|
The amount of allocated memory depends on type, where the allocated memory spans as many bytes as the C representation of type. A type of the form elem_type[expr] is allowed with a non-literal expr to allocate the indicated multiple of the C size of elem_type in bytes.
By default, allocation uses ~gcable mode, but a maybe_mode specification can pick any of the supported modes:
~gcable: Allocates in Rhombus’s garbage-collected space. The allocated memory becomes eligible for garbage collection when it is not referenced by any reachable pointer object or traced allocated memory. Even before collection, the memory manager may relocate the object, but garbage collection or relocation cannot happen with a foreign-procedure call is active.
~immobile: Like ~gcable, but the allocated memory will not be relocated by a different address by the memory manager as long as it is not collected.
~traced: Like ~gcable, but the allocated memory is traced, meaning that it can itself contain references to other allocated memory. The references are updated by the memory manager if it moved the referenced objects.
~traced_immobile: Like ~traced, but the allocated memory will not be relocated by a different address by the memory manager as long as it is not collected.
~manual: Allocates outside of Rhombus’s garbage-collected space. The allocated memory is never relocated by the garbage collection, and it must be freed explicitly with free.
> def i1_imm = ptr_to_uintptr(p1_imm)
> memory.GC() // probably moves p1, does not move p1_imm
> i1_imm == ptr_to_uintptr(p1_imm)
#true
> pm[0] := 6
> def pm_i = ptr_to_uintptr(pm)
> pm := #false
> memory.GC() // does not affect manual allocation
> pm := uintptr_to_ptr(pm_i)
> pm[0]
6
> free(pm)
If ~as to_type is specified, the result pointer is tagged as to_type, where to_type must be a pointer type, and the malloc expression has the static information of to_type.
foreign.struct Point_t(x :: int_t,
expression | |
| |
expression | |
| |
expression | |
| |
expression | |
| |
expression | |
| |
expression | |
| |
expression | |
When the *(ptr_type)ptr_expr form is used, the tags on the result of ptr_expr are ignored. The pointer is effectively cast to ptr_type, and ptr_type must be a pointer type referring to type elements.
When the * ptr_expr, ptr_expr[index_expr], or & ptr_expr[delta_expr] form is used, then ptr_expr must have static information to indicate an element type or pointer-referenced type to be used as type. The mem ptr_expr[index_expr] form is equivalent to ptr_expr[index_expr] without mem, since ptr_expr must have static information that would also allow it to work with [] via #%index.
> p
#<double_t*/gcable>
> mem p[0]
0.0
0
> mem p[0]
#nan
#nan
expression | ||||||||
| ||||||||
| ||||||||
| ||||||||
| ||||||||
| ||||||||
|
If ~offset (offset_expr) is specified, then the (pre-conversion) address produced by the cast is offset_expr bytes after the address represented by the converted result of expr. A ~offset (offset_expr) is allowed only when to_type has an address representation.
257
514
cast: value does not match type
value: #<int16_t*/gcable>
type: int32_t*
#<byte_t*/gcable>
function | |||||||
| |||||||
function | |||||||
| |||||||
function | |||||||
The memcpy function sets len bytes at the address represented by dest plus dest_pffset so that each byte’s value is byte.
> memcpy(cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,
cast ~from (bytes_ptr_t) ~to (ptr_t) bstr2,
2)
> bstr1
#"Gollo".copy()
> memmove(mem &(cast ~from (bytes_ptr_t) ~to (byte_t*) bstr1)[2],
cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,
3)
> bstr1
#"GoGol".copy()
> memset(cast ~from (bytes_ptr_t) ~to (ptr_t) bstr1,
3,
~dest_offset: 2)
> bstr1
#"Goooo".copy()
function | |
| |
function | |
Beware that the integer form of an address managed by Rhombus’s garbage collector can become immediately invalid, unless an object at the address was allocated as immobile.
function | |
| |
function | |