Close some races with locking on R100 and R200 which could manifest as rendering
authorEric Anholt <anholt@FreeBSD.org>
Tue, 17 Aug 2004 01:41:29 +0000 (01:41 +0000)
committerEric Anholt <anholt@FreeBSD.org>
Tue, 17 Aug 2004 01:41:29 +0000 (01:41 +0000)
errors on r100 and rendering errors and hangs on r200 (same for R100 without
OLD_PACKETS).

If a command buffer filled after some state (EmitState or a VBPNTR write) was
emitted, the lock was grabbed, the buffer flushed, a new buffer prepared, and
the lock dropped.  Another client could come in, set its own state as part of
rendering, and when the first client flushed the rendering commands depending
on the previous state, it got the 2nd client's state.  This is fixed by checking
for enough space before beginning a set of state emits and rendering, and
flushing the buffer first if so.  This guarantees that the buffer won't wrap.

Also, move the "lost_context = 1" from the end of cmdbuf flushing to
UNLOCK_HARDWARE for clarity (at a minimum) that any time the lock is dropped,
state may get overwritten.  We don't have enough information at the point of the
LOCK_HARDWARE to reset our state to the last UNLOCK_HARDWARE point in the case
that we did lose our context, but saving the information to rebuild that state
may be a useful optimization (ipers data suggests up to 5%).

15 files changed:
src/mesa/drivers/dri/r200/r200_cmdbuf.c
src/mesa/drivers/dri/r200/r200_context.h
src/mesa/drivers/dri/r200/r200_ioctl.c
src/mesa/drivers/dri/r200/r200_ioctl.h
src/mesa/drivers/dri/r200/r200_lock.h
src/mesa/drivers/dri/r200/r200_state_init.c
src/mesa/drivers/dri/r200/r200_swtcl.c
src/mesa/drivers/dri/r200/r200_tcl.c
src/mesa/drivers/dri/radeon/radeon_context.h
src/mesa/drivers/dri/radeon/radeon_ioctl.c
src/mesa/drivers/dri/radeon/radeon_ioctl.h
src/mesa/drivers/dri/radeon/radeon_lock.h
src/mesa/drivers/dri/radeon/radeon_state_init.c
src/mesa/drivers/dri/radeon/radeon_swtcl.c
src/mesa/drivers/dri/radeon/radeon_tcl.c

index 26812d50b628e44f59f0637d43d23af356bce634..fa0c62385b00ee8d275abc266a5dd5bb69477e16 100644 (file)
@@ -183,7 +183,7 @@ extern void r200EmitVbufPrim( r200ContextPtr rmesa,
       fprintf(stderr, "%s cmd_used/4: %d prim %x nr %d\n", __FUNCTION__,
              rmesa->store.cmd_used/4, primitive, vertex_nr);
    
-   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, 3 * sizeof(*cmd),
+   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, VBUF_BUFSZ,
                                                  __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
@@ -236,8 +236,7 @@ GLushort *r200AllocEltsOpenEnded( r200ContextPtr rmesa,
    
    r200EmitState( rmesa );
    
-   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, 
-                                               12 + min_nr*2,
+   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, ELTS_BUFSZ(min_nr),
                                                __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
@@ -275,7 +274,7 @@ void r200EmitVertexAOS( r200ContextPtr rmesa,
       fprintf(stderr, "%s:  vertex_size 0x%x offset 0x%x \n",
              __FUNCTION__, vertex_size, offset);
 
-   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, 5 * sizeof(int),
+   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, VERT_AOS_BUFSZ,
                                                  __FUNCTION__ );
 
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3;
@@ -292,18 +291,17 @@ void r200EmitAOS( r200ContextPtr rmesa,
                    GLuint offset )
 {
    drm_radeon_cmd_header_t *cmd;
-   int sz = 3 + ((nr/2)*3) + ((nr&1)*2);
+   int sz = AOS_BUFSZ(nr);
    int i;
    int *tmp;
 
    if (R200_DEBUG & DEBUG_IOCTL)
       fprintf(stderr, "%s nr arrays: %d\n", __FUNCTION__, nr);
 
-   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, sz * sizeof(int),
-                                                 __FUNCTION__ );
+   cmd = (drm_radeon_cmd_header_t *)r200AllocCmdBuf( rmesa, sz, __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3;
-   cmd[1].i = R200_CP_CMD_3D_LOAD_VBPNTR | ((sz-3) << 16);
+   cmd[1].i = R200_CP_CMD_3D_LOAD_VBPNTR | (((sz / sizeof(int)) - 3) << 16);
    cmd[2].i = nr;
    tmp = &cmd[0].i;
    cmd += 3;
index 8f90c15831527712ef18d77014cbbdf2da63598e..f000e143308cabf5ce0556702ea332efe0e768ba 100644 (file)
@@ -528,6 +528,8 @@ struct r200_hw_state {
    struct r200_state_atom grd; /* guard band clipping */
    struct r200_state_atom fog; 
    struct r200_state_atom glt; 
+
+   int max_state_size; /* Number of bytes necessary for a full state emit. */
 };
 
 struct r200_state {
index 54875d5d22b8024680ab7bfea7e6fab917e0863a..5d084baf3e00d1184cd72fdf7e4436ed9e6e590b 100644 (file)
@@ -132,7 +132,6 @@ int r200FlushCmdBufLocked( r200ContextPtr rmesa, const char * caller )
    rmesa->store.statenr = 0;
    rmesa->store.cmd_used = 0;
    rmesa->dma.nr_released_bufs = 0;
-   rmesa->lost_context = 1;    
    return ret;
 }
 
@@ -564,8 +563,6 @@ static void r200Clear( GLcontext *ctx, GLbitfield mask, GLboolean all,
         return;
    }
 
-   r200EmitState( rmesa );
-
    /* Need to cope with lostcontext here as kernel relies on
     * some residual state:
     */
index 011288161ba40d595d8028dd467480cdc2ed67bd..1503df70754b20b93794d7b749eeae8696575466 100644 (file)
@@ -169,6 +169,31 @@ do {                                                       \
    }                                                   \
 } while (0)
 
+/* Command lengths.  Note that any time you ensure ELTS_BUFSZ or VBUF_BUFSZ
+ * are available, you will also be adding an rmesa->state.max_state_size because
+ * r200EmitState is called from within r200EmitVbufPrim and r200FlushElts.
+ */
+#define AOS_BUFSZ(nr)  ((3 + ((nr / 2) * 3) + ((nr & 1) * 2)) * sizeof(int))
+#define VERT_AOS_BUFSZ (5 * sizeof(int))
+#define ELTS_BUFSZ(nr) (12 + nr * 2)
+#define VBUF_BUFSZ     (3 * sizeof(int))
+
+/* Ensure that a minimum amount of space is available in the command buffer.
+ * This is used to ensure atomicity of state updates with the rendering requests
+ * that rely on them.
+ *
+ * An alternative would be to implement a "soft lock" such that when the buffer
+ * wraps at an inopportune time, we grab the lock, flush the current buffer,
+ * and hang on to the lock until the critical section is finished and we flush
+ * the buffer again and unlock.
+ */
+static __inline void r200EnsureCmdBufSpace( r200ContextPtr rmesa, int bytes )
+{
+   if (rmesa->store.cmd_used + bytes > R200_CMD_BUF_SZ)
+      r200FlushCmdBuf( rmesa, __FUNCTION__ );
+   assert( bytes <= R200_CMD_BUF_SZ );
+}
+
 /* Alloc space in the command buffer
  */
 static __inline char *r200AllocCmdBuf( r200ContextPtr rmesa,
index 7a4dfcf15cc172102fb27a14d786083b1b955c1c..c913bd5f629d80a24c64f03bec6799774477e1ea 100644 (file)
@@ -98,7 +98,15 @@ extern int prevLockLine;
       DEBUG_LOCK();                                            \
    } while (0)
 
-/* Unlock the hardware.
+/* Unlock the hardware.  We must assume that state has been lost when we unlock,
+ * because when we next grab the lock (to emit an accumulated cmdbuf), we don't
+ * have the information to recreate the context state as of the last unlock in
+ * in the case that we did lose the context state.
+ *
+ * The alternative to this would be to copy out the state on unlock
+ * (approximately) and if we did lose the context, dispatch a cmdbuf to reset
+ * the state to that old copy before continuing with the accumulated command
+ * buffer.
  */
 #define UNLOCK_HARDWARE( rmesa )                                       \
    do {                                                                        \
@@ -106,6 +114,7 @@ extern int prevLockLine;
                  rmesa->dri.hwLock,                                    \
                  rmesa->dri.hwContext );                               \
       DEBUG_RESET();                                                   \
+      rmesa->lost_context = GL_TRUE;                                   \
    } while (0)
 
 #endif
index ed53c5d2cf0d469c6cdbee6c1aec6e3f5d74ba98..3b6893aeee96fdd1206da78954fbcb47506a9090 100644 (file)
@@ -205,6 +205,7 @@ void r200InitState( r200ContextPtr rmesa )
    make_empty_list(&(rmesa->hw.dirty)); rmesa->hw.dirty.name = "DIRTY";
    make_empty_list(&(rmesa->hw.clean)); rmesa->hw.clean.name = "CLEAN";
 
+   rmesa->hw.max_state_size = 0;
 
 #define ALLOC_STATE( ATOM, CHK, SZ, NM, IDX )                          \
    do {                                                                \
@@ -215,6 +216,7 @@ void r200InitState( r200ContextPtr rmesa )
       rmesa->hw.ATOM.idx = IDX;                                        \
       rmesa->hw.ATOM.check = check_##CHK;                              \
       insert_at_head(&(rmesa->hw.dirty), &(rmesa->hw.ATOM));   \
+      rmesa->hw.max_state_size += SZ * sizeof(int);            \
    } while (0)
       
       
index d694ff1b9bcbc41d62e1d2c5c333100c1cb08c35..66662bb4d032d5cce3905d5eed6275c858059c8b 100644 (file)
@@ -260,6 +260,8 @@ static void flush_last_swtcl_prim( r200ContextPtr rmesa  )
              current->ptr);
 
       if (rmesa->dma.current.start != rmesa->dma.current.ptr) {
+        r200EnsureCmdBufSpace( rmesa, VERT_AOS_BUFSZ +
+                               rmesa->hw.max_state_size + VBUF_BUFSZ );
         r200EmitVertexAOS( rmesa,
                              rmesa->swtcl.vertex_size,
                              current_offset);
index 85f4bc1f7dc446ba409f54fd98b93264408af690..b613911b06d93dc978de047efefb9d5da96a48f4 100644 (file)
@@ -143,6 +143,9 @@ static GLushort *r200AllocElts( r200ContextPtr rmesa, GLuint nr )
    if (rmesa->dma.flush)
       rmesa->dma.flush( rmesa );
 
+   r200EnsureCmdBufSpace( rmesa, AOS_BUFSZ(rmesa->tcl.nr_aos_components) +
+                         rmesa->hw.max_state_size + ELTS_BUFSZ(nr) );
+   
    r200EmitAOS( rmesa,
                rmesa->tcl.aos_components,
                rmesa->tcl.nr_aos_components, 0 );
@@ -167,6 +170,9 @@ static void EMIT_PRIM( GLcontext *ctx,
    r200ContextPtr rmesa = R200_CONTEXT( ctx );
    r200TclPrimitive( ctx, prim, hwprim );
    
+   r200EnsureCmdBufSpace( rmesa, AOS_BUFSZ(rmesa->tcl.nr_aos_components) +
+                         rmesa->hw.max_state_size + VBUF_BUFSZ );
+
    r200EmitAOS( rmesa,
                  rmesa->tcl.aos_components,
                  rmesa->tcl.nr_aos_components,
index c8f74272ae06744d0e432ecdf32bc3bd68088367..03392ee6754ad670e2ae1de7196c508123a075c9 100644 (file)
@@ -426,6 +426,8 @@ struct radeon_hw_state {
    struct radeon_state_atom fog; 
    struct radeon_state_atom glt; 
    struct radeon_state_atom txr[2]; /* for NPOT */
+
+   int max_state_size; /* Number of bytes necessary for a full state emit. */
 };
 
 struct radeon_state {
index c5707a02807752810d4b22bb1ba3c54bde2e2ed9..3cb7dca215096d96243346e8013d5bb427aa8342 100644 (file)
@@ -204,9 +204,10 @@ extern void radeonEmitVbufPrim( radeonContextPtr rmesa,
       fprintf(stderr, "%s cmd_used/4: %d\n", __FUNCTION__,
              rmesa->store.cmd_used/4);
    
+   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, VBUF_BUFSZ,
+                                                      __FUNCTION__ );
 #if RADEON_OLD_PACKETS
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, 6 * sizeof(*cmd),
-                                                 __FUNCTION__ );
+   cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
    cmd[1].i = RADEON_CP_PACKET3_3D_RNDR_GEN_INDX_PRIM | (3 << 16);
    cmd[2].i = rmesa->ioctl.vertex_offset;
@@ -223,8 +224,6 @@ extern void radeonEmitVbufPrim( radeonContextPtr rmesa,
              __FUNCTION__,
              cmd[1].i, cmd[2].i, cmd[4].i, cmd[5].i);
 #else
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, 4 * sizeof(*cmd),
-                                                 __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
    cmd[1].i = RADEON_CP_PACKET3_3D_DRAW_VBUF | (1 << 16);
@@ -291,10 +290,10 @@ GLushort *radeonAllocEltsOpenEnded( radeonContextPtr rmesa,
    
    radeonEmitState( rmesa );
    
+   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa,
+                                                      ELTS_BUFSZ(min_nr),
+                                                      __FUNCTION__ );
 #if RADEON_OLD_PACKETS
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, 
-                                                 24 + min_nr*2,
-                                                 __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
    cmd[1].i = RADEON_CP_PACKET3_3D_RNDR_GEN_INDX_PRIM;
@@ -308,9 +307,6 @@ GLushort *radeonAllocEltsOpenEnded( radeonContextPtr rmesa,
 
    retval = (GLushort *)(cmd+6);
 #else   
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, 
-                                                 16 + min_nr*2,
-                                                 __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3_CLIP;
    cmd[1].i = RADEON_CP_PACKET3_3D_DRAW_INDX;
@@ -354,7 +350,7 @@ void radeonEmitVertexAOS( radeonContextPtr rmesa,
       fprintf(stderr, "%s:  vertex_size 0x%x offset 0x%x \n",
              __FUNCTION__, vertex_size, offset);
 
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, 5 * sizeof(int),
+   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, VERT_AOS_BUFSZ,
                                                  __FUNCTION__ );
 
    cmd[0].i = 0;
@@ -380,7 +376,7 @@ void radeonEmitAOS( radeonContextPtr rmesa,
       (component[0]->aos_start + offset * component[0]->aos_stride * 4);
 #else
    drm_radeon_cmd_header_t *cmd;
-   int sz = 3 + (nr/2 * 3) + (nr & 1) * 2;
+   int sz = AOS_BUFSZ;
    int i;
    int *tmp;
 
@@ -388,11 +384,11 @@ void radeonEmitAOS( radeonContextPtr rmesa,
       fprintf(stderr, "%s\n", __FUNCTION__);
 
 
-   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, sz * sizeof(int),
+   cmd = (drm_radeon_cmd_header_t *)radeonAllocCmdBuf( rmesa, sz,
                                                  __FUNCTION__ );
    cmd[0].i = 0;
    cmd[0].header.cmd_type = RADEON_CMD_PACKET3;
-   cmd[1].i = RADEON_CP_PACKET3_3D_LOAD_VBPNTR | ((sz-3) << 16);
+   cmd[1].i = RADEON_CP_PACKET3_3D_LOAD_VBPNTR | (((sz / sizeof(int))-3) << 16);
    cmd[2].i = nr;
    tmp = &cmd[0].i;
    cmd += 3;
@@ -548,7 +544,6 @@ static int radeonFlushCmdBufLocked( radeonContextPtr rmesa,
    rmesa->store.statenr = 0;
    rmesa->store.cmd_used = 0;
    rmesa->dma.nr_released_bufs = 0;
-   rmesa->lost_context = 1;    
    return ret;
 }
 
@@ -982,8 +977,6 @@ static void radeonClear( GLcontext *ctx, GLbitfield mask, GLboolean all,
               __FUNCTION__, all, cx, cy, cw, ch );
    }
 
-   radeonEmitState( rmesa );
-
    /* Need to cope with lostcontext here as kernel relies on
     * some residual state:
     */
index 3f6e1751cf6a8ece8406202f377080f3e994caab..695eb5770be5d3608a2d3a6d5049f8593f68d419 100644 (file)
@@ -164,6 +164,39 @@ do {                                                       \
    }                                                   \
 } while (0)
 
+/* Command lengths.  Note that any time you ensure ELTS_BUFSZ or VBUF_BUFSZ
+ * are available, you will also be adding an rmesa->state.max_state_size because
+ * r200EmitState is called from within r200EmitVbufPrim and r200FlushElts.
+ */
+#if RADEON_OLD_PACKETS
+#define AOS_BUFSZ(nr)  ((3 + ((nr / 2) * 3) + ((nr & 1) * 2)) * sizeof(int))
+#define VERT_AOS_BUFSZ (0)
+#define ELTS_BUFSZ(nr) (24 + nr * 2)
+#define VBUF_BUFSZ     (6 * sizeof(int))
+#else
+#define AOS_BUFSZ(nr)  ((3 + ((nr / 2) * 3) + ((nr & 1) * 2)) * sizeof(int))
+#define VERT_AOS_BUFSZ (5 * sizeof(int))
+#define ELTS_BUFSZ(nr) (16 + nr * 2)
+#define VBUF_BUFSZ     (4 * sizeof(int))
+#endif
+
+/* Ensure that a minimum amount of space is available in the command buffer.
+ * This is used to ensure atomicity of state updates with the rendering requests
+ * that rely on them.
+ *
+ * An alternative would be to implement a "soft lock" such that when the buffer
+ * wraps at an inopportune time, we grab the lock, flush the current buffer,
+ * and hang on to the lock until the critical section is finished and we flush
+ * the buffer again and unlock.
+ */
+static __inline void radeonEnsureCmdBufSpace( radeonContextPtr rmesa,
+                                             int bytes )
+{
+   if (rmesa->store.cmd_used + bytes > RADEON_CMD_BUF_SZ)
+      radeonFlushCmdBuf( rmesa, __FUNCTION__ );
+   assert( bytes <= RADEON_CMD_BUF_SZ );
+}
+
 /* Alloc space in the command buffer
  */
 static __inline char *radeonAllocCmdBuf( radeonContextPtr rmesa,
index 783db7e92a8531d65e6e5489c6c6973c86558f15..e18a642088acda81efc7e362f1d6daeab45cee10 100644 (file)
@@ -99,7 +99,15 @@ extern int prevLockLine;
       DEBUG_LOCK();                                            \
    } while (0)
 
-/* Unlock the hardware.
+/* Unlock the hardware.  We must assume that state has been lost when we unlock,
+ * because when we next grab the lock (to emit an accumulated cmdbuf), we don't
+ * have the information to recreate the context state as of the last unlock in
+ * in the case that we did lose the context state.
+ *
+ * The alternative to this would be to copy out the state on unlock
+ * (approximately) and if we did lose the context, dispatch a cmdbuf to reset
+ * the state to that old copy before continuing with the accumulated command
+ * buffer.
  */
 #define UNLOCK_HARDWARE( rmesa )                                       \
    do {                                                                        \
@@ -107,6 +115,7 @@ extern int prevLockLine;
                  rmesa->dri.hwLock,                                    \
                  rmesa->dri.hwContext );                               \
       DEBUG_RESET();                                                   \
+      rmesa->lost_context = GL_TRUE;                                   \
    } while (0)
 
 #endif
index f842e430e1b812d4717af6871a34ce9dc3e1aebc..7b2f14707059a586721162752aeeff8c522b0079 100644 (file)
@@ -202,6 +202,7 @@ void radeonInitState( radeonContextPtr rmesa )
    make_empty_list(&(rmesa->hw.dirty));
    make_empty_list(&(rmesa->hw.clean));
 
+   rmesa->hw.max_state_size = 0;
 
 #define ALLOC_STATE( ATOM, CHK, SZ, NM, FLAG )                         \
    do {                                                                \
@@ -212,6 +213,7 @@ void radeonInitState( radeonContextPtr rmesa )
       rmesa->hw.ATOM.is_tcl = FLAG;                                    \
       rmesa->hw.ATOM.check = check_##CHK;                              \
       insert_at_head(&(rmesa->hw.dirty), &(rmesa->hw.ATOM));   \
+      rmesa->hw.max_state_size += SZ * sizeof(int);            \
    } while (0)
       
       
index ade97daef21ef465876167edfae7cf707e340038..40a61e10add19619bbfa51969a5894342d1e331e 100644 (file)
@@ -380,6 +380,8 @@ static void flush_last_swtcl_prim( radeonContextPtr rmesa  )
              current->ptr);
 
       if (rmesa->dma.current.start != rmesa->dma.current.ptr) {
+        radeonEnsureCmdBufSpace( rmesa, VERT_AOS_BUFSZ +
+                                 rmesa->hw.max_state_size + VBUF_BUFSZ );
         radeonEmitVertexAOS( rmesa,
                              rmesa->swtcl.vertex_size,
                              current_offset);
index 9aec22b1a52c1d4142e8081be32320d965c4914c..6e294d2395c9fd3344fa079b057accc2b3b80ada 100644 (file)
@@ -149,6 +149,9 @@ static GLushort *radeonAllocElts( radeonContextPtr rmesa, GLuint nr )
    if (rmesa->dma.flush)
       rmesa->dma.flush( rmesa );
 
+   radeonEnsureCmdBufSpace(rmesa, AOS_BUFSZ(rmesa->tcl.nr_aos_components) +
+                          rmesa->hw.max_state_size + ELTS_BUFSZ(nr));
+
    radeonEmitAOS( rmesa,
                rmesa->tcl.aos_components,
                rmesa->tcl.nr_aos_components, 0 );
@@ -175,6 +178,9 @@ static void EMIT_PRIM( GLcontext *ctx,
    radeonContextPtr rmesa = RADEON_CONTEXT( ctx );
    radeonTclPrimitive( ctx, prim, hwprim );
    
+   radeonEnsureCmdBufSpace( rmesa, AOS_BUFSZ(rmesa->tcl.nr_aos_components) +
+                           rmesa->hw.max_state_size + VBUF_BUFSZ );
+
    radeonEmitAOS( rmesa,
                  rmesa->tcl.aos_components,
                  rmesa->tcl.nr_aos_components,