Elk: A low footprint JavaScript engine for embedded systems
github.com
github.com
It doesn't have an AST or a bytecode VM. It just interprets directly off of source code.
Take a look: https://github.com/cesanta/elk/blob/master/elk.c.
The first time I scanned through the file and thought there was nothing there. It does an entire implementation in the same amount of code that most implementations do a parser alone (but yes, it implements way less than a 100% spec compliant parser does). This implementation really sets a new bar for me in terms of compact-but-readable language implementations.
Separately, this isn't even Cesanta's only embedded JavaScript implementation. They also have: https://github.com/cesanta/mjs. mjs is a bit more complete and does have a bytecode VM and thus the source has many more lines of code and files.
At the end of the day, that's all that matters for it to become reality. if you want something like this but X language, I'm sure there are similar projects, or you can build one yourself.
I have no use for this and don't like JS, but I appreciate and respect the efforts put into this project, especially with the clean implementation.
The ecosystem is gargantuan, npm is great with pnpm, and all of the annoying parts of client side JS is made fun and easy with Svelte. With TS, CSS, and HTML (and Svelte bringing out their full potential), I can quickly make almost any app I want with incredible DX, comprehensive tooling, and next to no boilerplate. Every year it gets faster and faster, and when it’s not fast enough, there is WASM.
I would genuinely love to know what is so bad about it, and what I’m missing out on!
another is the ecosystem. pnpm feels like a bandaid to a bad package management system . the whole 'node_modules' folder ends up being a polluted mess very quickly. I import a small library and I feel like I download half of npm registry with it.
any time I've wanted to do anything in the JS world, I have to download 30 different frameworks, that don't quite mesh well together. svelte+tailwind a year ago was a disaster to setup with rollup. had to switch to webpack which pulled in a bunch of other stuff and svelte broke.
I think the biggest problem is interop and all the translators and transpilers in a standard application. between Babylon, es6/7/8/next/jsx/TS/tsx transpilers. the scss, css, transpilers. media pipelines and converters. and half the time if you don't set them up just right. they don't play well together.
maybe it's just not my world. I'm a backend/DevOps/systems dev. and while many languages have similar problems, rarely do they have ALL of these at the same time.
back to the language, I feel like early on all the prototype overriding/monkeypatching/etc made for a very very messy language. I'm familiar it's gotten better, but all that cruft is there and still used often.
finally, while all the WebApps have been good, I hate how EVERYTHING is using JS on the web, even for useless things, or things that don't need to be JS. I hate how many sites literally are blank I'd you don't have JS enabled. it's also slowed down the web a lot. things take forever to load.
so I have many factors that contribute to make having a disdain for JS, both social and technical.
and I single out JS because I like programming languages and learn and use most of them, JS suffers from this problem the most. anyways, sorry, I did not intend to argue over this. so I will refrain from continuing. I have my personal views on JS and I was trying not to detail the thread, seems I failed.
A few things that aren't great for my use cases:
- The language has its fair share of footguns. It's a much better language than most people give it credit for, and most of them are easily avoided by a good linter, but still doesn't feel as clean as something like Python.
- The ecosystem is great but it's hard to separate the wheat from the chaff. node_modules with gigabytes of dependencies isn't even funny. I'd still classify the ecosystem as a big win for JS, but there are things that could be handled better.
- The standard library is awful and some parts are downright broken (e.g. dates). If I'm using C++, Java or Python I have at my fingertips very powerful data structures an import away. Many of those things are also available for JS, but they come as separate dependencies.
- Having the same language full-stack is nice, but client-side JS and server-side JS are different enough that there's going to be a context switch regardless. That point is true but somewhat oversold.
(You didn't say this and I'm not going to claim you did, but don't even get me started on how full stack JS evangelists try to sell you mongoDB-like datastores. There is a 99% chance the correct place to put your data is a SQL database.)
- It doesn't play nice with C.
- Doesn't lend itself to quick stuff outside the browser/web server paradigm. Things like OS scripting or small ad-hoc programs.
I would imagine that you can run those in Deno (for example ) just fine?
It's not a problem for larger projects, but very clunky in a script.
In 2021, nobody likes programming efficiently in native languages anymore it seems.
The reason that people write GUI apps in Electron instead of Qt/Gtk is because the latter is so far behind technologically that it feels like ancient technology. Electron has GPU-accelerated, declarative graphics in CSS, it has React, it has the web inspector, it has a scripting language with an extremely good JIT.
Qt/Gtk has not adapted to the state of the art, it has stayed static while the world has moved on. It's now 20 years out of date. That is why people use Electron.
Since you named it explicitly - why would my application want a web inspector or CSS layouting overhead if the goal is to display a small batch of controls? This is exactly the mindset I meant when I wrote my post.
If GTK had received a fraction of the attention and misdirected enthusiasm that went into bolstering the NodeJS ecosystem, things would be very different.
* No var, no const. Use let (strict mode only)
* No do, switch, for. Use while
* No => functions. Use let f = function(...) {...};
* No arrays, closures, prototypes, this, new, delete
* No standard library: no Date, Regexp, Function, String, Number
Seems more "JS inspired" than actual JavaScript.QuickJS is small compared to say, V8, but quite a lot larger than Elk. Just the main C source file for QuickJS is ~1.7MB, and there's several other source files.
Just saying they seem to be in different niches.
case TOK_CASE: case TOK_CATCH: case TOK_CLASS: case TOK_CONST:
case TOK_DEFAULT: case TOK_DELETE: case TOK_DO: case TOK_FINALLY:
case TOK_FOR: case TOK_IN: case TOK_INSTANCEOF: case TOK_NEW:
case TOK_SWITCH: case TOK_THIS: case TOK_THROW: case TOK_TRY:
case TOK_VAR: case TOK_VOID: case TOK_WITH: case TOK_YIELD:
res = js_err(js, "'%.*s' not implemented", (int) js->tlen, js->code + js->toff);
break;
So that's quite a lot of the language I guess, but it does say that it's a sub-set so of course that's fine. Very impressive code, it really shows that the developer has an eye to minimizing the memory footprint. I was fooled for a couple of seconds by the init call: char mem[200];
struct js *js = js_create(mem, sizeof(mem));
Since it really "feels" like a function that creates a struct instance on the heap, but of course it does not: it's created inside the working memory passed in (so if it works, js == mem).Trust but verify, but also I'll feel mislead if it does since it's an extremely common pattern
I've used QuickJS but when I got stuck on an issue (stack overflow in win32 because of passing by-value) I got no help from the tiny community on the mailing list and had to abandon the attempt and go back to (overweight) V8.
Boy oh boy I did not expect to run into memory issues so quickly. Including async/await increases the size of the build so much the esp just dies.
But most npm packages worked just fine, including lodash ( a life saver), as long as you’re willing to work with the super tiny memory budget and the difficulty to detect memory leaks.
Using an esp32 with 8mb of PSRAM yielded much better results.
Good times.
You might have been over doing it! /s
I regularly develop intensive apps in Espruino for the ESP32 (I mentioned in another thread how I pull in exchange data directly using HTTPS, and process it on the TTGO Watch 2020), but it's all hand crafted code. Maybe you should consider a small Friendly Elec board and node...
...and yes PSRAM is super awesome! :)
You’re completely right! Fed up with Arduino C, I tried to bring the entirety of the web toolchain and related dev patterns to the embedded world. :’)
What you’re doing sounds super cool! Is it just as a hobby or your day job as well?
Do you have any tips on debugging long running memory leaks? (basically the board crashes after a few days running continuously, which were my specs)
Nowadays I’m back to Arduino C and the M5Stack/M5Paper boards, it’s a bunch of fun when I find time for it!
It's kind of a serious hobby. I enjoy it.
In Espruino the memory leaks are always almost the variables. So you can do say process.memory() to see if indeed your memory is decreasing, and if it is, then you can E.getSizeOf(global) to see which variable it might be. It's still javascript at the end so, standard techniques apply.
Yeah I like the M5Stack boards, they're quite interesting. I use the their StickC. The orange one. It does take time for sure!
[1]: http://sam.zoy.org/elk/ [2]: http://www-rn.informatik.uni-bremen.de/software/elk/
Elk Scheme has been around since nineteen eighty seven. Given the power of embeddedish devices these days, I wonder if Elk Scheme would be a useful embedded language implementation in 2021? Sadly I don't have the cycles to experiment.
You'd have to build some hardware abstraction to use with the scripting language anyways at which point you could just use the language you wrote the abstraction in imo. Is it so you can have inexperienced (cheaper?) devs do the maintainence? For all the embedded projects I worked on the high level program logic wasn't the thing that took the most effort.
Knowing how to do it vs using a higher DSL is always going to be nicer especially to gain new users. Look at the sega genesis/mega drive SDK. Many 2020+ Sega Genesis/Megadrive games are built using it. It is C. Why not use 68000 assembler like every developer before now? Documentation, ease of use, community, cross domain developers can pick it up easier...
Same thing applies here.
I believe C is much too fragile for interpreters to be written in them, especially for high-usage scenarios like the web.
Enumerating and analyzing 40 non-V8 JavaScript implementations
* Lua already exists and is better suited to this
* JS is a relatively complicated language to evaluate
* JS requires a large amount of dynamic allocation
* JS isn't really the first thing (or in the top 25 things) I would pick when developing on a platform where I wasn't already stuck with it
One of the top listed features of this interpreter is a fixed memory footprint
- JS is a relatively complicated language to evaluate
In developing this interpreter they eliminated some of the languages features to make it simpler.
That means you can't run general-purpose code safely, which means you should probably write/rewrite/adapt it, which means you might as well use a different language.
I'm a JS developer, but the parent might have a point here. Why run something like this in production when you're likely to end up in unexpected situations? Either a runtime is compliant, or you're going to have a bad time.
The project is cool, but I wouldn't use it as an example for what JS can do.
In embedded systems that is often a necessity but calling it a feature instead of a constraint is a good idea.
The example with ESP32 is quite, the hardware is much more powerful than pre-Windows PCs, so why pretend it is something where C and Assembly are the only answer, when MS-DOS had plenty of languages to chose from.
Literally just yesterday I was trying to find the equivalent for Java, byte code or otherwise. I want to attempt to run several nano JVM apps on an ESP32, but I'm starting to think that it might not be possible.
Many years ago now I ran a custom 'script runner' that fit into 512 bytes of assembly language for a custom kernel (that was also 512 bytes) [1]. I would really like to play with something like Elk and see what can be done!
[1] https://bitbucket.org/danielbarry/saxoperatingsystem/src/mas...
Don't get me wrong, this is a cool project, but I don't see why you would ever choose to use JS in an embedded context except for fun. The arguments for using lua or micropython are already very narrowly applicable.
https://www.microej.com/product/sdk/
https://developer.microej.com/supported-embedded-runtime-arc...
I did see microej but I'm specifically interested in open-source - and from what I can tell this is some kind of paid service. I didn't look particularly close though [1]. The ESP32 device they do support appears to be a very specific development kit [2].
[1] https://github.com/MicroEJ
[2] https://github.com/MicroEJ/Platform-Espressif-ESP-WROVER-KIT...
I was just looking at the Java bytecode and thinking "in theory, one could build a small JVM for this" [1]. But it still looks like an enormous effort to build, test and then use in a meaningful way.
[1] https://en.wikipedia.org/wiki/Java_bytecode_instruction_list...
Totally on topic. I dislike JS but this is rad. And like upthread said, clean understandable implementation. Very educational.
Forth is at that size. Tcl implementations are at least triple that. As are all the Schemes I have seen. Lua similarly.
Anything I've missed?
Embedded development is the bat country of software. You will definitely have to do some unsavory things to get where you're trying to go.
Pretty big oversight of the author not to call out their lack of association with the 153 year old fraternal order.
JS has some features that wouldn't make it ideal (e.g. no difference between ints and floats). I don't think it would be hard for developers to learn slightly different syntax.
If it's a big subset or the full language, I can see the benefits of using existing JS apps. But with a small subset and presumably for a small computer, I don't think most of those apps would work anyways.
I believe Arduino has a C++ transpiler. I would almost prefer Javascript here, although you could just use C as a subset.
Plenty of modern µC, e.g. ESP32, are more powerful than computers like these ones,
https://en.wikipedia.org/wiki/PC1512
As exercise for the reader check the programming languages available up to MS-DOS 6.22.
It is about time to move on from the mindset that µC are only PICs with 4 KB.
I think ESPXXXs usually have < 500kb ram. Still quite tight.
COM executables were 64KB, and EXE could use as much as they wanted from 512 - 640 KB in multiples of 64 KB, minus the MS-DOS resident size.
Naturally stuff like HMA came later into play with MS-DOS 5, which wasn't something that MS-DOS 3.3, again from the PC above, was capable of.
Or if you prefer, I refer to what was possible with 64 - 128 KB on Timex 2068, Spectrum 128 +3A (with CP/M), Commodore 64 with GeOS,...
Which, yes games would be coded in Assembly, there was business stuff being sold and coded in BASIC, Pascal, Forth,...
Why not some other? You're free to build one yourself for whatever language you choose.