wasm 如何互動

https://github.com/x213212/wasm_riscv_emu 五年前wasm files系統很不友善只有編譯過,硬改後也沒什結果,這次再結合online ide一下打算做一個類似debug的工具,今天就先把檔案系統給搞定. https://openhome.cc/Gossip/WebAssembly/C2Wasm.html https://www.itread01.com/hkcxxxe.html

code
#include<emscripten/emscripten.h>
int EMSCRIPTEN_KEEPALIVE sum(){

printf("sum = %i\n", 100);

return 1;
}

編譯時

code
emcc main.c ./src/bus.c ./src/cpu.c ./src/csr.c ./src/dram.c -o main -I ./include -s WASM=1  -o hello2.html  --preload-file ./addi.bin -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']"

-s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" 應該也是不用下,除非要對到其他fucntion

code
var Module = typeof Module != 'undefined' ? Module : {};
var back;
code
      back.then(prog => {
        console.log(prog.instance.exports.sum()); 
      });
      back.then(prog => {
        console.log(prog.instance.exports.sum()); 
      });

這樣就可以進行呼叫c 語言

因為模組導出也是module

code
    function sampleClick() {
      back.then(prog => {
        console.log(prog.instance.exports.sum()); 
      });
      back.then(prog => {
        console.log(prog.instance.exports.sum()); 
      });
      back.then(prog => {
        console.log(prog.instance.exports.sum2(1,5)); 
      });
    }
  </script>
  <script async type="text/javascript" src="hello2.js"></script>
  <button type="button" id="btn" onclick="sampleClick()">Click Here!</button>

在js裡面找到一樣把back

