Smart.js IoT platform

by Sergey Lyubka, Cesanta

@CesantaHQ

Connected product architecture

IoT platform components

  • Firmware
    • Filesystem
    • Configuration infrastructure
    • Ability to write logic in small JavaScript snippet
    • Hardware and networking API, C and JavaScript
  • Cloud part
    • Data collection
    • Remove firmware update
    • Remote device control

JavaScript environment

JavaScript Engine

JS standard

library

Host program

Host Program API

JavaScript Application

Browser environment

JavaScript Engine

Object, Function, Date, String, Regex,

Browser (C/C++)

JavaScript Application

Error, Array, Math, eval(), parseInt(), ...

DOM API

STDLIB API

Document, Window, Event, Node, Element, URL, ...

WebSocket, document, window, location, ...

Browser environment

JavaScript Engine (V7, Rhino, ...)

Object, Function, Date, String, Regex,

Browser (C/C++)

document.write('hello world')

Error, Array, Math, eval(), parseInt(), ...

DOM API

STDLIB API

Document, Window, Event, Node, Element, URL, ...

document, window, location, ...

Node.js environment

JavaScript Engine (V8)

Object, Function, Date, String, Regex,

node process

 fs = require('fs'); fs.readFile('/etc/hosts', 'utf8', function (err,data) {
  console.log(arguments);
 });

Error, Array, Math, eval(), parseInt(), ...

Node.js API

STDLIB API

process, net, console, fs, http, os, ...

Smart.js environment

JavaScript Engine (V7)

Object, Function, Date, String, Regex,

Low-level SDK

GPIO.setmode(pin, 0, 0);
GPIO.write(pin, !level);

Error, Array, Math, eval(), parseInt(), ...

Smart.js API

STDLIB API

File, Websocket, Http, GPIO, I2C, 

ESP8266 - a typical IoT Device

  • 32-bit 160Mhz Tensilica processor
  • 50k RAM (*)
  • 512k (or 4M) Flash

Size comparison

Node.js (*) / ESP8266 / Smart.js

180k

16k

512k

50k

11M

7.6M

Disk/Flash

RAM

Smart.js Key Components

  • Mongoose networking library, C/C++
    • Mature - developed since 2004
    • TCP, UDP, HTTP, Websocket, DNS, MQTT, ...
    • Used in e.g. Apple maps, NASA, Samsung,  ...
  • V7 JavaScript engine
    • development started in 2013
    • smallest in the world
    • design goals: simple C/C++ API, small footprint

Demo with Nodemcu

V7 JavaScript Engine

  • Careful data structure design
  • Multiple techniques to make footprint small
  • Few to note:
    • NaN packing
    • Coroutine-based parser

NaN Packing

//  Double-precision floating-point number, IEEE 754
//
//  64 bit (8 bytes) in total
//  1  bit sign
//  11 bits exponent
//  52 bits mantissa
// 
//      7         6        5        4        3        2        1        0
//  seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm
//
//  11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000001  NaN



//  V7 NaN-packing:
//    sign and exponent is 0xffff
//    4 bits specify type
//    48 bits specify value
//
//  11111111|1111xxxx|00000000|00000000|00000000|00000000|00000000|00000000  NaN
//   NaN marker |type|  48-bit placeholder for values: pointers, strings
//

typedef uint64_t v7_val_t;

#define V7_TAG_BOOLEAN ((uint64_t) 0xFFFC << 48)
#define V7_TAG_FUNCTION ((uint64_t) 0xFFF5 << 48)  /* JavaScript function */
#define V7_TAG_OBJECT ((uint64_t) 0xFFFF << 48)    /* JavaScript object */
#define V7_TAG_REGEXP ((uint64_t) 0xFFF2 << 48)    /* RegExp */
  • The largest JavaScript scalar type is Numbe - IEEE 754 double
  • 1 sign bit, 11 bits exponent, 52 bits mantissa
  • If exp bits are all 1 and the mantissa is non-0, the number is NaN

