Browse Source

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
D.R.racer 3 years ago
parent
commit
d275fe0e83
7 changed files with 193 additions and 150 deletions
  1. 1 117
      Firmware/SdBaseFile.cpp
  2. 3 26
      Firmware/SdBaseFile.h
  3. 170 0
      Firmware/SdFile.cpp
  4. 14 2
      Firmware/SdFile.h
  5. 1 1
      Firmware/SdVolume.h
  6. 3 1
      Firmware/cardreader.h
  7. 1 3
      Firmware/ultralcd.cpp

+ 1 - 117
Firmware/SdBaseFile.cpp

@@ -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);

+ 3 - 26
Firmware/SdBaseFile.h

@@ -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();
-  
-  int16_t readFilteredGcode();
-  bool computeNextFileBlock(uint32_t *block, uint16_t *offset);
-
-private:  
+protected:  
   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:
-  friend class GCodeInputFilter;
+ protected:
   // 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

+ 170 - 0
Firmware/SdFile.cpp

@@ -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.
  *
  *

+ 14 - 2
Firmware/SdFile.h

@@ -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);
@@ -51,4 +63,4 @@ class SdFile : public SdBaseFile/*, public Print*/ {
 #endif  // SdFile_h
 #endif  // SdFile_h
 
 
 
 
-#endif
+#endif

+ 1 - 1
Firmware/SdVolume.h

@@ -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;
 
 

+ 3 - 1
Firmware/cardreader.h

@@ -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); };

+ 1 - 3
Firmware/ultralcd.cpp

@@ -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;