Extract gcode filter from SdBaseFile into SdFile + optimization

- Start saving instructions as the whole PR was >1KB long.
- It turned out the compiler was unable to understand the core skipping
cycle and an ASM version had to be used.
- Add seekSet aware of the G-code filter
This commit is contained in:
D.R.racer 2021-01-27 09:33:28 +01:00
parent c3758d350e
commit d275fe0e83
7 changed files with 193 additions and 150 deletions

View File

@ -534,17 +534,6 @@ bool SdBaseFile::open(const char* path, uint8_t oflag) {
return open(cwd_, path, oflag); return open(cwd_, path, oflag);
} }
bool SdBaseFile::openFilteredGcode(SdBaseFile* dirFile, const char* path){
if( open(dirFile, path, O_READ) ){
gf.reset(0,0);
// compute the block to start with
if( ! computeNextFileBlock(&gf.block, &gf.offset) )
return false;
return true;
} else {
return false;
}
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** Open a file or directory by name. /** Open a file or directory by name.
* *
@ -1043,111 +1032,6 @@ int16_t SdBaseFile::read() {
return read(&b, 1) == 1 ? b : -1; return read(&b, 1) == 1 ? b : -1;
} }
int16_t SdBaseFile::readFilteredGcode() {
// avoid calling the default heavy-weight read() for just one byte
return gf.read_byte();
}
void GCodeInputFilter::reset(uint32_t blk, uint16_t ofs){
// @@TODO clean up
block = blk;
offset = ofs;
cachePBegin = sd->vol_->cache()->data;
// reset cache read ptr to its begin
cacheP = cachePBegin;
}
int16_t GCodeInputFilter::read_byte(){
EnsureBlock(); // this is unfortunate :( ... other calls are using the cache and we can loose the data block of our gcode file
// assume, we have the 512B block cache filled and terminated with a '\n'
// SERIAL_PROTOCOLPGM("read_byte enter:");
// for(uint8_t i = 0; i < 16; ++i){
// SERIAL_PROTOCOL( cacheP[i] );
// }
const uint8_t *start = cacheP;
uint8_t consecutiveCommentLines = 0;
while( *cacheP == ';' ){
for(;;){
while( *(++cacheP) != '\n' ); // skip until a newline is found
// found a newline, prepare the next block if block cache end reached
if( cacheP - cachePBegin >= 512 ){
// at the end of block cache, fill new data in
sd->curPosition_ += cacheP - start;
if( ! sd->computeNextFileBlock(&block, &offset) )goto fail;
EnsureBlock(); // fetch it into RAM
cacheP = start = cachePBegin;
} else {
if(++consecutiveCommentLines == 255){
// SERIAL_PROTOCOLLN(sd->curPosition_);
goto forceExit;
}
// peek the next byte - we are inside the block at least at 511th index - still safe
if( *(cacheP+1) == ';' ){
// consecutive comment
++cacheP;
++consecutiveCommentLines;
}
break; // found the real end of the line even across many blocks
}
}
}
forceExit:
sd->curPosition_ += cacheP - start + 1;
{
int16_t rv = *cacheP++;
// prepare next block if needed
if( cacheP - cachePBegin >= 512 ){
// SERIAL_PROTOCOLLN(sd->curPosition_);
if( ! sd->computeNextFileBlock(&block, &offset) )goto fail;
// don't need to force fetch the block here, it will get loaded on the next call
cacheP = cachePBegin;
}
return rv;
}
fail:
// SERIAL_PROTOCOLLNPGM("CacheFAIL");
return -1;
}
bool GCodeInputFilter::EnsureBlock(){
if ( sd->vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_READ)){
// terminate with a '\n'
const uint16_t terminateOfs = (sd->fileSize_ - offset) < 512 ? (sd->fileSize_ - offset) : 512;
sd->vol_->cache()->data[ terminateOfs ] = '\n';
return true;
} else {
return false;
}
}
bool SdBaseFile::computeNextFileBlock(uint32_t *block, uint16_t *offset) {
// error if not open or write only
if (!isOpen() || !(flags_ & O_READ)) return false;
*offset = curPosition_ & 0X1FF; // offset in block
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
*block = vol_->rootDirStart() + (curPosition_ >> 9);
} else {
uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
if (*offset == 0 && blockOfCluster == 0) {
// start of new cluster
if (curPosition_ == 0) {
// use first cluster in file
curCluster_ = firstCluster_;
} else {
// get next cluster from FAT
if (!vol_->fatGet(curCluster_, &curCluster_)) return false;
}
}
*block = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
}
return true;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** Read data from a file starting at the current position. /** Read data from a file starting at the current position.
* *
@ -1561,7 +1445,7 @@ bool SdBaseFile::rmRfStar() {
* \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
* OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t). * OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t).
*/ */
SdBaseFile::SdBaseFile(const char* path, uint8_t oflag):gf(this) { SdBaseFile::SdBaseFile(const char* path, uint8_t oflag) {
type_ = FAT_FILE_TYPE_CLOSED; type_ = FAT_FILE_TYPE_CLOSED;
writeError = false; writeError = false;
open(path, oflag); open(path, oflag);

View File

@ -176,32 +176,15 @@ uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1;
uint16_t const FAT_DEFAULT_TIME = (1 << 11); uint16_t const FAT_DEFAULT_TIME = (1 << 11);
class SdBaseFile;
class GCodeInputFilter {
SdBaseFile *sd; //@@TODO subject to removal - merge with some derived SdFileXX
const uint8_t *cachePBegin;
const uint8_t *cacheP;
bool EnsureBlock();
public:
uint32_t block; // remember the current file block to be kept in cache - due to reuse of the memory, the block may fall out a must be read back
uint16_t offset;
public:
inline GCodeInputFilter(SdBaseFile *sd):sd(sd){}
void reset(uint32_t blk, uint16_t ofs);
int16_t read_byte();
};
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** /**
* \class SdBaseFile * \class SdBaseFile
* \brief Base class for SdFile with Print and C++ streams. * \brief Base class for SdFile with Print and C++ streams.
*/ */
class SdBaseFile { class SdBaseFile {
GCodeInputFilter gf;
public: public:
/** Create an instance. */ /** Create an instance. */
SdBaseFile() : gf(this), writeError(false), type_(FAT_FILE_TYPE_CLOSED) {} SdBaseFile() : writeError(false), type_(FAT_FILE_TYPE_CLOSED) {}
SdBaseFile(const char* path, uint8_t oflag); SdBaseFile(const char* path, uint8_t oflag);
~SdBaseFile() {if(isOpen()) close();} ~SdBaseFile() {if(isOpen()) close();}
/** /**
@ -294,18 +277,13 @@ class SdBaseFile {
bool open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag); bool open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag);
bool open(SdBaseFile* dirFile, const char* path, uint8_t oflag); bool open(SdBaseFile* dirFile, const char* path, uint8_t oflag);
bool open(const char* path, uint8_t oflag = O_READ); bool open(const char* path, uint8_t oflag = O_READ);
bool openFilteredGcode(SdBaseFile* dirFile, const char* path);
bool openNext(SdBaseFile* dirFile, uint8_t oflag); bool openNext(SdBaseFile* dirFile, uint8_t oflag);
bool openRoot(SdVolume* vol); bool openRoot(SdVolume* vol);
int peek(); int peek();
static void printFatDate(uint16_t fatDate); static void printFatDate(uint16_t fatDate);
static void printFatTime( uint16_t fatTime); static void printFatTime( uint16_t fatTime);
bool printName(); bool printName();
protected:
int16_t readFilteredGcode();
bool computeNextFileBlock(uint32_t *block, uint16_t *offset);
private:
int16_t read(); int16_t read();
int16_t read(void* buf, uint16_t nbyte); int16_t read(void* buf, uint16_t nbyte);
public: public:
@ -347,8 +325,7 @@ public:
SdVolume* volume() const {return vol_;} SdVolume* volume() const {return vol_;}
int16_t write(const void* buf, uint16_t nbyte); int16_t write(const void* buf, uint16_t nbyte);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
private: protected:
friend class GCodeInputFilter;
// allow SdFat to set cwd_ // allow SdFat to set cwd_
friend class SdFat; friend class SdFat;
// global pointer to cwd dir // global pointer to cwd dir

View File

@ -30,6 +30,176 @@
*/ */
SdFile::SdFile(const char* path, uint8_t oflag) : SdBaseFile(path, oflag) { SdFile::SdFile(const char* path, uint8_t oflag) : SdBaseFile(path, oflag) {
} }
//size=100B
bool SdFile::openFilteredGcode(SdBaseFile* dirFile, const char* path){
if( open(dirFile, path, O_READ) ){
gfReset(0,0);
// compute the block to start with
if( ! gfComputeNextFileBlock() )
return false;
return true;
} else {
return false;
}
}
//size=90B
bool SdFile::seekSetFilteredGcode(uint32_t pos){
bool rv = seekSet(pos);
gfComputeNextFileBlock();
return rv;
}
//size=50B
void SdFile::gfReset(uint32_t blk, uint16_t ofs){
// @@TODO clean up
gfBlock = blk;
gfOffset = ofs;
gfCachePBegin = vol_->cache()->data;
// reset cache read ptr to its begin
gfCacheP = gfCachePBegin;
}
//FORCE_INLINE const uint8_t * find_endl(const uint8_t *p){
// while( *(++p) != '\n' ); // skip until a newline is found
// return p;
//}
// think twice before allowing this to inline - manipulating 4B longs is costly
// moreover - this function has its parameters in registers only, so no heavy stack usage besides the call/ret
void __attribute__((noinline)) SdFile::gfUpdateCurrentPosition(uint16_t inc){
curPosition_ += inc;
}
#define find_endl(resultP, startP) \
__asm__ __volatile__ ( \
"cycle: \n" \
"ld r22, Z+ \n" \
"cpi r22, 0x0A \n" \
"brne cycle \n" \
: "=z" (resultP) /* result of the ASM code - in our case the Z register (R30:R31) */ \
: "z" (startP) /* input of the ASM code - in our case the Z register as well (R30:R31) */ \
: "r22" /* modifying register R22 - so that the compiler knows */ \
)
//size=400B
// avoid calling the default heavy-weight read() for just one byte
int16_t SdFile::readFilteredGcode(){
gfEnsureBlock(); // this is unfortunate :( ... other calls are using the cache and we can loose the data block of our gcode file
// assume, we have the 512B block cache filled and terminated with a '\n'
// SERIAL_PROTOCOLPGM("read_byte enter:");
// for(uint8_t i = 0; i < 16; ++i){
// SERIAL_PROTOCOL( cacheP[i] );
// }
const uint8_t *start = gfCacheP;
uint8_t consecutiveCommentLines = 0;
while( *gfCacheP == ';' ){
for(;;){
//while( *(++gfCacheP) != '\n' ); // skip until a newline is found - suboptimal code!
// Wondering, why this "nice while cycle" is done in such a weird way using a separate find_endl() function?
// Have a look at the ASM code GCC produced!
// At first - a separate find_endl() makes the compiler understand,
// that I don't need to store gfCacheP every time, I'm only interested in the final address where the '\n' was found
// - the cycle can run on CPU registers only without touching memory besides reading the character being compared.
// Not only makes the code run considerably faster, but is also 40B shorter!
// This was the generated code:
//FORCE_INLINE const uint8_t * find_endl(const uint8_t *p){
// while( *(++p) != '\n' ); // skip until a newline is found
// return p; }
// 11c5e: movw r30, r18
// 11c60: subi r18, 0xFF ; 255
// 11c62: sbci r19, 0xFF ; 255
// 11c64: ld r22, Z
// 11c66: cpi r22, 0x0A ; 10
// 11c68: brne .-12 ; 0x11c5e <get_command()+0x524>
// Still, even that was suboptimal as the compiler seems not to understand the usage of ld r22, Z+ (the plus is important)
// aka automatic increment of the Z register (R30:R31 pair)
// There is no other way than pure ASM!
find_endl(gfCacheP, gfCacheP);
// found a newline, prepare the next block if block cache end reached
if( gfCacheP - gfCachePBegin >= 512 ){
// at the end of block cache, fill new data in
gfUpdateCurrentPosition( gfCacheP - start );
if( ! gfComputeNextFileBlock() )goto fail;
gfEnsureBlock(); // fetch it into RAM
gfCacheP = start = gfCachePBegin;
} else {
if(++consecutiveCommentLines == 255){
// SERIAL_PROTOCOLLN(sd->curPosition_);
goto forceExit;
}
// peek the next byte - we are inside the block at least at 511th index - still safe
if( *(gfCacheP+1) == ';' ){
// consecutive comment
++gfCacheP;
++consecutiveCommentLines;
}
break; // found the real end of the line even across many blocks
}
}
}
forceExit:
{
gfUpdateCurrentPosition( gfCacheP - start + 1 );
int16_t rv = *gfCacheP++;
// prepare next block if needed
if( gfCacheP - gfCachePBegin >= 512 ){
if( ! gfComputeNextFileBlock() )goto fail;
// don't need to force fetch the block here, it will get loaded on the next call
gfCacheP = gfCachePBegin;
}
return rv;
}
fail:
// SERIAL_PROTOCOLLNPGM("CacheFAIL");
return -1;
}
//size=100B
bool SdFile::gfEnsureBlock(){
if ( vol_->cacheRawBlock(gfBlock, SdVolume::CACHE_FOR_READ)){
// terminate with a '\n'
const uint16_t terminateOfs = (fileSize_ - gfOffset) < 512 ? (fileSize_ - gfOffset) : 512U;
vol_->cache()->data[ terminateOfs ] = '\n';
return true;
} else {
return false;
}
}
//size=350B
bool SdFile::gfComputeNextFileBlock() {
// error if not open or write only
if (!isOpen() || !(flags_ & O_READ)) return false;
gfOffset = curPosition_ & 0X1FF; // offset in block
if (type_ == FAT_FILE_TYPE_ROOT_FIXED) {
gfBlock = vol_->rootDirStart() + (curPosition_ >> 9);
} else {
uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_);
if (gfOffset == 0 && blockOfCluster == 0) {
// start of new cluster
if (curPosition_ == 0) {
// use first cluster in file
curCluster_ = firstCluster_;
} else {
// get next cluster from FAT
if (!vol_->fatGet(curCluster_, &curCluster_)) return false;
}
}
gfBlock = vol_->clusterStartBlock(curCluster_) + blockOfCluster;
}
return true;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/** Write data to an open file. /** Write data to an open file.
* *

View File

@ -34,7 +34,16 @@
* \brief SdBaseFile with Print. * \brief SdBaseFile with Print.
*/ */
class SdFile : public SdBaseFile/*, public Print*/ { class SdFile : public SdBaseFile/*, public Print*/ {
public: // GCode filtering vars and methods - due to optimization reasons not wrapped in a separate class
const uint8_t *gfCachePBegin;
const uint8_t *gfCacheP;
uint32_t gfBlock; // remember the current file block to be kept in cache - due to reuse of the memory, the block may fall out a must be read back
uint16_t gfOffset;
void gfReset(uint32_t blk, uint16_t ofs);
bool gfEnsureBlock();
bool gfComputeNextFileBlock();
void gfUpdateCurrentPosition(uint16_t inc);
public:
SdFile() {} SdFile() {}
SdFile(const char* name, uint8_t oflag); SdFile(const char* name, uint8_t oflag);
#if ARDUINO >= 100 #if ARDUINO >= 100
@ -43,6 +52,9 @@ class SdFile : public SdBaseFile/*, public Print*/ {
void write(uint8_t b); void write(uint8_t b);
#endif #endif
bool openFilteredGcode(SdBaseFile* dirFile, const char* path);
int16_t readFilteredGcode();
bool seekSetFilteredGcode(uint32_t pos);
int16_t write(const void* buf, uint16_t nbyte); int16_t write(const void* buf, uint16_t nbyte);
void write(const char* str); void write(const char* str);
void write_P(PGM_P str); void write_P(PGM_P str);

View File

@ -119,7 +119,7 @@ class SdVolume {
bool dbgFat(uint32_t n, uint32_t* v) {return fatGet(n, v);} bool dbgFat(uint32_t n, uint32_t* v) {return fatGet(n, v);}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
private: private:
friend class GCodeInputFilter; friend class SdFile;
// Allow SdBaseFile access to SdVolume private data. // Allow SdBaseFile access to SdVolume private data.
friend class SdBaseFile; friend class SdBaseFile;

View File

@ -1,6 +1,8 @@
#ifndef CARDREADER_H #ifndef CARDREADER_H
#define CARDREADER_H #define CARDREADER_H
#define SDSUPPORT
#ifdef SDSUPPORT #ifdef SDSUPPORT
#define MAX_DIR_DEPTH 10 #define MAX_DIR_DEPTH 10
@ -64,7 +66,7 @@ public:
//@@TODO potential performance problem - when the comment reading fails, sdpos points to the last correctly read character. //@@TODO potential performance problem - when the comment reading fails, sdpos points to the last correctly read character.
// However, repeated reading (e.g. after power panic) the comment will be read again - it should survive correctly, it will just take a few moments to skip // However, repeated reading (e.g. after power panic) the comment will be read again - it should survive correctly, it will just take a few moments to skip
FORCE_INLINE int16_t getFilteredGcodeChar() { sdpos = file.curPosition();return (int16_t)file.readFilteredGcode();}; FORCE_INLINE int16_t getFilteredGcodeChar() { sdpos = file.curPosition();return (int16_t)file.readFilteredGcode();};
FORCE_INLINE void setIndex(long index) {sdpos = index;file.seekSet(index);}; /*FORCE_INLINE*/ void setIndex(long index) {sdpos = index;file.seekSetFilteredGcode(index);};
FORCE_INLINE uint8_t percentDone(){if(!isFileOpen()) return 0; if(filesize) return sdpos/((filesize+99)/100); else return 0;}; FORCE_INLINE uint8_t percentDone(){if(!isFileOpen()) return 0; if(filesize) return sdpos/((filesize+99)/100); else return 0;};
FORCE_INLINE char* getWorkDirName(){workDir.getFilename(filename);return filename;}; FORCE_INLINE char* getWorkDirName(){workDir.getFilename(filename);return filename;};
FORCE_INLINE uint32_t get_sdpos() { if (!isFileOpen()) return 0; else return(sdpos); }; FORCE_INLINE uint32_t get_sdpos() { if (!isFileOpen()) return 0; else return(sdpos); };

View File

@ -8473,10 +8473,8 @@ static void lcd_selftest_screen_step(int _row, int _col, int _state, const char
/** Menu action functions **/ /** Menu action functions **/
static bool check_file(const char* filename) { static bool check_file(const char* filename) {
return true; // @@TODO
if (farm_mode) return true; if (farm_mode) return true;
card.openFileFilteredGcode((char*)filename, true); //@@TODO card.openFileFilteredGcode((char*)filename, true);
bool result = false; bool result = false;
const uint32_t filesize = card.getFileSize(); const uint32_t filesize = card.getFileSize();
uint32_t startPos = 0; uint32_t startPos = 0;