Skip to content

Commit

Permalink
Merge branch 'JIT'
Browse files Browse the repository at this point in the history
  • Loading branch information
gfwilliams committed May 12, 2022
2 parents 8291789 + 2914db8 commit b75a36a
Show file tree
Hide file tree
Showing 13 changed files with 1,432 additions and 10 deletions.
5 changes: 5 additions & 0 deletions Makefile
Expand Up @@ -641,6 +641,11 @@ ifeq ($(USE_TENSORFLOW),1)
include make/misc/tensorflow.make
endif

ifeq ($(USE_JIT),1)
DEFINES += -DESPR_JIT
SOURCES += src/jsjit.c src/jsjitc.c
endif


endif # BOOTLOADER ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DON'T USE STUFF ABOVE IN BOOTLOADER

Expand Down
158 changes: 158 additions & 0 deletions README_JIT.md
@@ -0,0 +1,158 @@
Espruino JIT compiler
======================

This compiler allows Espruino to compile JS code into ARM Thumb code.

Right now this roughly doubles execution speed.

Works:

* Assignments
* Maths operators, postfix operators
* Function calls
* `for (;;)` loops
* `if ()`
* On the whole functions that can't be JITed will produce a message on the console and will be treated as normal functions.

Doesn't work:

* Everything else
* Function arguments
* `var/const/let`
* Member access (with `.` or `[]`)

Performance:

* Right now, variable accesses search for the variable each time - so this is pretty slow. Maybe they could all be referenced at the start just once?
* Built-in functions could be called directly, which would be a TON faster
* Peephole optimisation could still be added (eg. removing `push r0, pop r0`) but this is the least of our worries
* Stuff is in place to allow ints to be stored on the stack and converted when needed. This could maybe allow us to keep some vars as ints.

Big stuff to do:

* There seems to be a 'lock leak' - maybe on assignments
* When calling a JIT function, using existing FunctionCall code to set up args and an execution scope (so args can be passed in)


## Testing

### Linux

* Build for Linux `USE_JIT=1 DEBUG=1 make`
* Test with `./espruino --test-jit` - doesn't do much useful right now
* CLI test `./espruino -e 'function jit() {"jit";return 123;}'`
* On Linux builds, a file `jit.bin` is created each time JIT runs. It contains the raw Thumb code.
* Disassemble binary with `arm-none-eabi-objdump -D -Mforce-thumb -b binary -m cortex-m4 jit.bin`

You can see what code is created with stuff like:

```
./espruino -e "E.setFlags({jitDebug:1});trace(function jit() {'jit';return 1+2;})"
./espruino -e 'E.setFlags({jitDebug:1});function jit() {"jit";return "Hello"}'
./espruino -e 'E.setFlags({jitDebug:1});function jit() {"jit";print(42)}'
./espruino -e 'E.setFlags({jitDebug:1});function jit() {"jit";i=5}'
./espruino -e 'E.setFlags({jitDebug:1});function jit() {"jit";if (i<3) print("T"); else print("X");}}'
./espruino -e 'E.setFlags({jitDebug:1});function jit() {"jit";for (i=0;i<5;i=i+1) print(i);}'
```


### Raspberry Pi

The Pi can execute Thumb-2 code (Pi 3 and on only)

* Just build a normal Pi Binary on the Pi: `USE_JIT=1 DEBUG=1 make`
* CLI test `./espruino -e 'function jit() {"jit";print("Hello World");};jit()'`
* This may or may not work - sometimes it does (especially when launched from GDB) but I'm unsure why it's flakey!
* Dump binary on pi with `objdump -D -Mforce-thumb -b binary -m arm jit.bin`

### Build for an actual device

* Build for ARM: `USE_JIT=1 BOARD=BOARD_NAME RELEASE=1 make flash`