code
   var result = WebAssembly.instantiateStreaming(response, info);

        return result.then(
          receiveInstantiationResult,

改為

code
    back = WebAssembly.instantiateStreaming(response, info);

        return back.then(
          receiveInstantiationResult,

並在最上層加入

code
var back;

讓上層fucntion可以呼叫

https://www.cnblogs.com/xuanhun/p/9963082.html

https://medium.com/@scalevectors/webassembly-c-pointers-strings-part-2-8e173e50912b 從c語言讀值,這部分是ok但我們更多想知道的是把js的value 傳至 c code

找到一個範例 https://github.com/dudeofx/webassembly-IDBFS-barebones/blob/main/index.html 不過這wasm 版本基本上經過大改 這樣傳值可能會出錯(或者我沒找到更好方法

基本上idbfs已經廢除

code
 emcc main.c ./src/bus.c ./src/cpu.c ./src/csr.c ./src/dram.c -o main -I ./include -s WASM=1  -o hello2.html  --preload-file ./addi.bin  -lidbfs.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['UTF8ToString', 'getValue','stringToUTF8','lengthBytesUTF8']" 

後面的是我修改過後的code

code
 -lidbfs.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['UTF8ToString', 'getValue','stringToUTF8','lengthBytesUTF8']" 

那麼這個example

code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <emscripten.h>

//-----------------------------------------------------------------------
EMSCRIPTEN_KEEPALIVE 
char *LoadData() {
   int fd;
   int size;
   char *buff;

   fd = open("/data/textfile.txt", O_RDONLY);

   if (fd == -1) return strerror(errno);

   size = lseek(fd, 0, SEEK_END);
   lseek(fd, 0, SEEK_SET);
   buff = (char *) malloc(size+1);
   read(fd, buff, size);
   buff[size] = '\0';
   close(fd); 

   return buff;
}
//-----------------------------------------------------------------------
EMSCRIPTEN_KEEPALIVE
void SaveData(char *data) {
   int fd;
   int size;

   if (data == NULL) return;

   fd = open("/data/textfile.txt", O_CREAT | O_WRONLY, 0666);
   if (fd == -1) {
       printf("ERROR: could not open the file for writing!\n, %s\n", strerror(errno));
       return;
   }
   size = strlen(data);
   printf("saving %i bytes... %s\n", size, data);
   write(fd, data, size);
   ftruncate(fd, size);
   close(fd);

   EM_ASM ( FS.syncfs(false, function (err) {} ); );
}
//-----------------------------------------------------------------------
int main() {
   EM_ASM(
      FS.mkdir('/data');
      FS.mount(IDBFS, {}, '/data');

      FS.syncfs(true, function (err) { 
         // provide the DOM side a way to execute code after directory is mounted
         if (typeof Module.OnDataMounted !== 'undefined') {
            Module.OnDataMounted();
         }
      } );
   );

   emscripten_exit_with_live_runtime();
}
//-----------------------------------------------------------------------

沒意外可以編譯成功,接下來我們假設怎麼從 js的輸入框讀value 和讀出buffer的值呢

code
<script type='text/javascript'>

  function InboxHandler() {

    var str= document.getElementById('inbox').value;
    var strLen =  Module.lengthBytesUTF8(str);
    var strPtr = Module._malloc(strLen + 1);
    Module.stringToUTF8(str, strPtr, strLen + 1);
    console.log(strLen);

    back.then(prog => {

      // prog.instance.exports.queryString(ppcStr); 

      var pcStr = Module.getValue(strPtr, "i32");
      var jsStr = Module.UTF8ToString(pcStr);
      console.log("_queryString: " + jsStr);

      // var jsStr = Module.UTF8ToString(pcStr);
      // console.log("_queryString: " + ppcStr);
        prog.instance.exports.SaveData(strPtr); 
      });
  //  /
  }

  function OutboxHandler() {
    back.then(prog => {
      var jsStr = Module.UTF8ToString( document.getElementById('outbox').value =prog.instance.exports.LoadData());
      console.log("_queryString2: " + jsStr);
      document.getElementById('outbox').value =jsStr;
        // console.log(prog.instance.exports.LoadData()); 
      });
  }

  // Module.OnDataMounted = OutboxHandler;
</script>
<input id="inbox" type="text" /><br>
<input id="outbox" type="text" readonly /><br>

這樣就可以完成非同步的讀入/讀出file的資訊

在更複雜一點前端是binary輸入我們要轉成Uint8Array怎辦

code
  <input id="file-input" type="file" />
  <script>

    // var worker = new Worker('worker.js');
    var inputElement = document.getElementById("file-input");
    inputElement.addEventListener("change", handleFiles, false);
    function handleFiles() {
      var fileList = this.files[0];
      console.log(fileList)
      let buffer = fileList;
      let u = new Uint8Array(buffer);
      // console.log(this.files[0].size);
      // console.log(this.files[0].arrayBuffer);
      const reader = new FileReader();
      const fileByteArray = [];
      reader.readAsArrayBuffer(this.files[0]);
      reader.onloadend = (evt) => {
        if (evt.target.readyState === FileReader.DONE) {
          const arrayBuffer = evt.target.result,
            array = new Uint8Array(arrayBuffer);
          for (const a of array) {
            fileByteArray.push(a);
          }
          console.log(fileByteArray)
        }
      }

    }

  </script>

實測後轉成blob比較方便 https://yahone-chow.medium.com/file-blob-arraybuffer-576a8e99de0d

再從blob 轉成hex

code
function buf2hex(buffer) { // buffer is an ArrayBuffer
  return [...new Uint8Array(buffer)]
      .map(x => x.toString(16).padStart(2, '0'))
      .join('');

     function handleFiles() {

      var fileList = this.files[0];
      console.log(fileList)
      let buffer = fileList;
      let u = new Uint8Array(buffer);
      // console.log(this.files[0].size);
      // console.log(this.files[0].arrayBuffer);
      const reader = new FileReader();
      const fileByteArray = [];
      var reader2 = new FileReader();
      reader2.onload = function (event) {
        var content = reader2.result;//内容就在这里
        alert(content);
        console.log(content);
        var blob = new Blob([content], {
          type: ''
        });
        // console.info(blob);
        // console.info(blob.slice(1, 3, 'text/plain'));
      };
      reader.readAsArrayBuffer(this.files[0]);
      reader.onloadend = (evt) => {
        if (evt.target.readyState === FileReader.DONE) {
          const arrayBuffer = evt.target.result,
            array = new Uint8Array(arrayBuffer);
          for (const a of array) {
            fileByteArray.push(a);
          }
          console.log(fileByteArray)
          console.log(array)
          var after = Uint8ArrayToString(array)

          var blob = new Blob([array])
          console.info(blob);
          var href = URL.createObjectURL(blob);
          // 從 Blob 取出資料
          // var link = document.createElement("a");
          // document.body.appendChild(link);
          // link.href = href;
          // link.download = "fileName";
          // link.click();

          // reader2.readAsText(blob);
          console.log(fileByteArray.length)
          console.log(sumUp(fileByteArray));
          console.log(buf2hex(fileByteArray))
          document.getElementById('inbox').value=buf2hex(fileByteArray)
        }

      }

    }

我們hex在to string 到這邊可以轉成string後,我們傳達字元陣列給c 這邊本來要用uint8array進行傳值 不過試到後面還是轉hex再轉string 進行存取比較方便

code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include<emscripten/emscripten.h>
#include <emscripten.h>
#include "includes/cpu.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <inttypes.h>
#include "test.h"
EMSCRIPTEN_KEEPALIVE
void tesqwe(char *input2);
// ANSI colors
#define ANSI_RED     "\x1b[31m"
#define ANSI_GREEN   "\x1b[32m"
#define ANSI_YELLOW  "\x1b[33m"
#define ANSI_BLUE    "\x1b[34m"
#define ANSI_MAGENTA "\x1b[35m"
#define ANSI_CYAN    "\x1b[36m"
#define ANSI_RESET   "\x1b[0m"

void read_file(CPU* cpu, char *filename)
{
    FILE *file;
    uint8_t *buffer;
    unsigned long fileLen;

    //Open file
    file = fopen(filename, "rb");
    if (!file)
    {
        fprintf(stderr, "Unable to open file %s", filename);
    }

    //Get file length
    fseek(file, 0, SEEK_END);
    fileLen=ftell(file);
    fseek(file, 0, SEEK_SET);

    //Allocate memory
    buffer=(uint8_t *)malloc(fileLen+1);
    if (!buffer)
    {
        fprintf(stderr, "Memory error!");
        fclose(file);
    }

    //Read file contents into buffer
    fread(buffer, fileLen, 1, file);
    fclose(file);

    // Print file contents in hex
    for (int i=0; i<fileLen; i+=2) {
        if (i%16==0) printf("\n%.8x: ", i);
        printf("%02x%02x ", *(buffer+i), *(buffer+i+1));
    }
    /*printf("\n");*/

    // copy the bin executable to dram
    memcpy(cpu->bus.dram.mem, buffer, fileLen*sizeof(uint8_t));
    free(buffer);
}
int EMSCRIPTEN_KEEPALIVE sum(){

printf("sum = %i\n", 100);

return 1;
}

int EMSCRIPTEN_KEEPALIVE sum2(int a,int b){

printf("sum2 = %i\n",a+b);

return 0;
}

//-----------------------------------------------------------------------
EMSCRIPTEN_KEEPALIVE 
char *LoadData() {
   int fd;
   int size;
   char *buff;

   fd = open("/data/textfile.txt", O_RDONLY);

   if (fd == -1) return strerror(errno);

   size = lseek(fd, 0, SEEK_END);
   lseek(fd, 0, SEEK_SET);
   buff = (uint8_t *) malloc(size+1);
   read(fd, buff, size);
   buff[size] = '\0';
       for (int i=0; i<size; i+=2) {
        if (i%16==0) printf("\n%.8x: ", i);
        printf("%02x%02x ", *(buff+i), *(buff+i+1));
    }
   close(fd); 
   printf("read %s bytes...\n",buff);
   struct CPU cpu;
    cpu_init(&cpu);
    // Read input file
    read_file(&cpu,"/data/textfile2.txt");
    while (1) {
        // fetch
        uint32_t inst = cpu_fetch(&cpu);
        // Increment the program counter
        cpu.pc += 4;
        // execute
        if (!cpu_execute(&cpu, inst))
            break;

        // dump_registers(&cpu);

        if(cpu.pc==0)
            break;
    }
   return buff;
}

//-----------------------------------------------------------------------
EMSCRIPTEN_KEEPALIVE
void SaveData(char *data) {
   int fd;
   int size;

   if (data == NULL) return;

   fd = open("/data/textfile.txt", O_CREAT | O_WRONLY, 0666);
   if (fd == -1) {
       printf("ERROR: could not open the file for writing!\n, %s\n", strerror(errno));
       return;
   }
   size = strlen(data);
   printf("saving %i bytes... %s\n", size, data);
   write(fd, data, size);
   ftruncate(fd, size);
   tesqwe(data);
   close(fd);
   EM_ASM ( FS.syncfs(false, function (err) {} ); );
}

static char g_str[256] = "Hello, world!";
void EMSCRIPTEN_KEEPALIVE queryString(char** ppStr) {
   *ppStr = g_str;
}

void printArry(unsigned char arr[], int n)
{
    int i;
    for (i = 0; i < n; i++)
    {
        printf("%x ", arr[i]);
    }

    printf("\n");
}
void float2u8Arry(uint8_t *u8Arry, float *floatdata)
{
    uint8_t farray[4];
    *(float *)farray = *floatdata;

      u8Arry[3] = farray[0];
        u8Arry[2] = farray[1];
        u8Arry[1] = farray[2];
        u8Arry[0] = farray[3];

}
float EMSCRIPTEN_KEEPALIVE sum_up(float vals[], int size) {
  float res = 0;
  for(int i=0; i<size; i++){
    res += vals[i];

    printf("%f \n",vals[i]);
    uint8_t u8data[4];
    float2u8Arry(u8data, &vals[i]);
    printArry(u8data, 4);
    //    printf("saving %d bytes... %s\n",  vals[i]);
    }

    // write_to_file()
  return res;
}

//-----------------------------------------------------------------------
int main(int argc, char* argv[]) {
    // if (argc != 2) {
    //     printf("Usage: rvemu <filename>\n");
    //     exit(1);
    // }
 EM_ASM(
      FS.mkdir('/data');
      FS.mount(IDBFS, {}, '/data');

      FS.syncfs(true, function (err) { 
         // provide the DOM side a way to execute code after directory is mounted
         if (typeof Module.OnDataMounted !== 'undefined') {
            Module.OnDataMounted();
         }
      } );
   );

    // Initialize cpu, registers and program counter
    struct CPU cpu;
    cpu_init(&cpu);
    // Read input file
    read_file(&cpu,"addi.bin");
    while (1) {
        // fetch
        uint32_t inst = cpu_fetch(&cpu);
        // Increment the program counter
        cpu.pc += 4;
        // execute
        if (!cpu_execute(&cpu, inst))
            break;

        // dump_registers(&cpu);

        if(cpu.pc==0)
            break;
    }

    emscripten_exit_with_live_runtime();
    return 0;
}

主要異動SaveData和LoadData各自存去不同的檔案 /data/textfile.txt /data/textfile2.txt SaveData 的部分還有去呼叫tesqwe(data); 這邊順便體驗一下link,c++ hex 轉 bin 檔案很簡單

code
emcc -c test.cpp -o test

link ,多了./test

code
emcc  main.c ./test  ./src/bus.c ./src/cpu.c ./src/csr.c ./src/dram.c -o main -I ./include -s WASM=1  -o hello2.html  --preload-file ./addi.bin  -lidbfs.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['UTF8ToString', 'getValue','stringToUTF8','lengthBytesUTF8']" 

到這邊c就可以去呼叫c++的fucntion了!

code
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <ios>
#include "test.h"
std::string wut = "6f008004732f2034930f80006308ff03930f90006304ff03930fb0006300ff03130f000063040f0067000f00732f203463540f006f00400093e19153171f000023223ffc6ff09fff93000000130100009301000013020000930200001303000093030000130400009304000013050000930500001306000093060000130700009307000013080000930800001309000093090000130a0000930a0000130b0000930b0000130c0000930c0000130d0000930d0000130e0000930e0000130f0000930f0000732540f16310050097020000938202017390523073500018970200009382020273905230b70200809382f2ff7390023b9302f0017390023a73504030970200009382420173905230735020307350303093010000970200009382c2ee73905230130510001315f501634c05000f00f00f930110009308d005130500007300000093020000638a020073905210b7b20000938292107390223073500030970200009382420173901234732540f17300203093000000138700009303000093012000631c7726930010001387100093032000930130006312772693003000138770009303a000930140006318772493000000138700809303008093015000631e7722b700008013870000b70300809301600063147722b700008013870080b7030080938303809301700063187720930000001387f07f9303f07f93018000631e771eb70000809380f0ff13870000b70300809383f3ff930190006310771eb70000809380f0ff1387f07fb70300809383e37f9301a0006312771cb70000801387f07fb70300809383f37f9301b0006316771ab70000809380f0ff13870080b7f3ff7f9383f37f9301c00063187718930000001387f0ff9303f0ff9301d000631e77169300f0ff13871000930300009301e000631477169300f0ff1387f0ff9303e0ff9301f000631a7714b70000809380f0ff13871000b703008093010001631e77129300d0009380b000930380019301100163947012130200009300d0001387b000130307001302120093022000e31652fe930380019301200163107310130200009300d0001387a00013000000130307001302120093022000e31452fe9303700193013001631a730c130200009300d000138790001300000013000000130307001302120093022000e31252fe93036001930140016312730a130200009300d0001387b0001302120093022000e31852fe930380019301500163107708130200009300d000130000001387a0001302120093022000e31652fe9303700193016001631c7704130200009300d0001300000013000000138790001302120093022000e31452fe930360019301700163167702930000029303000293018001639e70009300100213802003930300009301900163147000631030020f00f00f638001009391110093e111009308d00513850100730000000f00f00f930110009308d0051305000073000000731000c00000000000000000000000000000000000000000000000000";
void tesqwe(char *input2){
    std::ofstream datafile("/data/textfile2.txt", std::ios_base::binary | std::ios_base::out);

    char buf[3];
    buf[2] = 0;
    // std::string s = input2;
    std::string str(input2);
    // std::string rjo(1,input2);
    std::stringstream input(str);
    input.flags(std::ios_base::hex);
    while (input)
    {
        input >> buf[0] >> buf[1];
        long val = strtol(buf, nullptr, 16);
        datafile << static_cast<unsigned char>(val & 0xff);
    }
    std::cout<< "ok"<<std::endl;
}

到這邊我們就可以透過tesqwe 把前端的hex char array轉為string 存入前面掛載的目錄,如果要讀出來呢,那就反向一下

code
//-----------------------------------------------------------------------
EMSCRIPTEN_KEEPALIVE 
char *LoadData() {
   int fd;
   int size;
   char *buff;

   fd = open("/data/textfile.txt", O_RDONLY);

   if (fd == -1) return strerror(errno);

   size = lseek(fd, 0, SEEK_END);
   lseek(fd, 0, SEEK_SET);
   buff = (uint8_t *) malloc(size+1);
   read(fd, buff, size);
   buff[size] = '\0';
       for (int i=0; i<size; i+=2) {
        if (i%16==0) printf("\n%.8x: ", i);
        printf("%02x%02x ", *(buff+i), *(buff+i+1));
    }
   close(fd); 
   /////////////////////////////////////////////////////////////
   struct CPU cpu;
    cpu_init(&cpu);
    // Read input file
    read_file(&cpu,"/data/textfile2.txt");
    while (1) {
        // fetch
        uint32_t inst = cpu_fetch(&cpu);
        // Increment the program counter
        cpu.pc += 4;
        // execute
        if (!cpu_execute(&cpu, inst))
            break;

        // dump_registers(&cpu);

        if(cpu.pc==0)
            break;
    }
   return buff;
}

在第二個分隔線就可以讓我們的模擬器去讀前端載入的elf檔案,實現動態加載分析檔案的功能 read_file(&cpu,"/data/textfile2.txt"); 並呼叫載入elf執行模擬指令。 到這邊如果換成是gb模擬器,應該就完全可以在瀏覽器實現讀檔案寫檔案的功能。 目前看來emcc在編譯過程中把datatype某些部分給優化掉導致指令沒辦法正常顯示

計算結果正確,應該是wasm把某些datatype給轉換調導致printf無法判斷。

code
    printf("\n%#.8lx -> Inst: %llx ",
            cpu->pc-4, inst); // DEBUG*/
    printf("\n<OpCode: %#.2x, funct3:%#x, funct7:%#x> ", opcode, funct3, funct7);

   for (int i=0; i<8; i++) {
        printf("   %4s:%llx ", abi[i],    cpu->regs[i]);
        printf("   %2s:%llx  ", abi[i+8],  cpu->regs[i+8]);
        printf("   %2s:%llx  ", abi[i+16], cpu->regs[i+16]);
        printf("   %3s:%llx\n", abi[i+24], cpu->regs[i+24]);
    }

上述c code重寫後就可以正常顯示

debug

更多時候等待並不是c很慢是 這邊註解output就不會一直拉到最下導致瀏覽器速度降低

code
 // element.scrollTop = element.scrollHeight; // focus on bottom