Crafting Interpreters: Strings
craftinginterpreters.com
craftinginterpreters.com
Waves awkwardly, shuffles feet.
https://github.com/munificent/craftinginterpreters/blob/mast...
> 2018/03/29 - pancakes
:)
This chapter introduces not only strings but the concept of more complex value types that use an auxiliary struct. There's an anecdote related to that which you might find amusing - and maybe a bit appalling.
In the implementation of perl, there's multiple kinds of integer value type in order to support it's complex implicit casting semantics and additional edge cases. To pick the least wonky, there's plain integer values, "IVs", and those which have a stringified representation of the int attached for caching the expensive conversion, "PVIVs". The former store the int in the primary value struct. The latter use an auxiliary struct with extra space.
Now, the reason for all this prep is that in order to make the two behave the same, and not introduce a lot of branches to low level code, perl always uses the indirect access to the integer value (essentially "x->aux.intval"). This works because for basic integer values, it initializes the aux pointer in the main struct to point to the right place in memory before the location of the main struct such that this access always points at the right slot of the main struct. Madness! So much fun!
(A better explanation is at https://medium.com/booking-com-development/how-we-spent-two-...)
The perl implementation is full of this sort of stuff. Some corners are hardly penetrable because of it. I'll be visiting the offices in MSN and KIR/SEA next week. Happy to rant over coffee or a drink some night if timing works out.
This might be a naive question, but how do you do all that redirection under concurrent accesses? Clearly that amount of rewiring isn't going to be atomic? Or am I misunderstand and the only change that needs to be atomic is the top-of-structure redirection?
If you want more info, the is a good presentation by Christine Flood about Shenandoah at FOSDEM 2017 https://archive.fosdem.org/2017/schedule/event/shenandoah/
PS: I am enjoying the design and writing. Kudos sir!
[1]: http://www.craftinginterpreters.com/the-lox-language.html
Also, either with a "compact" representation (Wren) or a less compact one like Lox, have you tried storage small strings in the object itself (<= 7 bytes for a 64-bit object size, or <= 15 bytes for two 64-bit words), and how much difference would that make?
This is a really really hard question to answer with any generality. Especially when you talk about percentage of perf improvements, then the performance of the rest of the system directly impacts the gain you see from changing the value representation.
At the time that I implemented it in Wren, using NaN tagging made about a 10% improvement in the handful of microbenchmarks I was using at the time, on my machine. You can toggle NaN tagging on and off with a #define, so it's worth checking the difference today on different benchmarks and on top of the other changes I've made to Wren since then.
The last I checked, Wren spent a large fraction of time in instruction decoding and dispatch (not uncommon for a bytecode VM). In an interpreter where that's more efficient (because of a JIT, threaded code, superinstructions, peephole optimizations, etc.), the value representation will probably make a larger difference.
> have you tried storage small strings in the object itself (<= 7 bytes for a 64-bit object size, or <= 15 bytes for two 64-bit words), and how much difference would that make?
I haven't, though I've briefly toyed with it. With Wren, I'm trying really hard to keep the implementation small and simple, so I try to avoid special-case optimizations like this that add a lot of complexity.
It might be worth it, but I'm not sure. It's a hard call to make. Each optimization or little feature on its own seems small, but when you add them all up, it can be the difference between an approachable, hackable codebase, and a big sprawling one.
(Bravo)
Implimented my own interpreter of the back of the original series. Managed to get a type system and all sorts added.
The author was even responsive via email giving me hints on where to investigate extending it further. (belive I was trying to add lists at the time).
Having followed a ton of 'how to write a programming language / compiler / ect tutorials'.
Its the first one to really make me grok what's going on and move beyond just copy pasting examples.
So thanks Bob. Keep up the awesome work!
I have some spare change kicking in a bitcoin wallet gathering dust and would like to buy you a coffee got an address I can send it to?
Couldn't see one on the site or your blog.
Until then, just enjoy it. :)
struct sObjString {
Obj obj;
int length;
char* chars;
};
needlessly wastes 4 or 8 bytes for the chars pointer (among other heap management overhead related to separate allocations).Instead, I think, you should have
struct sObjString {
Obj obj;
int length;
char chars[0];
};
and allocate that string object as one chunk with its characters.A little thing in your section about the different UTFs: I've heard UTF-16 is a lot better than UTF-8 for East Asian languages because pretty much all those codepoints are multibyte. I don't _necessarily_ think anything you wrote was invalid, but just FYI.
> Alas, if your language needs to run on or interoperate with the browser, the JVM, or the CLR, you might be stuck with [UTF-16]
And Windows. Win32 and COM APIs use UTF-16 natively, so if you want to avoid lots of conversions on Windows, UTF-16 is the way to go. Maybe the best solution would be an abstraction that lets you choose the internal string encoding at compile time.
madness,