```
// Enable debug output
E.setFlags({jitDebug:1});
function jit() {'jit';return 1;}
jit()==1
function jit() {'jit';return 1+2+3+4+5;}
jit()==15
function jit() {'jit';return 'Hello';}
jit()=="Hello"
function jit() {'jit';return true;}
jit()==true
var test = "Hello world";
function jit() {'jit';return test;}
jit()=="Hello world";
var test = "Hello ";
var jit = E.nativeCall(1, "JsVar()", E.JIT("test+'World!'"))
jit()=="Hello World!"
function t() { print("Hello"); }
function jit() {'jit';t();}
jit(); // prints 'hello'
function jit() {'jit';print(42);}
function jit() {'jit';print(42);return 123;}
jit()==123
function t() { return "Hello"; }
function jit() {'jit'; return t()+" world";}
jit()=="Hello world"
function jit() {'jit';digitalWrite(LED1,1);}
jit(); // LED on
function jit() {'jit';i=42;}
jit();i==42
function jit() {'jit';return 1<2;}
jit();i==true
function jit() {"jit";if (i<3) print("T"); else print("X");print("--")}
i=2;jit(); // prints T,--
i=5;jit(); // prints X,--
function jit() {"jit";for (i=0;i<5;i=i+1) print(i);}
jit(); // prints 0,1,2,3,4
function nojit() {for (i=0;i<1000;i=i+1);}
function jit() {"jit";for (i=0;i<1000;i=i+1);}
t=getTime();jit();getTime()-t // 0.14 sec
t=getTime();nojit();getTime()-t // 0.28 sec
```

Run JIT on ARM and then disassemble:

```
// on ARM
function jit() {"jit";return 1;}
print(btoa(jit["\xffcod"]))
// On Linux
echo ASBL8Kz7AbQBvHBH | base64 -d > jit.bin
arm-none-eabi-objdump -D -Mforce-thumb -b binary -m cortex-m4 jit.bin
```

## Useful links


http://www.cs.cornell.edu/courses/cs414/2001FA/armcallconvention.pdf
https://developer.arm.com/documentation/ddi0308/d/Thumb-Instructions/Alphabetical-list-of-Thumb-instructions/B
https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/condition-codes-1-condition-flags-and-codes

3 changes: 2 additions & 1 deletion boards/RASPBERRYPI.py
Expand Up @@ -17,7 +17,7 @@
info = {
'name' : "Raspberry Pi",
'default_console' : "EV_USBSERIAL",
'variables' : 0, # 0 = resizable variables, rather than fixed
'variables' : 5000, # 0 = resizable variables, rather than fixed
'binary_name' : 'espruino_%v_raspberrypi',
'build' : {
'optimizeflags' : '-O3',
Expand All @@ -33,6 +33,7 @@
'makefile' : [
'LINUX=1',
'DEFINES += -DRASPBERRYPI',
'DEFINES += -DJSVAR_MALLOC=1', # This is needed for JIT testing because otherwise we can't exec from the heap
]
}
};
Expand Down
6 changes: 5 additions & 1 deletion src/jsflags.h
Expand Up @@ -22,9 +22,13 @@ typedef enum {
JSF_PRETOKENISE = 1<<1, ///< When adding functions, pre-minify them and tokenise reserved words
JSF_UNSAFE_FLASH = 1<<2, ///< Some platforms stop writes/erases to interpreter memory to stop you bricking the device accidentally - this removes that protection
JSF_UNSYNC_FILES = 1<<3, ///< When accessing files, *don't* flush all data to the SD card after each command. Faster, but risky if power is lost
#ifdef ESPR_JIT
JSF_JIT_DEBUG = 1<<4, ///< When JIT enabled,
#endif
} PACKED_FLAGS JsFlags;

#define JSFLAG_NAMES "deepSleep\0pretokenise\0unsafeFlash\0unsyncFiles\0"

#define JSFLAG_NAMES "deepSleep\0pretokenise\0unsafeFlash\0unsyncFiles\0jitDebug\0"
// NOTE: \0 also added by compiler - two \0's are required!

extern volatile JsFlags jsFlags;
Expand Down

0 comments on commit b75a36a

Please sign in to comment.