Commit b03460d9 authored by marko's avatar marko

branches/zip: Move the page flushing fields from buf_block_t to

buf_page_t and page_zip_des_t.
parent 0d7111c9
......@@ -863,7 +863,7 @@ buf_pool_init(void)
-------------------------------- */
UT_LIST_INIT(buf_pool->flush_list);
for (i = BUF_FLUSH_LRU; i <= BUF_FLUSH_LIST; i++) {
for (i = BUF_FLUSH_LRU; i < BUF_FLUSH_N_TYPES; i++) {
buf_pool->n_flush[i] = 0;
buf_pool->init_flush[i] = FALSE;
buf_pool->no_flush[i] = os_event_create(NULL);
......@@ -1855,8 +1855,8 @@ buf_page_init(
block->freed_page_clock = 0;
block->newest_modification = 0;
block->oldest_modification = 0;
block->page.newest_modification = 0;
block->page.oldest_modification = 0;
block->accessed = FALSE;
block->buf_fix_count = 0;
......
......@@ -49,19 +49,19 @@ Inserts a modified block into the flush list. */
void
buf_flush_insert_into_flush_list(
/*=============================*/
buf_block_t* block) /* in: block which is modified */
buf_page_t* bpage) /* in: block which is modified */
{
#ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&(buf_pool->mutex)));
#endif /* UNIV_SYNC_DEBUG */
ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
ut_a(buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE);
ut_ad((UT_LIST_GET_FIRST(buf_pool->flush_list) == NULL)
|| (UT_LIST_GET_FIRST(buf_pool->flush_list)->oldest_modification
<= block->oldest_modification));
<= bpage->oldest_modification));
UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, block);
UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, bpage);
#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG
ut_a(buf_flush_validate_low());
......@@ -76,10 +76,10 @@ necessarily come in the order of lsn's. */
void
buf_flush_insert_sorted_into_flush_list(
/*====================================*/
buf_block_t* block) /* in: block which is modified */
buf_page_t* bpage) /* in: block which is modified */
{
buf_block_t* prev_b;
buf_block_t* b;
buf_page_t* prev_b;
buf_page_t* b;
#ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&(buf_pool->mutex)));
......@@ -88,16 +88,16 @@ buf_flush_insert_sorted_into_flush_list(
prev_b = NULL;
b = UT_LIST_GET_FIRST(buf_pool->flush_list);
while (b && b->oldest_modification > block->oldest_modification) {
while (b && b->oldest_modification > bpage->oldest_modification) {
prev_b = b;
b = UT_LIST_GET_NEXT(flush_list, b);
}
if (prev_b == NULL) {
UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, block);
UT_LIST_ADD_FIRST(flush_list, buf_pool->flush_list, bpage);
} else {
UT_LIST_INSERT_AFTER(flush_list, buf_pool->flush_list, prev_b,
block);
bpage);
}
#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG
......@@ -131,7 +131,7 @@ buf_flush_ready_for_replace(
return(FALSE);
}
return(block->oldest_modification == 0
return(block->page.oldest_modification == 0
&& block->buf_fix_count == 0
&& block->io_fix == 0);
}
......@@ -145,7 +145,7 @@ buf_flush_ready_for_flush(
/* out: TRUE if can flush immediately */
buf_block_t* block, /* in: buffer control block, must be in state
BUF_BLOCK_FILE_PAGE */
ulint flush_type)/* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
enum buf_flush flush_type)/* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
{
#ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&(buf_pool->mutex)));
......@@ -153,7 +153,7 @@ buf_flush_ready_for_flush(
#endif /* UNIV_SYNC_DEBUG */
ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
if (block->oldest_modification != 0 && block->io_fix == 0) {
if (block->page.oldest_modification != 0 && block->io_fix == 0) {
if (flush_type != BUF_FLUSH_LRU) {
return(TRUE);
......@@ -179,21 +179,24 @@ buf_flush_write_complete(
/*=====================*/
buf_block_t* block) /* in: pointer to the block in question */
{
enum buf_flush flush_type;
ut_ad(block);
#ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&(buf_pool->mutex)));
#endif /* UNIV_SYNC_DEBUG */
ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
block->oldest_modification = 0;
block->page.oldest_modification = 0;
UT_LIST_REMOVE(flush_list, buf_pool->flush_list, block);
UT_LIST_REMOVE(flush_list, buf_pool->flush_list, &(block->page));
ut_d(UT_LIST_VALIDATE(flush_list, buf_block_t, buf_pool->flush_list));
buf_pool->n_flush[block->flush_type]--;
flush_type = buf_page_get_flush_type(&block->page);
buf_pool->n_flush[flush_type]--;
if (block->flush_type == BUF_FLUSH_LRU) {
if (flush_type == BUF_FLUSH_LRU) {
/* Put the block to the end of the LRU list to wait to be
moved to the free list */
......@@ -203,14 +206,14 @@ buf_flush_write_complete(
}
/* fprintf(stderr, "n pending flush %lu\n",
buf_pool->n_flush[block->flush_type]); */
buf_pool->n_flush[flush_type]); */
if ((buf_pool->n_flush[block->flush_type] == 0)
&& (buf_pool->init_flush[block->flush_type] == FALSE)) {
if ((buf_pool->n_flush[flush_type] == 0)
&& (buf_pool->init_flush[flush_type] == FALSE)) {
/* The running flush batch has ended */
os_event_set(buf_pool->no_flush[block->flush_type]);
os_event_set(buf_pool->no_flush[flush_type]);
}
}
......@@ -582,7 +585,7 @@ buf_flush_write_block_low(
ut_a(ibuf_count_get(buf_block_get_space(block),
buf_block_get_page_no(block)) == 0);
#endif
ut_ad(block->newest_modification != 0);
ut_ad(block->page.newest_modification != 0);
#ifdef UNIV_LOG_DEBUG
if (!univ_log_debug_warned) {
......@@ -594,11 +597,12 @@ buf_flush_write_block_low(
}
#else
/* Force the log to the disk before writing the modified block */
log_write_up_to(block->newest_modification, LOG_WAIT_ALL_GROUPS, TRUE);
log_write_up_to(block->page.newest_modification,
LOG_WAIT_ALL_GROUPS, TRUE);
#endif
buf_flush_init_for_writing(block->frame,
buf_block_get_page_zip(block),
block->newest_modification);
block->page.newest_modification);
if (!srv_use_doublewrite_buf || !trx_doublewrite) {
ulint zip_size = buf_block_get_zip_size(block);
......@@ -621,11 +625,12 @@ static
ulint
buf_flush_try_page(
/*===============*/
/* out: 1 if a page was flushed, 0 otherwise */
/* out: 1 if a page was
flushed, 0 otherwise */
ulint space, /* in: space id */
ulint offset, /* in: page offset */
ulint flush_type) /* in: BUF_FLUSH_LRU, BUF_FLUSH_LIST, or
BUF_FLUSH_SINGLE_PAGE */
enum buf_flush flush_type) /* in: BUF_FLUSH_LRU, BUF_FLUSH_LIST,
or BUF_FLUSH_SINGLE_PAGE */
{
buf_block_t* block;
ibool locked;
......@@ -651,7 +656,7 @@ buf_flush_try_page(
block->io_fix = BUF_IO_WRITE;
block->flush_type = flush_type;
buf_page_set_flush_type(&block->page, flush_type);
if (buf_pool->n_flush[flush_type] == 0) {
......@@ -707,7 +712,7 @@ buf_flush_try_page(
block->io_fix = BUF_IO_WRITE;
block->flush_type = flush_type;
buf_page_set_flush_type(&block->page, flush_type);
if (buf_pool->n_flush[flush_type] == 0) {
......@@ -734,11 +739,11 @@ buf_flush_try_page(
block->io_fix = BUF_IO_WRITE;
block->flush_type = flush_type;
buf_page_set_flush_type(&block->page, flush_type);
if (buf_pool->n_flush[block->flush_type] == 0) {
if (buf_pool->n_flush[flush_type] == 0) {
os_event_reset(buf_pool->no_flush[block->flush_type]);
os_event_reset(buf_pool->no_flush[flush_type]);
}
buf_pool->n_flush[flush_type]++;
......@@ -778,7 +783,8 @@ buf_flush_try_neighbors(
/* out: number of pages flushed */
ulint space, /* in: space id */
ulint offset, /* in: page offset */
ulint flush_type) /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
enum buf_flush flush_type) /* in: BUF_FLUSH_LRU or
BUF_FLUSH_LIST */
{
buf_block_t* block;
ulint low, high;
......@@ -875,7 +881,7 @@ buf_flush_batch(
write request was queued;
ULINT_UNDEFINED if there was a flush
of the same type already running */
ulint flush_type, /* in: BUF_FLUSH_LRU or
enum buf_flush flush_type, /* in: BUF_FLUSH_LRU or
BUF_FLUSH_LIST; if BUF_FLUSH_LIST,
then the caller must not own any
latches on pages */
......@@ -932,7 +938,7 @@ buf_flush_batch(
block = UT_LIST_GET_LAST(buf_pool->flush_list);
if (!block
|| block->oldest_modification >= lsn_limit) {
|| block->page.oldest_modification >= lsn_limit) {
/* We have flushed enough */
break;
......@@ -983,7 +989,8 @@ buf_flush_batch(
mutex_exit(&block->mutex);
block = UT_LIST_GET_PREV(flush_list, block);
block = UT_LIST_GET_PREV(flush_list,
(&block->page));
}
}
......@@ -1032,7 +1039,7 @@ Waits until a flush batch of the given type ends */
void
buf_flush_wait_batch_end(
/*=====================*/
ulint type) /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
enum buf_flush type) /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
{
ut_ad((type == BUF_FLUSH_LRU) || (type == BUF_FLUSH_LIST));
......@@ -1125,22 +1132,22 @@ buf_flush_validate_low(void)
/*========================*/
/* out: TRUE if ok */
{
buf_block_t* block;
buf_page_t* bpage;
ib_ulonglong om;
UT_LIST_VALIDATE(flush_list, buf_block_t, buf_pool->flush_list);
UT_LIST_VALIDATE(flush_list, buf_page_t, buf_pool->flush_list);
block = UT_LIST_GET_FIRST(buf_pool->flush_list);
bpage = UT_LIST_GET_FIRST(buf_pool->flush_list);
while (block != NULL) {
om = block->oldest_modification;
ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
while (bpage != NULL) {
om = bpage->oldest_modification;
ut_a(buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE);
ut_a(om > 0);
block = UT_LIST_GET_NEXT(flush_list, block);
bpage = UT_LIST_GET_NEXT(flush_list, bpage);
if (block) {
ut_a(om >= block->oldest_modification);
if (bpage) {
ut_a(om >= bpage->oldest_modification);
}
}
......
......@@ -130,14 +130,15 @@ scan_again:
goto scan_again;
}
if (block->oldest_modification != 0) {
if (block->page.oldest_modification != 0) {
/* Remove from the flush list of modified
blocks */
block->oldest_modification = 0;
block->page.oldest_modification = 0;
UT_LIST_REMOVE(flush_list,
buf_pool->flush_list, block);
buf_pool->flush_list,
&(block->page));
}
/* Remove from the LRU list */
......@@ -913,7 +914,7 @@ buf_LRU_block_remove_hashed_page(
ut_a(buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE);
ut_a(block->io_fix == 0);
ut_a(block->buf_fix_count == 0);
ut_a(block->oldest_modification == 0);
ut_a(block->page.oldest_modification == 0);
buf_LRU_remove_block(block);
......@@ -1091,7 +1092,7 @@ buf_LRU_print(void)
fprintf(stderr, "io_fix %lu ", (ulong) block->io_fix);
}
if (block->oldest_modification) {
if (block->page.oldest_modification) {
fputs("modif. ", stderr);
}
......
......@@ -33,13 +33,6 @@ Created 11/5/1995 Heikki Tuuri
#include "os0proc.h"
#include "page0types.h"
/* Flags for flush types */
#define BUF_FLUSH_LRU 1
#define BUF_FLUSH_SINGLE_PAGE 2
#define BUF_FLUSH_LIST 3 /* An array in the pool struct
has size BUF_FLUSH_LIST + 1: if you
add more flush types, put them in
the middle! */
/* Modes for buf_page_get_gen */
#define BUF_GET 10 /* get always */
#define BUF_GET_IF_IN_POOL 11 /* get if in pool */
......@@ -541,6 +534,14 @@ buf_block_dbg_add_level(
Gets the state of a block. */
UNIV_INLINE
enum buf_page_state
buf_page_get_state(
/*===============*/
/* out: state */
const buf_page_t* bpage); /* in: pointer to the control block */
/*************************************************************************
Gets the state of a block. */
UNIV_INLINE
enum buf_page_state
buf_block_get_state(
/*================*/
/* out: state */
......@@ -555,6 +556,23 @@ buf_block_set_state(
buf_block_t* block, /* in/out: pointer to control block */
enum buf_page_state state); /* in: state */
/*************************************************************************
Get the flush type of a page. */
UNIV_INLINE
enum buf_flush
buf_page_get_flush_type(
/*====================*/
/* out: flush type */
const buf_page_t* bpage) /* in: buffer page */
__attribute__((pure));
/*************************************************************************
Set the flush type of a page. */
UNIV_INLINE
void
buf_page_set_flush_type(
/*====================*/
buf_page_t* bpage, /* in: buffer page */
enum buf_flush flush_type); /* in: flush type */
/*************************************************************************
Map a block to a file page. */
UNIV_INLINE
void
......@@ -713,7 +731,24 @@ struct buf_page_struct{
ulint space:32; /* tablespace id */
ulint offset:32; /* page number */
page_zip_des_t zip; /* compressed page; zip.state
is relevant for all pages */
and zip.flush_type are relevant
for all pages */
/* 2. Page flushing fields; protected by buf_pool->mutex */
UT_LIST_NODE_T(buf_page_t) flush_list;
/* node of the modified, not yet
flushed blocks list */
ib_ulonglong newest_modification;
/* log sequence number of the youngest
modification to this block, zero if
not modified */
ib_ulonglong oldest_modification;
/* log sequence number of the START of
the log entry written of the oldest
modification to this block which has
not yet been flushed on disk; zero if
all modifications are on disk */
};
/* The buffer control block structure */
......@@ -750,25 +785,6 @@ struct buf_block_struct{
buffer pool which are index pages,
but this flag is not set because
we do not keep track of all pages */
/* 2. Page flushing fields */
UT_LIST_NODE_T(buf_block_t) flush_list;
/* node of the modified, not yet
flushed blocks list */
ib_ulonglong newest_modification;
/* log sequence number of the youngest
modification to this block, zero if
not modified */
ib_ulonglong oldest_modification;
/* log sequence number of the START of
the log entry written of the oldest
modification to this block which has
not yet been flushed on disk; zero if
all modifications are on disk */
ulint flush_type; /* if this block is currently being
flushed to disk, this tells the
flush_type: BUF_FLUSH_LRU or
BUF_FLUSH_LIST */
/* 3. LRU replacement algorithm fields */
......@@ -921,16 +937,16 @@ struct buf_pool_struct{
the pool with no read */
/* 2. Page flushing algorithm fields */
UT_LIST_BASE_NODE_T(buf_block_t) flush_list;
UT_LIST_BASE_NODE_T(buf_page_t) flush_list;
/* base node of the modified block
list */
ibool init_flush[BUF_FLUSH_LIST + 1];
ibool init_flush[BUF_FLUSH_N_TYPES];
/* this is TRUE when a flush of the
given type is being initialized */
ulint n_flush[BUF_FLUSH_LIST + 1];
ulint n_flush[BUF_FLUSH_N_TYPES];
/* this is the number of pending
writes in the given flush type */
os_event_t no_flush[BUF_FLUSH_LIST + 1];
os_event_t no_flush[BUF_FLUSH_N_TYPES];
/* this is in the set state when there
is no flush batch of the given type
running */
......
......@@ -47,17 +47,17 @@ buf_pool_get_oldest_modification(void)
/* out: oldest modification in pool,
zero if none */
{
buf_block_t* block;
buf_page_t* bpage;
ib_ulonglong lsn;
mutex_enter(&(buf_pool->mutex));
block = UT_LIST_GET_LAST(buf_pool->flush_list);
bpage = UT_LIST_GET_LAST(buf_pool->flush_list);
if (block == NULL) {
if (bpage == NULL) {
lsn = 0;
} else {
lsn = block->oldest_modification;
lsn = bpage->oldest_modification;
}
mutex_exit(&(buf_pool->mutex));
......@@ -87,12 +87,12 @@ buf_pool_clock_tic(void)
Gets the state of a block. */
UNIV_INLINE
enum buf_page_state
buf_block_get_state(
/*================*/
buf_page_get_state(
/*===============*/
/* out: state */
const buf_block_t* block) /* in: pointer to the control block */
const buf_page_t* bpage) /* in: pointer to the control block */
{
enum buf_page_state state = block->page.zip.state;
enum buf_page_state state = bpage->zip.state;
#ifdef UNIV_DEBUG
switch (state) {
......@@ -111,6 +111,17 @@ buf_block_get_state(
return(state);
}
/*************************************************************************
Gets the state of a block. */
UNIV_INLINE
enum buf_page_state
buf_block_get_state(
/*================*/
/* out: state */
const buf_block_t* block) /* in: pointer to the control block */
{
return(buf_page_get_state(&block->page));
}
/*************************************************************************
Sets the state of a block. */
UNIV_INLINE
void
......@@ -149,6 +160,43 @@ buf_block_set_state(
block->page.zip.state = state;
ut_ad(buf_block_get_state(block) == state);
}
/*************************************************************************
Get the flush type of a page. */
UNIV_INLINE
enum buf_flush
buf_page_get_flush_type(
/*====================*/
/* out: flush type */
const buf_page_t* bpage) /* in: buffer page */
{
enum buf_flush flush_type = bpage->zip.flush_type;
#ifdef UNIV_DEBUG
switch (flush_type) {
case BUF_FLUSH_LRU:
case BUF_FLUSH_SINGLE_PAGE:
case BUF_FLUSH_LIST:
return(flush_type);
case BUF_FLUSH_N_TYPES:
}
ut_error;
#endif /* UNIV_DEBUG */
return(flush_type);
}
/*************************************************************************
Set the flush type of a page. */
UNIV_INLINE
void
buf_page_set_flush_type(
/*====================*/
buf_page_t* bpage, /* in: buffer page */
enum buf_flush flush_type) /* in: flush type */
{
bpage->zip.flush_type = flush_type;
ut_ad(buf_page_get_flush_type(bpage) == flush_type);
}
/*************************************************************************
Map a block to a file page. */
UNIV_INLINE
......@@ -422,7 +470,7 @@ buf_block_get_newest_modification(
mutex_enter(&(buf_pool->mutex));
if (buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE) {
lsn = block->newest_modification;
lsn = block->page.newest_modification;
} else {
lsn = 0;
}
......
......@@ -52,7 +52,7 @@ buf_flush_batch(
write request was queued;
ULINT_UNDEFINED if there was a flush
of the same type already running */
ulint flush_type, /* in: BUF_FLUSH_LRU or
enum buf_flush flush_type, /* in: BUF_FLUSH_LRU or
BUF_FLUSH_LIST; if BUF_FLUSH_LIST,
then the caller must not own any
latches on pages */
......@@ -70,7 +70,7 @@ Waits until a flush batch of the given type ends */
void
buf_flush_wait_batch_end(
/*=====================*/
ulint type); /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
enum buf_flush type); /* in: BUF_FLUSH_LRU or BUF_FLUSH_LIST */
/************************************************************************
This function should be called at a mini-transaction commit, if a page was
modified in it. Puts the block to the list of modified blocks, if it not
......
......@@ -15,7 +15,7 @@ Inserts a modified block into the flush list. */
void
buf_flush_insert_into_flush_list(
/*=============================*/
buf_block_t* block); /* in: block which is modified */
buf_page_t* block); /* in: block which is modified */
/************************************************************************
Inserts a modified block into the flush list in the right sorted position.
This function is used by recovery, because there the modifications do not
......@@ -24,7 +24,7 @@ necessarily come in the order of lsn's. */
void
buf_flush_insert_sorted_into_flush_list(
/*====================================*/
buf_block_t* block); /* in: block which is modified */
buf_page_t* block); /* in: block which is modified */
/************************************************************************
This function should be called at a mini-transaction commit, if a page was
......@@ -49,16 +49,16 @@ buf_flush_note_modification(
ut_ad(mtr->modifications);
ut_ad(block->newest_modification <= mtr->end_lsn);
block->newest_modification = mtr->end_lsn;
block->page.newest_modification = mtr->end_lsn;
if (!block->oldest_modification) {
if (!block->page.oldest_modification) {
block->oldest_modification = mtr->start_lsn;
ut_ad(block->oldest_modification != 0);
block->page.oldest_modification = mtr->start_lsn;
ut_ad(block->page.oldest_modification != 0);
buf_flush_insert_into_flush_list(block);
buf_flush_insert_into_flush_list(&block->page);
} else {
ut_ad(block->oldest_modification <= mtr->start_lsn);
ut_ad(block->page.oldest_modification <= mtr->start_lsn);
}
++srv_buf_pool_write_requests;
......@@ -85,19 +85,19 @@ buf_flush_recv_note_modification(
mutex_enter(&(buf_pool->mutex));
ut_ad(block->newest_modification <= end_lsn);
ut_ad(block->page.newest_modification <= end_lsn);
block->newest_modification = end_lsn;
block->page.newest_modification = end_lsn;
if (!block->oldest_modification) {
if (!block->page.oldest_modification) {
block->oldest_modification = start_lsn;
block->page.oldest_modification = start_lsn;
ut_ad(block->oldest_modification != 0);
ut_ad(block->page.oldest_modification != 0);
buf_flush_insert_sorted_into_flush_list(block);
buf_flush_insert_sorted_into_flush_list(&block->page);
} else {
ut_ad(block->oldest_modification <= start_lsn);
ut_ad(block->page.oldest_modification <= start_lsn);
}
mutex_exit(&(buf_pool->mutex));
......
......@@ -17,6 +17,13 @@ typedef struct buf_pool_struct buf_pool_t;
/* The 'type' used of a buffer frame */
typedef byte buf_frame_t;
/* Flags for flush types */
enum buf_flush {
BUF_FLUSH_LRU = 0,
BUF_FLUSH_SINGLE_PAGE,
BUF_FLUSH_LIST,
BUF_FLUSH_N_TYPES /* index of last element + 1 */
};
#endif
......@@ -33,7 +33,11 @@ struct page_zip_des_struct
page_zip_t* data; /* compressed page data */
ulint state:3; /* state of the control block
(cf. enum buf_page_state) */
ulint :11; /* reserved */
ulint flush_type:2; /* if this block is currently being
flushed to disk, this tells the
flush_type (cf. enum buf_flush);
protected by block->mutex */
ulint :9; /* reserved */
ulint n_blobs:12; /* number of externally stored
columns on the page; the maximum
is 744 on a 16 KiB page */
......
......@@ -28,7 +28,7 @@ page_zip_get_size(
/*==============*/
/* out: size in bytes */
const page_zip_des_t* page_zip) /* in: compressed page */
__attribute__((nonnull, const));
__attribute__((nonnull, pure));
/**************************************************************************
Set the size of a compressed page in bytes. */
UNIV_INLINE
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment