New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unsafe operations for array uninitialization and raw arrays #207
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The Array_uninit primitive sets all objptrs in the element at the given index to a bogus non-objptr value (0wx1). One motivation for this primitive is to support space-efficient polymorphic resizeable arrays. When shrinking a resizeable array, we would like to "NULL" out the elements that are no longer part of the logical array, in order to avoid a (logical) space leak.
It is correct to not special case the Array_uninit primitive, but doing so inhibits the Useless optimization.
The Array_uninitIsNop primitive answers if the Array_uninit primitive applied to the same array would be a nop (i.e., if the array has no objptrs in the elements). This can be used to skip a bulk-Array_uninit loop when it is known that the Array_uninit operations would be nops. The primitive is translated to a constant by toRssa. It would be possible to introduce SSA and SSA2 optimizations that simplified Array_uninitIsNop and Array_uninit when it is clear that the array will never have objptrs in the elements. In particular, while an `(int64 * int64) array` (in SSA) might become an `int64 array` (due to Useless), the reverse can never happen. Similarly, while an `((int64 * int64)) array` (in SSA2) might become an `(int64 * int64) array` (due to DeepFlatten), the reverse can never happen. Also, RSSA shrink does not seem to simplify the switch {test = 0x0: Word32, default = None, cases = ((0x0, L_1277), (0x1, L_1276))} that results from replacing Array_uninitIsNop with a constant.
The `Array_allocRaw` primitive allocates an array, but with a header that indicates that the array has no objptrs. A related `Array_toArray` primitive updates the header of an `Array_allocRaw` primitive to reveal the objptrs. Between the allocation and the cast, all elements of the array must be uninitialized (setting all objptrs in elements to a bogus non-objptr value (0wx1)). One motiviation for this primitive is that, in a parallel setting, the uninitialization of an array can be a sequential bottleneck. The `Array_allocRaw` is a constant time operation and the subsequent `Array_uninit` operations can be performed in parallel. Note that it is not safe to write non-bogus objptrs into a raw array. Because the raw array's header indicates that the array has no objptrs, non-bogus objptrs written to the array will not be traced or updated during a garbage collection. In the future, we may consider eliminating `Array_alloc` (the non-raw array allocation) and moving the uninitialization loop from the runtime to SML. To do so, though, we should ensure that arrays that do not need uninitialization are optimized early in the optimization pipeline.
Currently, no arrays are globalized; see MLton#206. However, if "small" arrays were globalized, then an `Array_allocRaw[t] (l)` would be globalized as `Array_alloc[t] (l)` and the `Array_toArray` (that casts the raw array to a normal array) would be dropped. Now, the `Array` abstract value tracks whether the array is raw or normal, which is used to generate either `Array_allocRaw` or `Array_alloc` if the array is globalized. Also, `Array_toArray` creates the destination `Array` abstract value with `Birth.unknown()`, which prohibits globalization.
ShareZeroVec only optimizes val a: t array = Array_alloc[t] (l) ... val v: t vector = Array_toVector[t] (a) not val r: t array = Array_allocRaw[t] (l) ... val a: t vector = Array_toArray[t] (r) ... val v: t vector = Array_toVector[t] (a) The latter is possibly, just requires a more sophisticated analysis to connect Array_alloc/Array_allocRaw with Array_toVector.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Highlights:
Array_uninit
primitive toArray_alloc
(612eede)Array_uninit: 'a array * SeqInt.int -> unit
primitive (946385f)Array_uninitIsNop: 'a array -> bool
primitive (426baa1)Array_allocRaw: SeqIndex.int -> 'a array
andArray_toArray: 'a array -> 'a array
primitives (580da00)All new primitives are implemented in
toRssa
.The
Array_uninit
primitive sets all objptrs in the element at the given index to a bogus non-objptr value (0wx1). One motivation for this primitive is to support space-efficient polymorphic resizeable arrays. When shrinking a resizeable array, we would like to "NULL
" out the elements that are no longer part of the logical array, in order to avoid a (logical) space leak.The
Array_uninitIsNop
primitive answers if theArray_uninit
primitive applied to the same array would be a nop (i.e., if the array has no objptrs in the elements). This can be used to skip a bulk-Array_uninit
loop when it is known that theArray_uninit
operations would be nops.It would be possible to introduce (pre-
toRssa
) optimizations that simplifiedArray_uninitIsNop
andArray_uninit
when it is clear that the array will never have objptrs in the elements.The
Array_allocRaw
primitive allocates an array, but with a header that indicates that the array has no objptrs. A relatedArray_toArray
primitive updates the header of anArray_allocRaw
primitive to reveal the objptrs. Between the allocation and the cast, all elements of the array must be uninitialized (setting all objptrs in elements to a bogus non-objptr value (0wx1)).One motiviation for this primitive is that, in a parallel setting, the uninitialization of an array can be a sequential bottleneck. The
Array_allocRaw
is a constant time operation and the subsequentArray_uninit
operations can be performed in parallel.Note that it is not safe to write non-bogus objptrs into a raw array. Because the raw array's header indicates that the array has no objptrs, non-bogus objptrs written to the array will not be traced or updated during a garbage collection.
In the future, we may consider eliminating
Array_alloc
(the non-raw array allocation) and moving the uninitialization loop from the runtime to SML. To do so, though, we should ensure that arrays that do not need uninitialization are optimized early in the optimization pipeline.The extended
structure Unsafe.Array
has the following signature:@mikerainey