Coroutine Based Parser


   var x = 1 + (2 + (3 + (4 + (5 + (6 + (7))))));
  • Why this could be a problem?

Coroutine Based Parser


   var x = 1 + (2 + (3 + (4 + (5 + (6 + (7))))));
  • Recursive-descent parser is used
  • Expression parsing results in the function call chain, according to JavaScript grammar
  • parse_statement() calls parse_var() calls parse_binary()...

Coroutine Based Parser

(lldb) bt
* thread #1: tid = 0x59fbd, 0x00000001000312c7 v7`get_tok [inlined] parse_number(s=<unavailable>, end=0x0000000100801560, num=0x00000001008015b0) at v7.c:3306, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001000312c7 v7`get_tok [inlined] parse_number(s=<unavailable>, end=0x0000000100801560, num=0x00000001008015b0) at v7.c:3306
    frame #1: 0x00000001000312c7 v7`get_tok(s=0x0000000100801560, n=0x00000001008015b0, prev_tok=<unavailable>) + 695 at v7.c:3470
    frame #2: 0x000000010000a440 v7`parse_prefix [inlined] next_tok + 100 at v7.c:7485
    frame #3: 0x000000010000a3dc v7`parse_prefix(v7=0x0000000100801200, a=0x0000000100200100) + 1068 at v7.c:7745
    frame #4: 0x0000000100031ded v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=11, pos=63) + 45 at v7.c:7812
    frame #5: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=10, pos=63) + 73 at v7.c:7814
    frame #6: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=9, pos=63) + 73 at v7.c:7814
    frame #7: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=8, pos=63) + 73 at v7.c:7814
    frame #8: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=7, pos=63) + 73 at v7.c:7814
    frame #9: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=6, pos=63) + 73 at v7.c:7814
    frame #10: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=5, pos=63) + 73 at v7.c:7814
    frame #11: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=4, pos=63) + 73 at v7.c:7814
    frame #12: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=3, pos=63) + 73 at v7.c:7814
    frame #13: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=2, pos=63) + 73 at v7.c:7814
    frame #14: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=1, pos=63) + 73 at v7.c:7814
    frame #15: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=0, pos=63) + 73 at v7.c:7814
    frame #16: 0x0000000100030f13 v7`parse_var [inlined] parse_assign(v7=0x0000000100801200, a=0x0000000100200100) + 14 at v7.c:7855
    frame #17: 0x0000000100030f05 v7`parse_var(v7=0x0000000100801200, a=0x0000000100200100) + 741 at v7.c:7888
    frame #18: 0x000000010002e38e v7`parse_statement(v7=0x0000000100801200, a=0x0000000100200100) + 5614 at v7.c:8146
    frame #19: 0x000000010002c66b v7`parse_body(v7=0x0000000100801200, a=0x0000000100200100, end=TOK_CLOSE_CURLY) + 571 at v7.c:8223
    frame #20: 0x000000010002cc43 v7`parse_funcdecl(v7=0x0000000100801200, a=0x0000000100200100, require_named=<unavailable>, reserved_name=<unavailable>) + 1331 at v7.c:8190
    frame #21: 0x00000001000327e5 v7`parse_newexpr(v7=0x0000000100801200, a=0x0000000100200100) + 485 at v7.c:7643
    frame #22: 0x000000010000a6a3 v7`parse_prefix [inlined] parse_callexpr(v7=0x0000000100801200, a=0x0000000100200100) + 15 at v7.c:7695
    frame #23: 0x000000010000a694 v7`parse_prefix [inlined] parse_postfix(a=0x0000000100200100) + 4 at v7.c:7717
    frame #24: 0x000000010000a690 v7`parse_prefix(v7=0x0000000100801200, a=0x0000000100200100) + 1760 at v7.c:7777
    frame #25: 0x0000000100031ded v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=11, pos=32) + 45 at v7.c:7812
    frame #26: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=10, pos=32) + 73 at v7.c:7814
    frame #27: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=9, pos=32) + 73 at v7.c:7814
    frame #28: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=8, pos=32) + 73 at v7.c:7814
    frame #29: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=7, pos=32) + 73 at v7.c:7814
    frame #30: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=6, pos=32) + 73 at v7.c:7814
    frame #31: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=5, pos=32) + 73 at v7.c:7814
    frame #32: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=4, pos=32) + 73 at v7.c:7814
    frame #33: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=3, pos=32) + 73 at v7.c:7814
    frame #34: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=2, pos=32) + 73 at v7.c:7814
    frame #35: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=1, pos=32) + 73 at v7.c:7814
    frame #36: 0x0000000100031e09 v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=0, pos=32) + 73 at v7.c:7814
    frame #37: 0x0000000100031f8f v7`parse_binary(v7=0x0000000100801200, a=0x0000000100200100, level=<unavailable>, pos=5) + 463 at v7.c:7842
    frame #38: 0x000000010002d153 v7`parse_statement [inlined] parse_assign(v7=0x0000000100801200, a=0x0000000100200100) + 947 at v7.c:7855
    frame #39: 0x000000010002d13c v7`parse_statement [inlined] parse_expression(a=0x0000000100200100) + 4 at v7.c:7862
    frame #40: 0x000000010002d138 v7`parse_statement(v7=0x0000000100801200, a=0x0000000100200100) + 920 at v7.c:8157
    frame #41: 0x000000010002c66b v7`parse_body(v7=0x0000000100801200, a=0x0000000100200100, end=TOK_END_OF_INPUT) + 571 at v7.c:8223
    frame #42: 0x0000000100001a74 v7`parse [inlined] parse_script(v7=0x0000000100801200, a=0x0000000100200100) + 398 at v7.c:8249
    frame #43: 0x00000001000018e6 v7`parse(verbose=<unavailable>, v7=<unavailable>, a=<unavailable>, src=<unavailable>) + 230 at v7.c:8275
    frame #44: 0x000000010000bacd v7`v7_exec_with(v7=0x0000000100801200, res=0x00007fff5fbfefb0, src=0x0000000100035710, w=18445899648779419648) + 285 at v7.c:9909
    frame #45: 0x0000000100009891 v7`init_stdlib [inlined] v7_exec(res=0x0000000000000366, src=<unavailable>) + 15745 at v7.c:9931
    frame #46: 0x000000010000986e v7`init_stdlib [inlined] init_js_stdlib at v7.c:13545
    frame #47: 0x000000010000986e v7`init_stdlib(v7=0x0000000100801200) + 15710 at v7.c:13532
    frame #48: 0x00000001000017da v7`v7_create + 426 at v7.c:7022
    frame #49: 0x0000000100012ec9 v7`v7_main(argc=3, argv=0x00007fff5fbff920, init_func=0x0000000000000000) + 57 at v7.c:12156
    frame #50: 0x000000010001346b v7`main(argc=<unavailable>, argv=<unavailable>) + 11 at v7.c:12221
    frame #51: 0x00007fff908615fd libdyld.dylib`start + 1

