Adopted data-backed cache in lfs3_file_t to avoid undefined behavior
This fixes a strict aliasing violation in lfs3_file_sync_, where we cast
the file cache -> lfs3_data_t to avoid an extra stack allocation, by
modifying the file's cache struct to use an lfs3_data_t directly.
- file.cache.pos -> file.cache.pos
- file.cache.buffer -> file.cache.d.u.buffer_
- file.cache.size -> file.cache.d.size
- (const lfs3_data_t*)&file->cache -> &file->cache.d
Note the underscore_ in file.cache.d.u.buffer_. This did not fit
together as well as I had hoped, due to different const expectation
between the file cache and lfs3_data_t.
Up until this point lfs3_data_t has only been used to refer to const
data (ignoring side-band pointer casting in lfs3_mtree_traverse*), while
the file cache very much contains mutable data. To work around this I
added data.u.buffer_ as a mutable variant, which works, but risks an
accidental const violation in the future.
---
Unfortunately this does come with a minor RAM cost, since we no longer
hide file.cache.pos in lfs3_data_t's buffer padding:
code stack ctx
before: 36844 2368 684
after: 36832 (-0.0%) 2376 (+0.3%) 684 (+0.0%)
lfs3_file_t before: 164
lfs3_file_t after: 168 (+2.4%)
I think it's pretty fair to call C's strict aliasing rules a real wet
blanket. It would be interesting to create a -fno-strict-aliasing
variant of littlefs in the future, to see how much code/RAM could be
saved if we were given free reign to abuse the available memory.
Probably not enough to justify the extra work, but it would be an
interesting experiment.
This commit is contained in:
@@ -11586,7 +11586,7 @@ static inline void lfs3_file_discardcache(lfs3_file_t *file) {
|
||||
#ifndef LFS3_KVONLY
|
||||
file->cache.pos = 0;
|
||||
#endif
|
||||
file->cache.size = 0;
|
||||
file->cache.d.size = 0;
|
||||
}
|
||||
|
||||
#ifndef LFS3_KVONLY
|
||||
@@ -11611,7 +11611,7 @@ static inline lfs3_size_t lfs3_file_cachesize(lfs3_t *lfs3,
|
||||
|
||||
static inline lfs3_off_t lfs3_file_size_(const lfs3_file_t *file) {
|
||||
return lfs3_max(
|
||||
LFS3_IFDEF_KVONLY(0, file->cache.pos) + file->cache.size,
|
||||
LFS3_IFDEF_KVONLY(0, file->cache.pos) + file->cache.d.size,
|
||||
file->b.shrub.r.weight);
|
||||
}
|
||||
|
||||
@@ -11729,17 +11729,17 @@ int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
// the file cache, so make sure not to clobber it
|
||||
if (lfs3_o_iswrset(file->b.h.flags)) {
|
||||
file->b.h.flags |= LFS3_o_UNFLUSH;
|
||||
file->cache.buffer = file->cfg->cache_buffer;
|
||||
file->cache.d.u.buffer_ = file->cfg->cache_buffer;
|
||||
#ifndef LFS3_KVONLY
|
||||
file->cache.pos = 0;
|
||||
#endif
|
||||
file->cache.size = file->cfg->cache_size;
|
||||
file->cache.d.size = file->cfg->cache_size;
|
||||
} else if (file->cfg->cache_buffer) {
|
||||
file->cache.buffer = file->cfg->cache_buffer;
|
||||
file->cache.d.u.buffer_ = file->cfg->cache_buffer;
|
||||
} else {
|
||||
#ifndef LFS3_KVONLY
|
||||
file->cache.buffer = lfs3_malloc(lfs3_file_cachesize(lfs3, file));
|
||||
if (!file->cache.buffer) {
|
||||
file->cache.d.u.buffer_ = lfs3_malloc(lfs3_file_cachesize(lfs3, file));
|
||||
if (!file->cache.d.u.buffer_) {
|
||||
return LFS3_ERR_NOMEM;
|
||||
}
|
||||
#else
|
||||
@@ -11821,9 +11821,9 @@ int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
// small file wrset? can we atomically commit everything in one
|
||||
// commit? currently this is only possible via lfs3_set
|
||||
if (lfs3_o_iswrset(file->b.h.flags)
|
||||
&& file->cache.size <= lfs3->cfg->inline_size
|
||||
&& file->cache.size <= lfs3->cfg->fragment_size
|
||||
&& file->cache.size < lfs3->cfg->crystal_thresh) {
|
||||
&& file->cache.d.size <= lfs3->cfg->inline_size
|
||||
&& file->cache.d.size <= lfs3->cfg->fragment_size
|
||||
&& file->cache.d.size < lfs3->cfg->crystal_thresh) {
|
||||
// we need to mark as unsync for sync to do anything
|
||||
file->b.h.flags |= LFS3_o_UNSYNC;
|
||||
|
||||
@@ -11944,7 +11944,7 @@ static void lfs3_file_close_(lfs3_t *lfs3, const lfs3_file_t *file) {
|
||||
(void)lfs3;
|
||||
// clean up memory
|
||||
if (!file->cfg->cache_buffer) {
|
||||
lfs3_free(file->cache.buffer);
|
||||
lfs3_free(file->cache.d.u.buffer_);
|
||||
}
|
||||
|
||||
// are we orphaning a file?
|
||||
@@ -12165,14 +12165,14 @@ lfs3_ssize_t lfs3_file_read(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
lfs3_ssize_t d = lfs3_min(size, lfs3_file_size_(file) - pos_);
|
||||
|
||||
// any data in our cache?
|
||||
if (pos_ < file->cache.pos + file->cache.size
|
||||
&& file->cache.size != 0) {
|
||||
if (pos_ < file->cache.pos + file->cache.d.size
|
||||
&& file->cache.d.size != 0) {
|
||||
if (pos_ >= file->cache.pos) {
|
||||
lfs3_ssize_t d_ = lfs3_min(
|
||||
d,
|
||||
file->cache.size - (pos_ - file->cache.pos));
|
||||
file->cache.d.size - (pos_ - file->cache.pos));
|
||||
lfs3_memcpy(buffer_,
|
||||
&file->cache.buffer[pos_ - file->cache.pos],
|
||||
&file->cache.d.u.buffer_[pos_ - file->cache.pos],
|
||||
d_);
|
||||
|
||||
pos_ += d_;
|
||||
@@ -12207,13 +12207,13 @@ lfs3_ssize_t lfs3_file_read(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
// try to fill our cache with some data
|
||||
if (!lfs3_o_isunflush(file->b.h.flags)) {
|
||||
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
|
||||
pos_, file->cache.buffer, d);
|
||||
pos_, file->cache.d.u.buffer_, d);
|
||||
if (d_ < 0) {
|
||||
LFS3_ASSERT(d != LFS3_ERR_NOENT);
|
||||
return d_;
|
||||
}
|
||||
file->cache.pos = pos_;
|
||||
file->cache.size = d_;
|
||||
file->cache.d.size = d_;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -13338,10 +13338,10 @@ lfs3_ssize_t lfs3_file_write(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
// note we need to clear the cache anyways to avoid any
|
||||
// out-of-date data
|
||||
file->cache.pos = pos + size - lfs3_file_cachesize(lfs3, file);
|
||||
lfs3_memcpy(file->cache.buffer,
|
||||
lfs3_memcpy(file->cache.d.u.buffer_,
|
||||
&buffer_[size - lfs3_file_cachesize(lfs3, file)],
|
||||
lfs3_file_cachesize(lfs3, file));
|
||||
file->cache.size = lfs3_file_cachesize(lfs3, file);
|
||||
file->cache.d.size = lfs3_file_cachesize(lfs3, file);
|
||||
|
||||
file->b.h.flags &= ~LFS3_o_UNFLUSH;
|
||||
written += size;
|
||||
@@ -13363,25 +13363,25 @@ lfs3_ssize_t lfs3_file_write(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
//
|
||||
if (!lfs3_o_isunflush(file->b.h.flags)
|
||||
|| (pos >= file->cache.pos
|
||||
&& pos <= file->cache.pos + file->cache.size
|
||||
&& pos <= file->cache.pos + file->cache.d.size
|
||||
&& pos
|
||||
< file->cache.pos
|
||||
+ lfs3_file_cachesize(lfs3, file))) {
|
||||
// unused cache? we can move it where we need it
|
||||
if (!lfs3_o_isunflush(file->b.h.flags)) {
|
||||
file->cache.pos = pos;
|
||||
file->cache.size = 0;
|
||||
file->cache.d.size = 0;
|
||||
}
|
||||
|
||||
lfs3_size_t d = lfs3_min(
|
||||
size,
|
||||
lfs3_file_cachesize(lfs3, file)
|
||||
- (pos - file->cache.pos));
|
||||
lfs3_memcpy(&file->cache.buffer[pos - file->cache.pos],
|
||||
lfs3_memcpy(&file->cache.d.u.buffer_[pos - file->cache.pos],
|
||||
buffer_,
|
||||
d);
|
||||
file->cache.size = lfs3_max(
|
||||
file->cache.size,
|
||||
file->cache.d.size = lfs3_max(
|
||||
file->cache.d.size,
|
||||
pos+d - file->cache.pos);
|
||||
|
||||
file->b.h.flags |= LFS3_o_UNFLUSH;
|
||||
@@ -13394,7 +13394,7 @@ lfs3_ssize_t lfs3_file_write(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
|
||||
// flush our cache so the above can't fail
|
||||
err = lfs3_file_flush_(lfs3, file,
|
||||
file->cache.pos, file->cache.buffer, file->cache.size);
|
||||
file->cache.pos, file->cache.d.u.buffer_, file->cache.d.size);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
@@ -13458,13 +13458,13 @@ int lfs3_file_flush(lfs3_t *lfs3, lfs3_file_t *file) {
|
||||
if (lfs3_o_isunflush(file->b.h.flags)) {
|
||||
#ifdef LFS3_KVONLY
|
||||
err = lfs3_file_flushset_(lfs3, file,
|
||||
file->cache.buffer, file->cache.size);
|
||||
file->cache.d.u.buffer_, file->cache.d.size);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
#else
|
||||
err = lfs3_file_flush_(lfs3, file,
|
||||
file->cache.pos, file->cache.buffer, file->cache.size);
|
||||
file->cache.pos, file->cache.d.u.buffer_, file->cache.d.size);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
@@ -13547,7 +13547,7 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
#ifndef LFS3_KVONLY
|
||||
LFS3_ASSERT(file->cache.pos == 0);
|
||||
#endif
|
||||
LFS3_ASSERT(file->cache.size == lfs3_file_size_(file));
|
||||
LFS3_ASSERT(file->cache.d.size == lfs3_file_size_(file));
|
||||
|
||||
// discard any lingering bshrub state
|
||||
#ifndef LFS3_KVONLY
|
||||
@@ -13556,10 +13556,10 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
lfs3_file_discardbshrub(file);
|
||||
|
||||
// build a small shrub commit
|
||||
if (file->cache.size > 0) {
|
||||
if (file->cache.d.size > 0) {
|
||||
shrub_rattrs[shrub_rattr_count++] = LFS3_RATTR_DATA(
|
||||
LFS3_TAG_DATA, +file->cache.size,
|
||||
(const lfs3_data_t*)&file->cache);
|
||||
LFS3_TAG_DATA, +file->cache.d.size,
|
||||
&file->cache.d);
|
||||
|
||||
LFS3_ASSERT(shrub_rattr_count
|
||||
<= sizeof(shrub_rattrs)/sizeof(lfs3_rattr_t));
|
||||
@@ -13695,14 +13695,14 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
//
|
||||
// note we need to be careful if caches have different
|
||||
// sizes, prefer the most recent data in this case
|
||||
lfs3_size_t d = file->cache.size - lfs3_min(
|
||||
lfs3_size_t d = file->cache.d.size - lfs3_min(
|
||||
lfs3_file_cachesize(lfs3, file_),
|
||||
file->cache.size);
|
||||
file->cache.d.size);
|
||||
file_->cache.pos = file->cache.pos + d;
|
||||
lfs3_memcpy(file_->cache.buffer,
|
||||
file->cache.buffer + d,
|
||||
file->cache.size - d);
|
||||
file_->cache.size = file->cache.size - d;
|
||||
lfs3_memcpy(file_->cache.d.u.buffer_,
|
||||
file->cache.d.u.buffer_ + d,
|
||||
file->cache.d.size - d);
|
||||
file_->cache.d.size = file->cache.d.size - d;
|
||||
|
||||
// update any custom attrs
|
||||
for (lfs3_size_t i = 0; i < file->cfg->attr_count; i++) {
|
||||
@@ -13778,10 +13778,10 @@ int lfs3_file_sync(lfs3_t *lfs3, lfs3_file_t *file) {
|
||||
// though don't flush quite yet if our file is small and can be
|
||||
// combined with sync in a single commit
|
||||
int err;
|
||||
if (!(file->cache.size == lfs3_file_size_(file)
|
||||
&& file->cache.size <= lfs3->cfg->inline_size
|
||||
&& file->cache.size <= lfs3->cfg->fragment_size
|
||||
&& file->cache.size < lfs3->cfg->crystal_thresh)) {
|
||||
if (!(file->cache.d.size == lfs3_file_size_(file)
|
||||
&& file->cache.d.size <= lfs3->cfg->inline_size
|
||||
&& file->cache.d.size <= lfs3->cfg->fragment_size
|
||||
&& file->cache.d.size < lfs3->cfg->crystal_thresh)) {
|
||||
err = lfs3_file_flush(lfs3, file);
|
||||
if (err) {
|
||||
goto failed;
|
||||
@@ -13991,12 +13991,12 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
|
||||
}
|
||||
|
||||
// truncate our cache
|
||||
file->cache.size = lfs3_min(
|
||||
file->cache.size,
|
||||
file->cache.d.size = lfs3_min(
|
||||
file->cache.d.size,
|
||||
size_ - lfs3_min(file->cache.pos, size_));
|
||||
file->cache.pos = lfs3_min(file->cache.pos, size_);
|
||||
// mark as flushed if this completely truncates our cache
|
||||
if (file->cache.size == 0) {
|
||||
if (file->cache.d.size == 0) {
|
||||
lfs3_file_discardcache(file);
|
||||
}
|
||||
|
||||
@@ -14073,27 +14073,27 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
|
||||
}
|
||||
|
||||
// fruncate our cache
|
||||
lfs3_memmove(file->cache.buffer,
|
||||
&file->cache.buffer[lfs3_min(
|
||||
lfs3_memmove(file->cache.d.u.buffer_,
|
||||
&file->cache.d.u.buffer_[lfs3_min(
|
||||
lfs3_smax(
|
||||
size - size_ - file->cache.pos,
|
||||
0),
|
||||
file->cache.size)],
|
||||
file->cache.size - lfs3_min(
|
||||
file->cache.d.size)],
|
||||
file->cache.d.size - lfs3_min(
|
||||
lfs3_smax(
|
||||
size - size_ - file->cache.pos,
|
||||
0),
|
||||
file->cache.size));
|
||||
file->cache.size -= lfs3_min(
|
||||
file->cache.d.size));
|
||||
file->cache.d.size -= lfs3_min(
|
||||
lfs3_smax(
|
||||
size - size_ - file->cache.pos,
|
||||
0),
|
||||
file->cache.size);
|
||||
file->cache.d.size);
|
||||
file->cache.pos -= lfs3_smin(
|
||||
size - size_,
|
||||
file->cache.pos);
|
||||
// mark as flushed if this completely truncates our cache
|
||||
if (file->cache.size == 0) {
|
||||
if (file->cache.d.size == 0) {
|
||||
lfs3_file_discardcache(file);
|
||||
}
|
||||
|
||||
|
||||
@@ -636,6 +636,7 @@ typedef struct lfs3_data {
|
||||
lfs3_size_t size;
|
||||
union {
|
||||
const uint8_t *buffer;
|
||||
uint8_t *buffer_;
|
||||
struct {
|
||||
lfs3_block_t block;
|
||||
lfs3_size_t off;
|
||||
@@ -741,14 +742,11 @@ typedef struct lfs3_file {
|
||||
#endif
|
||||
|
||||
// in-RAM cache
|
||||
//
|
||||
// note this lines up with lfs3_data_t's buffer representation
|
||||
struct {
|
||||
lfs3_off_t size;
|
||||
uint8_t *buffer;
|
||||
#ifndef LFS3_KVONLY
|
||||
lfs3_off_t pos;
|
||||
#endif
|
||||
lfs3_data_t d;
|
||||
} cache;
|
||||
|
||||
// on-disk leaf bptr
|
||||
|
||||
Reference in New Issue
Block a user