1 //------------------------------------------------------------------------------
2 // GB_werk.h: definitions for werkspace management on the Werk stack
3 //------------------------------------------------------------------------------
4 
5 // SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2021, All Rights Reserved.
6 // SPDX-License-Identifier: Apache-2.0
7 
8 //------------------------------------------------------------------------------
9 
10 #ifndef GB_WERK_H
11 #define GB_WERK_H
12 
13 //------------------------------------------------------------------------------
14 // GB_werk_push/pop: manage werkspace in the Context->Werk stack
15 //------------------------------------------------------------------------------
16 
17 // Context->Werk is a small fixed-size array that is allocated on the stack
18 // of any user-callable GraphBLAS function.  It is used for small werkspace
19 // allocations.
20 
21 // GB_ROUND8(s) rounds up s to a multiple of 8
22 #define GB_ROUND8(s) (((s) + 7) & (~0x7))
23 
24 //------------------------------------------------------------------------------
25 // GB_werk_push: allocate werkspace from the Werk stack or malloc
26 //------------------------------------------------------------------------------
27 
28 // The werkspace is allocated from the Werk static if it small enough and space
29 // is available.  Otherwise it is allocated by malloc.
30 
GB_werk_push(size_t * size_allocated,bool * on_stack,size_t nitems,size_t size_of_item,GB_Context Context)31 static inline void *GB_werk_push    // return pointer to newly allocated space
32 (
33     // output
34     size_t *size_allocated,         // # of bytes actually allocated
35     bool *on_stack,                 // true if werkspace is from Werk stack
36     // input
37     size_t nitems,                  // # of items to allocate
38     size_t size_of_item,            // size of each item
39     GB_Context Context
40 )
41 {
42 
43     //--------------------------------------------------------------------------
44     // check inputs
45     //--------------------------------------------------------------------------
46 
47     ASSERT (on_stack != NULL) ;
48     ASSERT (size_allocated != NULL) ;
49 
50     //--------------------------------------------------------------------------
51     // determine where to allocate the werkspace
52     //--------------------------------------------------------------------------
53 
54     size_t size ;
55     if (Context == NULL || nitems > GB_WERK_SIZE || size_of_item > GB_WERK_SIZE
56         #ifdef GBCOVER
57         // Werk stack can be disabled for test coverage
58         || (GB_Global_hack_get (1) != 0)
59         #endif
60     )
61     {
62         // no context, or werkspace is too large to allocate from the Werk stack
63         (*on_stack) = false ;
64     }
65     else
66     {
67         // try to allocate from the Werk stack
68         size = GB_ROUND8 (nitems * size_of_item) ;
69         ASSERT (size % 8 == 0) ;        // size is rounded up to a multiple of 8
70         size_t freespace = GB_WERK_SIZE - Context->pwerk ;
71         ASSERT (freespace % 8 == 0) ;   // thus freespace is also multiple of 8
72         (*on_stack) = (size <= freespace) ;
73     }
74 
75     //--------------------------------------------------------------------------
76     // allocate the werkspace
77     //--------------------------------------------------------------------------
78 
79     if (*on_stack)
80     {
81         // allocate the werkspace from the Werk stack
82         GB_void *p = Context->Werk + Context->pwerk ;
83         Context->pwerk += (int) size ;
84         (*size_allocated) = size ;
85         return ((void *) p) ;
86     }
87     else
88     {
89         // allocate the werkspace from malloc
90         return (GB_malloc_memory (nitems, size_of_item, size_allocated)) ;
91     }
92 }
93 
94 //------------------------------------------------------------------------------
95 // GB_WERK helper macros
96 //------------------------------------------------------------------------------
97 
98 // declare a werkspace X of a given type
99 #define GB_WERK_DECLARE(X,type)                     \
100     type *restrict X = NULL ;                    \
101     bool X ## _on_stack = false ;                   \
102     size_t X ## _nitems = 0, X ## _size_allocated = 0 ;
103 
104 // push werkspace X
105 #define GB_WERK_PUSH(X,nitems,type)                                         \
106     X ## _nitems = (nitems) ;                                               \
107     X = (type *) GB_werk_push (&(X ## _size_allocated), &(X ## _on_stack),  \
108         X ## _nitems, sizeof (type), Context) ;
109 
110 // pop werkspace X
111 #define GB_WERK_POP(X,type)                                                 \
112     X = (type *) GB_werk_pop (X, &(X ## _size_allocated), X ## _on_stack,   \
113         X ## _nitems, sizeof (type), Context) ;
114 
115 //------------------------------------------------------------------------------
116 // GB_werk_pop:  free werkspace from the Werk stack
117 //------------------------------------------------------------------------------
118 
119 // If the werkspace was allocated from the Werk stack, it must be at the top of
120 // the stack to free it properly.  Freeing a werkspace in the middle of the
121 // Werk stack also frees everything above it.  This is not a problem if that
122 // space is also being freed, but the assertion below ensures that the freeing
123 // werkspace from the Werk stack is done in LIFO order, like a stack.
124 
GB_werk_pop(void * p,size_t * size_allocated,bool on_stack,size_t nitems,size_t size_of_item,GB_Context Context)125 static inline void *GB_werk_pop     // free the top block of werkspace memory
126 (
127     // input/output
128     void *p,                        // werkspace to free
129     size_t *size_allocated,         // # of bytes actually allocated for p
130     // input
131     bool on_stack,                  // true if werkspace is from Werk stack
132     size_t nitems,                  // # of items to allocate
133     size_t size_of_item,            // size of each item
134     GB_Context Context
135 )
136 {
137     ASSERT (size_allocated != NULL) ;
138 
139     if (p == NULL)
140     {
141         // nothing to do
142     }
143     else if (on_stack)
144     {
145         // werkspace was allocated from the Werk stack
146         ASSERT ((*size_allocated) == GB_ROUND8 (nitems * size_of_item)) ;
147         ASSERT (Context != NULL) ;
148         ASSERT ((*size_allocated) % 8 == 0) ;
149         ASSERT (((GB_void *) p) + (*size_allocated) ==
150                 Context->Werk + Context->pwerk) ;
151         Context->pwerk = ((GB_void *) p) - Context->Werk ;
152         (*size_allocated) = 0 ;
153     }
154     else
155     {
156         // werkspace was allocated from malloc
157         GB_dealloc_memory (&p, *size_allocated) ;
158     }
159     return (NULL) ;                 // return NULL to indicate p was freed
160 }
161 
162 #endif
163 
164