Ending up with C call stack like this:

Coroutine Based Parser

  • Embedded systems do not have gigabytes of stack size
  • Kilobytes, sometimes less than 1 kilobyte
  • What to do?
  • Using parser generators?
    • Result code is too big
  • Flatten the parser?
    • Loose algorithmic clarity of the parser, by having function call chain
  • Coroutines!
  • Code is on Github, function parse()
    • Careful: that code violates any coding standard

Coroutine Based Parser

  • Traditional function: 1 entry point, and multiple return points
  • Coroutine: multiple entry points and multiple return points
  • It preserves state
co = coroutine.create(function(n)
  for i = 1, n do
    coroutine.yield(i)
  end
  coroutine.yield(-1)
end)

print(coroutine.resume(co, 3));
print(coroutine.resume(co));
print(coroutine.resume(co));
print(coroutine.resume(co));


$ lua co.lua
true	1
true	2
true	3
true	-1

Coroutine Based Parser

lsm@callisto ~/src/cesanta.com]$ lldb v7/v7
(lldb) target create "v7/v7"
Current executable set to 'v7/v7' (x86_64).
(lldb) b parse_number
Breakpoint 1: 2 locations.
(lldb) process launch -- -e 'var x = 1 + (2 + (3))'
Process 10349 launched: '/Users/lsm/src/cesanta.com/v7/v7' (x86_64)
Process 10349 stopped
* thread #1: tid = 0x5a2b8, 0x000000010000af64 v7`parse_number(s=0x0000000100037604, end=0x00000001008015c8, num=0x0000000100801618) + 20 at tokenizer.c:132, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x000000010000af64 v7`parse_number(s=0x0000000100037604, end=0x00000001008015c8, num=0x0000000100801618) + 20 at tokenizer.c:132
   129 	}
   130
   131 	static void parse_number(const char *s, const char **end, double *num) {
-> 132 	  *num = strtod(s, (char **) end);
   133 	}
   134
   135 	static enum v7_tok parse_str_literal(const char **p) {
(lldb) bt
* thread #1: tid = 0x5a2b8, 0x000000010000af64 v7`parse_number(s=0x0000000100037604, end=0x00000001008015c8, num=0x0000000100801618) + 20 at tokenizer.c:132, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010000af64 v7`parse_number(s=0x0000000100037604, end=0x00000001008015c8, num=0x0000000100801618) + 20 at tokenizer.c:132
    frame #1: 0x000000010000a4da v7`get_tok(s=0x00000001008015c8, n=0x0000000100801618, prev_tok=TOK_OPEN_PAREN) + 874 at tokenizer.c:296
    frame #2: 0x00000001000194f4 v7`next_tok(v7=0x0000000100801200) + 196 at parser.c:1248
    frame #3: 0x000000010001630d v7`parser_cr_exec(p_ctx=0x00007fff5fbfe9d8, v7=0x0000000100801200, a=0x0000000100200160) + 8157 at parser.c:1836
    frame #4: 0x000000010001980f v7`parse(v7=0x0000000100801200, a=0x0000000100200160, src=0x0000000100037580, verbose=1, is_json=0) + 415 at parser.c:2454
    frame #5: 0x000000010001b454 v7`i_exec(v7=0x0000000100801200, src=0x0000000100037580, src_len=0, res=0x00007fff5fbfed10, w=18445899648779419648, is_json=0, fr=0) + 612 at interpreter.c:1838
    frame #6: 0x000000010001d4ad v7`v7_exec(v7=0x0000000100801200, src=0x0000000100037580, res=0x00007fff5fbfed10) + 93 at interpreter.c:1923
    frame #7: 0x000000010002302c v7`init_js_stdlib(v7=0x0000000100801200) + 60 at js_stdlib.c:132
    frame #8: 0x0000000100022fa6 v7`init_stdlib(v7=0x0000000100801200) + 710 at stdlib.c:199
    frame #9: 0x0000000100012440 v7`v7_create_opt(opts=v7_create_opts at 0x00007fff5fbfedb0) + 480 at vm.c:1831
    frame #10: 0x000000010003645d v7`v7_main(argc=3, argv=0x00007fff5fbff720, init_func=0x0000000000000000, fini_func=0x0000000000000000) + 1181 at main.c:126
    frame #11: 0x0000000100036b92 v7`main(argc=3, argv=0x00
  • Coroutine based parser reduces call chain 
  • From 40+ frames to just 5:

References

  • Code on Github:
    • https://github.com/cesanta/mongoose
    • https://github.com/cesanta/v7
    • https://github.com/cesanta/smart.js
  • Documentation:
    • https://www.cesanta.com/developer/

Thank You!

contact me at

sergey.lyubka@cesanta.com

Made with Slides.com