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
Smart.js IoT platform
By Sergey Lyubka
Smart.js IoT platform
Describes the architecture of Smart.js, a JavaScript-enabled firmware for the embedded devices
- 1,117