Older blog entries for wingo (starting at number 363)

value representation in javascript implementations

In my last note I mentioned that I had been doing a lot of reading of JavaScript implementation code. One point that I didn't mention is that the state of the art is completely undocumented. So to work on rectifying that, here's the first in what might be a series of articles about the internals of JavaScript implementations.

tagging

Initially, all JavaScript implementations used tagged pointers to represent JS values. This is a old trick that comes from the observation that allocated memory takes up at least 4 or 8 bytes, and are aligned in such a way that the least significant bit or three will be zero.

Here's an old part of the Guile manual that explains the technique. For example you could take values whose lowest bit is 1 to be a pointer to the heap. Any value that ends in 0 could be an integer, represented directly in the bits of the pointer. (If your pointer tag is not 0, you will have to mask off the low bits before dereferencing a pointer..)

Google's V8 JavaScript engine still does it this way. This yields 31-bit immediate signed integers; you can just do a signed right-shift to get the value. They call them "smi" values, for "small integers"; they are more commonly called "fixnums". There are other fun tricks you can do to avoid shifts for some common operations, like addition. Note that they also take advantage of the four-byte alignment (in their case) to encode another immediate value, "failure objects", using the other low bit.

The size of a tagged pointer is the size of a pointer, so 32 bits on a 32-bit machine, and 64 on a 64-bit machine. V8 doesn't actually use 63-bit smi values on 64-bit machines, AFAIK.

nan-boxing

JavaScriptCore, the implementation used by WebKit, switched from tagged pointers to a "nan-boxed" format. The best explanation of this strategy is given by Rob Sayre of Mozilla. I believe the technique was first made popular by LuaJIT, though I would appreciate additional references.

Sayre does a good job of explaining things, but to recap, there are about 253 bit patterns that represent not-a-number values for IEEE-754 double-precision floats, but that only one of those is actually emitted by current hardware and software. So cheeky implementations are free to use the other patterns for their own purposes.

What I always wondered when I heard about this strategy was how 53 bits were possibly enough, for a 64-bit machine. How are you going to fit a pointer in that space?

Well it turns out that on x64-64 systems, you only have a 48-bit address space, currently anyway. The shape of that space is quite peculiar, too. I don't know whether Linux gives out addresses in the upper half, but Windows does not, though Solaris does.

Anyway, the other way of looking at NaN-boxing is that there are 264 - 248 values in a pointer that aren't being used, and we might as well stuff something in them, and hey, the whole space of valid double precision numbers fits!

It's convenient too that numbers in JS are specified as doubles. (Even still, all implementations define separate "small integer" types, for use in loops and indexing and such; integer operations are faster when you can use them, too.)

JSC's implementation of nan-boxing is described here, and you can see some of the neat tricks they do here. It actually makes me envious of C++, as a C programmer!

nun-boxing

So, when you choose to do nan-boxing, you basically choose to do one of two things: you favor pointers, or you favor doubles. To favor pointers means that you recognize pointers as having initial (most-significant) 0 bits; if the initial bits are not 0, then it's a double, and you have to add or subtract a bit pattern to get to the double value.

Favoring doubles means that pointers are left as NaN values, so their initial bits are all ones (or the sign bit can be unset, it doesn't matter), and to unpack a pointer, you have to rotate the double space around.

Amusingly, there is a third option as well. For 32-bit machines, you can address the second word in the double directly, so there is no need to mask off anything. This is the JSVALUE32_64 case mentioned in the JSValue code I linked to above.

JSC chose to favor pointers, and as the first JS implementation to nan-box, got to call their choice "nan-boxing". Mozilla chose to favor doubles, and so made up the silly name "nun-boxing".

get thee to a nun boxery?

So you're implementing a language. Should you nan-box or not? I can't say in your case but I can give a couple of examples.

NaN-boxing has the obvious advantage of not allocating doubles on the heap. This reduces cache pressure, GC pressure, and such. That's why Moz and JSC chose it.

V8 on the other hand has not chosen it, at least not yet, anyway. I think that one reason is because especially on embedded devices, and to an extent on ia32, passing around 64-bit values is a big lose. It's so bad that I understand that Mozilla actually passes these values by reference instead of by value in some places, on 32-bit systems only.

But the real reason that V8 doesn't nan-box I think is that they have a compiler that is able to unbox values in registers and in temporary stack locations, both as int32 values and as double values. This works for loops and such things in a function, and is obviously quite efficient, as you don't need to box and unbox at all. Passing doubles as arguments or return values however does allocate them on the heap, as far as I can tell anyway.

Also there is the hackery that with NaN-boxing, you assume things about your OS's memory management. A language implementation should be able to get around this with use of mmap at specific addresses, but still, it's tricky.

I looked at doing nan-boxing for Guile, and eventually decided against it. Guile has a few more constraints that V8 does not have. Firstly it is very portable, so it can't rely the address range constraints that x86-64 has, not without some hackery. It's also portable to the low end, like V8, so 64-bit values are a lose there.

But the killer is the conservative GC on 32-bit systems. If you represent integers with the tag in the high bits, as you would with nan-boxing, then the lower 32 bits are not distinguishable from a pointer, so they can cause the GC to retain heap objects for longer than it should. With tagged pointers, the low-bit masking lets the GC know that an integer is not a pointer. If you were to reintroduce low-bit tagging to a nan-boxed system, you've lost much of the advantage of it. Furthermore if you want to support more than 32 bits of precision in a fixnum, then on a 32-bit system you need to play games with sign extension of a 48-bit value to a 64-bit value, and for all word sizes it looks like the overhead will be significantly higher than tagging.

Finally I think that if we're really going to shoot for the moon, we should implement something like V8's Crankshaft compiler that does decent type inference, to allow unboxed values in registers and on the stack. (For readers of source code, "crankshaft" is basically the "hydrogen" flow-graph code plus a "lithium" assembler. Confusing, right?)

Well that's all for now. If you corrections, or an idea for a further topic, let me know in the comments. Thanks!

Syndicated 2011-05-18 16:57:41 from wingolog

javascript in 2011

Good evening tubes,

This hacklog needs an update, and how! I meant to write some stuff about Guile, which recently got some pretty great performance improvements, but instead bits on JavaScript were what trickled out of my fingers onto this Emacs. So here we go!

kung fu

I took a fairly deep dive into the world of JavaScript implementations recently, and man, what a world: a rotated-double-space punboxed jaeger-lithium crankshafted nitro-assembling trace stack of bit-froth. Yow! Now that's a fat value!

That probably makes no sense, right, but you have to understand that I've been jacked into this matrix for like three weeks now, and they speak a different language out there. If I were to draw some preliminary conclusions about the state of the art in JavaScript implementations, they would be these:

  • Trace looked fun, but it seems that it's possible to get trace-level performance with more traditional compilation techniques. (The trick appears to be to treat tagging, untagging and type checks as nodes in your control-flow graph.) V8 already has an SSA-based intermediate language, JavaScriptCore (the WebKit JavaScript implementation) is moving to one, and the above bug is from the Mozilla folk.

  • As a correlary to the previous point, data representation matters. It seems to me that JavaScriptCore's solid improvements have all been centered on cleanliness: keeping it simple, direct, and clean leads to speed. Right now JSC is trailing the pack again, but with a good SSA compiler, they could reach V8 again. Inner loops are important, but that's not where big applications spend most of their time.

  • The JS benchmarks are terrible, but everyone is using them to optimize their implementations. There's a serious danger of garbage-in, garbage-out here.

    To my eye the V8 ones are the best general-purpose benchmarks: the ones that represent not just the inner loops, but bigger programs. But hey, maybe that's just the Schemer in me amused at seeing cross-compiled Lisp benchmarks in their suite.

  • Tool support is terrible.

    All of these projects have to implement their own profilers, disassemblers, debuggers, etc, and for all platforms, in theory. In practice, yuck. GDB just got support for JIT-generated code, and it's terrible: you have to generate whole object files (ELF on ELF architectures, presumably mach-O or whatever on Darwin, and who knows on windows!), and if you want debugging information, you have to generate DWARF.

    Of course oprofile couldn't use the same format, it has its own thing, and it's not the same as the lovely but ungooglable perf, which appears to only support map files generated by some JVM no one has heard of, but has no support for garbage-collecting executable code or for line numbers. Aaargh!!!

    Incidentally, my colleague Xan appears to have run into these issues independently.

I've been joking recently that I've been coming up like Neo, in the training sequence, eyes open, drawn breath: "whoa: I know kung-funun-boxing."

All in all JavaScript in mid-2011 is looking much better than JavaScript in mid-2010 -- humbling, even -- and I look forward to hacking more on it. Actually, simply "hacking on it", as I have been taking uncomfortably long to get up to speed on things. (No one has yet said "Really? Show me." Ha ha.)

I suspect that tools is where I'll start. It sure would be nice to have line-level profiling interleaved with disassembly for JavaScript, somehow. (I hope it would be perf, but they seem an opinionated bunch. I hope you don't have to be Peter Zijlstra to get something into it.)

forward-looking statements

I try not to make promises about the future, so consider it simply a prediction, that the next entry will feature more parentheses than this one. Until then, happy hacking!

Syndicated 2011-05-15 17:53:21 from wingolog

meta: per-tag feeds

Just a brief meta-note: I've added per-tag feeds to tekuti, based on a patch kindly provided by Brian Gough, and unkindly neglected by me these last four months.

Anyway, you can now access http://wingolog.org/feed/atom?with=guile&without=web&with=scheme and other such URLs. Should be good for planets.

Let me know if there are any problems. Thanks!

Syndicated 2011-04-25 18:13:27 from wingolog

new beginnings

Friday is my last day at Oblong. It's been good times: my colleagues have all been generous, intelligent, respectful folk. It has been a real privilege to work with them for these few years, and I wish them all the best. Oblong is in a great position to emancipate both pixels and people from the proxy world of mice and windows.

At Oblong I was mostly focused on video, but if you're a regular reader of this electro-rag, you'd see that my passion is more on the side of compilers and free software, especially Guile. So when the opportunity presented itself to do something more directly aligned with those interests, I let myself be tempted.

igalia ho!

So what's next? Igalia is what's next! I'll join their new compilers group later this month, with an initial focus on JavaScriptCore optimization work and on Guile projects.

Igalia is a Spanish free software consulting shop that is fairly well-known in the GNOME community, but perhaps not so widely outside of it. I admit: it is refreshing (and relieving) to return to free software "on the clock", and to hack compilers for a living. This is fantastic. But that's not the thing that's really awesome (and unique, as far as I know) about Igalia.

No, the truly great thing is that Igalia is an entirely egalitarian organization. All important decisions are taken by the assembly, using consensus and similar collective decision-making procedures. That includes research, business development, budgets, hiring, salaries: everything. After a few years working there you become a partner, which makes you an equal co-owner of the business.

Wild, no? And wonderful. Igalia has been around for almost 10 years now, so the system definitely works, and I am very much looking forward to seeing how it works. It's a project, and I'm pleased to be a part.

pottage

My last note of this kind quoted Thoreau, ending with:

If I should sell both my forenoons and afternoons to society, neglecting my peculiar calling, there would be nothing left worth living for. I trust that I shall never thus sell my birthright for a mess of pottage.

Time passes, and it turns out that, among other things, Guile is my "peculiar calling". So the thing that I've worked out with Igalia folk is to go back to full time, though still with a nominal split between JavaScriptCore work and Guile work.

To be honest this split seems to be something akin to therapy. Before, I sold my time for sustenance, and if I could get interesting projects and nice folks on top of that, then great. But now, the prospect of drawing passion and work closer together is daunting at the same time that it's exciting. Hopefully, eventually, this split will heal itself, into a state in which I do what needs doing, while doing what I love. We'll see.

I'll be staying in Barcelona, at least until the fall. Until next time, dear readers, happy hacking. It's good to be back!

Syndicated 2011-04-05 06:16:26 from wingolog

git-brunch(1)

NAME
git-brunch - Fresh fruit before lunch

SYNOPSIS
git brunch [-p]

DESCRIPTION
With no arguments, stashes all work and cherry-picks a snack onto the dining room working tree. Option -p runs a git-prune-snacked(1) afterwards.

NOTES
If running git-brunch does not produce the desired effects, try with sudo. For external brunches, see git-remote.

BUGS
Objects received via git-fetch-snack(1) should be inspected manually to ensure their fitness for brunch. Strawberries, cream, and waffles are currently unimplemented.

SEE ALSO
git-cherry-pick(1), git-snack-objects(1)

Independently discovered a few days ago, it seems.

git-brunch: the power of an idea whose time has come.

Syndicated 2011-03-28 11:24:52 from wingolog

storage primitives for the distributed web

An example-driven discussion of what seems to me to be the minimal set of persistent storage primitives needed in an autonomous web.

Lisa would like to keep a private journal, and store the journal entries in Marge's computer. Lisa is not worried about Marge reading her journal entries, but she wants to make sure that Bart, who also uses the computer, does not have access to her journal. Marge needs to give Lisa some storage primitives so that she can write her journal-entry program.

Marge calls up her operating system vendor, and gets the following procedure documentation in response:

make-cell init-string
Create a new storage location, and store init-string in it. Returns a write-cap, which allows a user to change the string associated with this storage location.
write-cap? obj
Return #t if obj is a write-cap, and #f for any other kind of object.
cell-set! write-cap string
Set the string associated with a cell to a new value.
write-cap->read-cap write-cap
Take a write-cap and return a read-cap, which allows a user to read the value associated with a cell.
read-cap? obj
Return #t if obj is a read-cap, and #f for any other kind of object.
cell-ref read-cap
Return the string associated with the cell.

Marge makes all of these procedures available to Lisa. Lisa then starts to program. She does not want to allow herself to edit her old entries, so she makes a helper:

(define (put-text string)
  (write-cap->read-cap (make-cell string)))

Since put-text throws away the write-cap for this string, nothing else will be able to change an entry, once it is written.

Read-caps and write-caps are capabilities. They are unforgeable. Since Lisa did not give any of these capabilities to Bart, she feels safe typing her innermost thoughts into put-text.

persistent objects

In Scheme, capabilities are objects. A piece of code has capabilities, in the normal English sense of the term, to all of the objects that are in its scope, and to objects that are in the current module.

But since all does not live in the warm world of Scheme, the storage primitives that the computer vendor provides allow read-caps and write-caps to be serialized to strings:

cap->string cap
Return a string representation of the capability cap.
string->cap string
Return a capability corresponding to string. Read capabilities have a different representation from write capabilities, so this procedure may return a read-cap or a write-cap. It returns #f if this string does not look like a capability.

When Lisa started writing, she wrote down the cap-strings for all of her entries in a book. Then when she wants to read them, she types the capability strings into the terminal:

(cell-ref (string->cap "read-cap:a6785kjiyv8c0..."))
=> "I was really happy with my solo today at band, but..."

But this got tiring after a while, so she decided to store the list of capabilities instead:

;; Build a list data type.
;;
(define (make-list-cell)
  (make-mutable-cell ""))

(define (list-cell-ref read-cap)
  (let ((str (cell-ref read-cap)))
    (if (equal? str "")
        '()
        (map string->cap (string-split str #\,)))))

(define (list-cell-set! write-cap caps)
  (cell-set! write-cap
             (string-join (map cap->string caps) ",")))

;; Helper.
;;
(define (->readable cap)
  (if (write-cap? cap)
      (write-cap->read-cap cap)
      cap))

;; Make a new cell, and print out its cap-string.
;; Note to self: write down this string!
;;
(display (cap->string (make-list-cell)))

(define (add-entry! entries cap)
  (list-cell-set!
   entries
   (cons cap (list-cell-ref (->readable *all-entries*)))))

Now she just has to write down the cap-string of the new list cell that she made, and she has a reference to all of her entries. Whenever she writes a new entry, she uses add-entry! to update the cell's value, adding on the new cap-string.

distribution

Colin's father Bono has a computer just like Marge's, and Lisa would like for Colin to be able to be able to read some specific entries. So she asks Marge how to give access to the cells that she is using for data storage to other machines.

Marge asks her vendor, and the vendor says that actually, the cells implementation that was provided to her stores its data in the cloud. So Lisa can just give Colin a cap-string -- read-only, presumably -- for the essays that she would like to share, and all is good.

Marge doesn't know what this means, but she tells Lisa, and Lisa freaks out. "You mean that I've been practicing Careless Computing this whole time, and you didn't tell me??? You mean that Chief Wiggum could have called up the cloud storage provider and gotten access to all of my data?"

careful computing

Lisa's concern is a good one. Marge puts her in contact with the vendor directly, who explains that actually, the cells implementation is designed to preserve users' autonomy.

Creating a cell creates an RSA key-pair. The write-cap contains the signing key (SK) and the public key (PK), and the read-cap contains just the PK. Before sending data to the cloud provider, the data is signed and encrypted using the SK, so only people with access to the read-cap can actually decrypt the strings.

The cells are stored in a standard key-value store. The key is computed as the secure hash (H) of the PK, so that even the cloud storage provider cannot decrypt the data. Furthermore, the cell-ref user does not rely on the provider to vouch for the data's integrity, as she can verify it directly against the PK. The only drawback is that Lisa cannot be sure that cell-ref is returning the latest value of a cell, whatever that means.

The vendor continues by noting that it doesn't actually matter much, from Lisa's perspective, what the key-value store is. It could be Amazon S3-backed web service, a Tahoe-LAFS friendnet, home-grown CouchDB things, or GNUnet. This is an important property in a time in which the peer-to-peer key-value stores have not yet stabilized. The vendor also says that they don't have accounting figured out yet, so they don't know how to charge people for storage, but that they trust that the commercial folks will work that out.

context

These primitives are sufficient to build proper data structures on top of k-v stores -- tries, queues, and such -- all with fine-grained access controls, and without having to trust the store itself. Lisa can, if she ever grows up, publish all (or a set) of her diaries to the world, which could then form part of larger data structures, like the "wall" of whatever comes after Facebook.

It seems to me that this set of primitives is a minimal set. You could add in better support for immutable data, but since you can implement it in terms of mutable data, it seemed unnecessary.

This scheme was mostly inspired by Tahoe-LAFS. You can read a short and very interesting technical paper about it here.

future

Next up would be seeing if these primitives interact well with a capabilities-based security kernel for mobile code. Cross your fingers!

Syndicated 2011-03-24 14:28:17 from wingolog

bart and lisa, hacker edition

Bart and Lisa are both hacking on a computer run by Marge. Bart has a program that sorts a list of integers. Being a generous person, he shares it with Lisa. Lisa would like to use Bart's program, but she doesn't trust Bart -- she wants to make sure that the program is safe to run before running it. She would like to sort a list of credit card numbers and would be quite vexed indeed if Bart's sort procedure posted them all to a web site.

(This example and much of the discussion is taken from the excellent Rees thesis, which I highly, highly recommend.)

One approach she can take is to examine the program, and only run it if it is obviously harmless. Due to fundamental concerns like undecidability, this will be a conservative evaluation. Then if the program is deemed safe, it can be compiled and invoked directly, without having to put it in a sandbox.

The question I wish to discuss this evening is how to do this safe compilation in Guile, in the case in which a Guile program provides environments for Bart and Lisa to both hack on, at the same time, along with means for Bart and Lisa to communicate.

Let us imagine that Bart gives Lisa a program, as a string. The first thing to do is to read it in as a Scheme data structure. Lisa creates a string port, and calls the system's read procedure on the port. This produces a Scheme value.

Lisa trusts that the read procedure and the open-input-string procedures are safe to call. Indeed she has to trust many things from the system; she doesn't really have much of a choice. She has to trust Marge. In this case, barring bugs, the choice is mostly valid. The exception would be reader macros, which cause arbitrary code to run at read-time. Lisa must assume that Bart has not invoked read-hash-extend on her machine. Indeed, Marge cannot supply read-hash-extend to Bart or to Lisa, just as she would not give them access to the low-level foreign function interface.

This brief discussion indicates that there exist in Guile cross-cutting, non-modular facilities which cannot be given to users. If you want to create a secure environment in which programs may only use the capabilities they are provided, "user-level" environments must not include some routines, like read-hash-extend.

names come to have meanings

So, having proceeded on, Lisa reads a Scheme form which she can pretty-print to her console, and it looks like this:

(define sort
  (lambda (l <)
    (define insert
      (lambda (x sorted)
        (if (null? sorted)
            (list x)
            (if (< x (car sorted))
                (cons x sorted)
                (cons (car sorted) (insert x (cdr sorted)))))))
    (if (null? l)
        '()
        (insert (car l) (sort (cdr l) <)))))

How can Lisa know if this program is safe to run?

Actually, this question is something of a cart before the horse; what we should ask is, what does this program mean? To that question, we have the lambda calculus to answer for, as part of the Scheme language definition.

In this form we have binding forms, bound variable references, free variable references, and a few conditionals and constants. The compiler obtains this information during its expansion of the form. Here we are assuming that Lisa compiles the form in an environment with the conventional bindings for define, lambda, and if.

Of these, the constants, conditionals, lexical binding forms, and bound variable references are all safe.

The only thing we need be concerned about are the free variable references (null?, list, car, cdr, cons, and, interestingly, sort itself), and the effect of the toplevel definition (of sort).

In Scheme, forms are either definitions, expressions, or sequences of forms. Definitions bind names to values, and have no value themselves. Expressions can evaluate to any number of values. So the question here for Lisa is: is she interested in a definition of a sort procedure, or a value? If the latter, she must mark this form as unsafe, as it is a definition. If the former, she needs to decide on what it means for Bart to provide a definition to her.

In Guile, top-level definitions and variable references are given meaning by modules. Free variables are scoped relative to the module in which the code appears. If Lisa expands the sort definition in a fresh module, then all of the free variables will be resolved relative to that module -- including the sort reference within the lambda expression.

Lisa can probably give Bart access to all of the primitives that his program calls: car, cons, etc.

We start to see how the solution works, then: a program is safe if all of its free variables are safe. if is safe. null? is safe. And so on. Lisa freely provides these resources to Bart (through his program), because she knows that he can't break them, or use them to break other things. She provides those resources through a fresh module, in which she compiles his program. Once compiled, she can invoke Bart's sort directly, via ((module-ref bart-module 'sort) my-credit-card-numbers), without the need to run Bart's code in any kind of sandbox.

world enough, and time

However, there are two resources which Lisa transfers to Bart's program which are not passed as procedure arguments: time, and space.

What if Bart's program had a bug that made it fail to terminate? What if it simply took too much time? In this case, Marge may provide Lisa with a utility, safe-apply, which calls Bart's function, but which cancels it after some amount of time. Such a procedure is easy for Marge to implement with setitimer. setitimer is one of those cross-cutting concerns however which Marge would not want to provide to either Lisa or Bart directly.

The space question is much more difficult, however. Bart's algorithm might cons a lot of memory, but that's probably OK if it's all garbage. However it's tough for Marge to determine what is Lisa's garbage and what is Bart's garbage, and what non-garbage is Lisa's, and what non-garbage is Bart's, given that they may share objects.

In the Guile case, Marge has even fewer resources at her disposal, as the BDW-GC doesn't give you all that much information. If however she can assume that she can have accurate per-thread byte allocation counters, she can give Lisa the tools needed to abort Bart's program if it allocates too much. Similarly, if Marge restricts her program to only one OS thread, she can guesstimate the active heap size. In that way she can give Lisa tools to restrict Bart's program to a certain amount of active memory.

related

Note that Marge's difficulties are not unique to her: until just a few weeks ago, the Linux kernel was still vulnerable to fork bombs, which are an instance of denial-of-service through resource allocation (processes, in that case).

Nor are Lisa's, for that matter, considering the number of Android applications that are given access to user data, and then proceed to give it to large corporations, and Android does have a security model for apps. Likewise the sort UNIX utility has access to your SSH private key, and it's only the hacker ethic that has kept free software systems in their current remarkably benign state; though, given the weekly Flash Player and Acrobat Reader vulnerabilities, even that is not good enough (for those of us that actually use those programs).

future

I'm going to work on creating a "safe evaluator" that Lisa can use to evaluate expressions from Bart, and later call them. The goal is to investigate the applicability of the ocap security model to the needs of an autonomy- and privacy-preserving distributed computing substrate. In such an environment, Bart should be able to share an "app" (in the current "app store" sense) with all of his friends. His friends could then run the app, but without needing to trust the app maker, Bart, or anyone else. (One precedent would be Racket's sandboxed evaluators; though I don't understand all of their decisions.)

The difficulty of this model seems to me to be more on the side of data storage than of local computation. It's tough to build a distributed queue on top of Tahoe-LAFS, much less GNUnet. Amusingly though, this sort of safe evaluation can be a remedy for that: if an app not only stores data but code, and data storage nodes allow apps to run safe mapreduce / indexing operations there, we may regain more proper data structures, built from the leaves on up. But hey, I get ahead of myself. Until next time, happy hacking.

Syndicated 2011-03-19 21:43:28 from wingolog

/usr/local, fedora, rpath, foo

Good evening intertubes,

I wish to broach a nerdy topic, once more. May my lay readers be rewarded in some heaven for your earthly sufferings. I'll see you all at the bar.

problem statement

For the rest of us, we have a topic, and that topic is about autotools, software installation, -rpath, and all that kind of thing.

As you might be aware, last month we released Guile 2.0.0. Of course we got a number of new interested users. For some of them things went great. It all started like this:

wget http://ftp.gnu.org/gnu/guile/guile-2.0.0.tar.gz
tar zxvf guile-2.0.0.tar.gz
cd guile-2.0.0
./configure
make
sudo make install

Up to here, things are pretty much awesome for everybody. Obviously the next thing is to run it.

$ guile
GNU Guile 2.0.0
Copyright (C) 1995-2011 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> "Hello, World!"
$1 = "Hello, World!"

Sweetness! Let's start going through the manual. (Time passes.) You get to the point of compiling a short program that links to Guile:

gcc -o simple-guile simple-guile.c \
  `guile-config compile` `guile-config link`

And so far so good! But alack:

$ ./simple-guile
./simple-guile: error while loading shared libraries:
    libguile-2.0.so.22: cannot open shared object

Pants! What is the deal?

righteous indignation

The deal is, you appear to be on a Fedora or some other Red Hat-derived system. These systems add /usr/local/bin to the PATH, so the guile-config call succeeds. /usr/local/lib is in the link-time path too, so it finds libguile-2.0.so. But /usr/local/lib is not in the runtime library lookup path by default. Hence the error above.

This, my friends, is a bug. It is a bug in Fedora and similar systems. This recipe works fine on Debian. It works fine with the GNU toolchain, as configured out-of-the-box.

The only reason I can think that Fedora would break this is because of their lib versus lib64 split. It would be strange to say, "32-bit libraries go in /usr/lib, but maybe or maybe not in /usr/local/lib." And in fact the FHS explicitly disclaims what's happening in /usr/local.

But the fact is, /usr/local is the default location for people to compile, and the $PATH and other settings indicate that Fedora folk know this. I can only conjecture that the situation is this way in order to preserve compatibility with legacy 32-bit closed-source binary apps that cannot be recompiled to politely install their libraries elsewhere.

This decision costs me time and energy in the form of bug reports. Thanks but no thanks, whoever made this decision.

solutions?

It's true, this is a specific case of a more general issue. If you installed instead to /opt/guile, you would have to adjust your PATH to be able to run guile-config, or your PKG_CONFIG_PATH if you wanted to use the underlying pkg-config file instead. So you do that:

export PKG_CONFIG_PATH=/opt/guile/lib/pkgconfig
gcc -o simple-guile simple-guile.c `pkg-config --cflags --libs guile-2.0`

Awesome. You run it:

$ ./simple-guile

Hey awesome it works! Actually that's not true. Or rather, it's only true if you're on some other system, like a Mac. If you're on GNU, you get, again:

./simple-guile: error while loading shared libraries:
   libguile-2.0.so.22: cannot open shared object

Willikers! The same badness! And in this case, we can't even blame Fedora, as no one would think to add /opt/guile/lib to the runtime library path. (Well, actually, Apple did; it linked the binary to the link-time location of the library. That has other problems, though.)

The deal is, that we ourselves need to add /opt/guile/lib to the runtime library lookup path. But how we do that is compiler-specific; and in any case it's not necessary if we're installing to somewhere that's already in the system's runtime search path.

So---and this is the point I was coming to, besides ripping on Fedora's defaults---you need a build-time determination of what to add to the compilation line to set the rpath.

That, my friends, is the function of AC_LIB_HAVE_LINKFLAGS, from the excellent gnulib. It takes some link-time flags and spits out something that will also set the run-time library search path.

why haven't I seen this?

Amusingly I had not personally encountered this issue because all of my projects use libtool for linking, which does add -rpath as appropriate. Libtool-induced blissful ignorance: who'd have thought it possible?

Having said my message, I now entreat my more informed readers to leave corrections below in the comments. Thanks!

Syndicated 2011-03-18 23:52:21 from wingolog

ports, weaks, gc, and dark matter

Just wanted to drop a note about some recent bug-hunting. I was experiencing memory leaks in a long-running web server, and, after fixing some port finalization leaks, ran into a weird situation: if I did a (gc) after every server loop, the memory usage (measured by mem_usage.py) stayed constant. But if I just let things run, memory usage grew and grew.

I believe that the problem centered on the weak ports table. Guile maintains a global hash table of all open ports, so that when the process exits, all of the ports can be flushed. Of course we don't want to allow that table to prevent ports from being garbage collected, so it is a "weak" table.

In Guile, hash tables are implemented as small objects that point to a vector of lists of pairs (a vector of alists). For weak hash tables, the pairs have disappearing links: their car or cdr, depending on the kind of table, will be cleared by the garbage collector when the pointed-to object becomes unreachable.

And by "cleared", I mean set to NULL. But the rib stays around, and the vertebra stays in the bucket alist. So later when you traverse a weak hash table, looking for a value, you lazily vacuum out the dead entries.

Dead entries also get vacuumed out when the hash table grows or shrinks such that it should be resized. Guile has an internal table of roughly doubling prime sizes, and when hash table occupancy rises to 90% of the size, or falls below 25%, the hash table is resized up or down. So ideally there are not many collisions.

So, with all of that background information, can you spot the bug?

Well. I certainly couldn't. But here's what I think it was. When you allocate a port, it adds an entry to this weak hash table, allocating at least 4 words and probably more, when you amortize over rehashings. When GC runs and the port is no longer reachable, the port gets collected, and the weak entry nulled out---but the weak entry is still there. Allocation proceeds and your hash table gains in occupancy, vacuuming some slots but, over time, increasing occupancy. Some entries in the hash table might actually be to unreachable ports that haven't been collected yet, for whatever reason.

At some point occupancy increases to the rehashing level. A larger vector is allocated, repopulated with the existing values, and in the process vacuuming ribs and vertebrae for dead references. Overall occupancy is lower but not so much lower as to trigger a rehashing on the low-water-mark side. The process repeats, leading to overall larger memory usge.

You wouldn't think it would be that bad though, would you? Just 32 bytes per port? Well. There are a couple of other things I didn't tell you.

Firstly, port objects are more than meets the eye: the eye of the garbage collector, that is. Besides the memory that they have that GC knows about, they also in general have iconv_t pointers for doing encoding conversions. Those iconv_t values hide kilobytes of allocation, on GNU anyway. This allocation is indeed properly reclaimed on GC---recall that the web server was not leaking when (gc) was run after every request---but it puts pressure on the heap without the GC knowing about it.

See, the GC only runs when it thinks it's necessary. Its idea of "necessary" depends, basically, on how much has been allocated since it last ran. The iconv_t doesn't inform this decision, though; to the GC, it is dark matter. So it is possible for the program to outrun the GC for a while, increasing RSS in the part of the heap the GC doesn't scan or account for. And when it is later freed, you have no guarantees about the fragmentation of those newly-allocated blocks.

I think it was ultimately this, that the GC wouldn't run for a while, we would reach the rehashing condition before GC realized the ports weren't accessible, and the process repeated.

This problem was exacerbated by what might be a bug in Guile. In Scheme, one doesn't typically open and close ports by hand. You use call-with-input-file, or with-output-to-string, or other such higher-order procedures. The reason is that you really want to make sure to delimit the lifetime of, for example, open files from the operating system. So these helper procedures open a port, call a function, close the port, then return the output string or the result from the procedure call or whatever. For the laity, it's like Python's with statement.

In the past, string ports did not have much state associated with them, so it wasn't necessary to actually close the port when leaving, for example, with-output-to-string. But now that Guile does unicode appropriately, all ports might have iconv_t descriptors, so closing the ports is a good idea. Unfortunately it's not a change we can make in the 2.0 series, but it will probably land in 2.2.

Well, what to do? As you can tell by the length of this entry, this problem bothered me for some time. In the end, I do think that open ports are still a problem, in that they can lead to an inappropriately low rate of GC. But it's the interaction with the weaks table---remember Alice?---that's the killer. GC runs, you collect the ports, but memory that was allocated when the ports were allocated (the rib and vertebra) stays around.

The solution there is to fix up the weak hash tables directly, when the GC runs, instead of waiting for lazy fixup that might never come until a rehash. But the Boehm-Demers-Weiser collector that we switched to doesn't give you hooks that are run after GC. So, game over, right?

Heh heh. Hie thee hither, hackety hack; sing through me, muse in a duct-tape dress. What we do is to allocate a word of memory, attach a finalizer, and then revive the object in its finalizer. In that way every time the object is collected, we get a callback. This code is so evil I'm going to paste it here:

static void
weak_gc_callback (void *ptr, void *data)
{
  void **weak = ptr;
  void *val = *weak;
  
  if (val)
    {
      void (*callback) (SCM) = data;

      GC_REGISTER_FINALIZER_NO_ORDER
         (ptr, weak_gc_callback, data, NULL, NULL);
      
      callback (PTR2SCM (val));
    }
}

static void
scm_c_register_weak_gc_callback (SCM obj, void (*callback) (SCM))
{
  void **weak = GC_MALLOC_ATOMIC (sizeof (void**));

  *weak = SCM2PTR (obj);
  GC_GENERAL_REGISTER_DISAPPEARING_LINK (weak, SCM2PTR (obj));

  GC_REGISTER_FINALIZER_NO_ORDER
     (weak, weak_gc_callback, (void*)callback, NULL, NULL);
}

And there you have it. I just do a scm_c_register_weak_gc_callback (table, vacuum_weak_hash_table), and that's that.

This discussion does have practical import for readers of this weblog, in that now it shouldn't die every couple days. It used to be that it would leak and leak, and then stop being able to fork out to git to get the data, leading to a number of interesting error cases, but unfortunately none that actually killed the server. It would accept the connection, go and try to serve it, fail, and loop back. It's the worst of all possible error states, if you were actually trying to read this thing; doubtless some planet readers were relieved, though :)

Now with this hackery, I think things are peachy, but time will tell. Tekuti is still inordinately slow in the non-cached case, so there's some ways to go yet.

Hey I'm talking about garbage collectors, yo! Did I mention the most awesome talk I saw at FOSDEM? No I didn't, did I. Well actually it was Eben Moglen's talk, well-covered by LWN. No slides, just the power of thought and voice. I think if he weren't a lawyer he'd make a great preacher. He speaks with the prophetic voice.

But hey, garbage collectors! The most awesome technical talk I saw at FOSDEM was Cliff Click's. It was supposedly about "Azul's foray into Open Source" [sic], but in reality he gave a brief overview of Azul's custom hardware -- their so-called "Vega" systems -- and then about how they've been working on moving downmarket to X86 machines running the Linux kernel.

Let me back up a bit and re-preach what is so fascinating about their work. Click was one of the authors of Java's HotSpot garbage collector. (I don't know how much of this work was all his, but I'm going to personify for a bit.) He decided that for really big heaps--- hundreds of gigabytes---that what was needed was constant, low-latency, multithreaded GC. Instead of avoiding stop-the-world GC for as long as possible, the algorithm should simply avoid it entirely.

What they have is a constantly-running GC thread that goes over all the memory, page by page, collecting everything all the time. My recollection is a little fuzzy right now---I'm writing in a café without internet---but it goes a little like this. The GC reaches a page (bigpages of course). Let's assume that it knows all active objects, somehow. It maps a new page, copies the live objects there, and unmaps the old page. (The OS is then free to re-use that page.) It continues doing so for all pages in the system, rewriting pointers in the copied objects to point to the new locations.

But the program is still running! What happens if the program accesses an object from one of the later pages that points to an object from an earlier one that was already moved? Here's where things get awesome: the page fault accessing the old page causes the GC to fix up the pointer, right there and then. La-la-la-la-life goes on. Now that I'm back on the tubes, here's a more proper link.

OK. So Azul does this with their custom hardware and custom OS, great. But they want to do this on Linux with standard x86 hardware. What they need from the OS is the ability to quickly remap pages, and to have lower latency from the scheduler. Also there are some points about avoiding TLB cache flushes, and lowering TLB pressure due to tagged pointers. (Did you know that the TLB is keyed on byte locations and not word locations? I did not. They have some kernel code to mask off the bottom few bits.)

They want to get these hooks into the standard kernel so that customers running RHEL can just download their JVM and have it working, and to that end have started what they call the "Managed Runtime Initiative".

Basically what this initiative is is a code dump. The initial patches were totally panned, and when I mentioned this to Click, he said that maybe they should wait for the next "code dump". He actually used that term!

It's really a shame that they are not clueful about interacting with Free Software folk, because their code could help all language runtimes. In particular I wonder: the Boehm collector is a miracle. It's a miracle that it works at all, and furthermore that it works as well as it does. But it can't work optimally because it can't relocate its objects, so it can lead to heap fragmentation. But it seem that with these page-mapping tricks, the Boehm GC could relocate objects. It already knows about all pointers on the system. With some kernel support it could collect in parallel, remapping and compacting the heap as it goes. It's a daunting task, but it sounds possible.

Well. Enough words for one morning. Back to the hack!

Syndicated 2011-02-25 13:59:05 from wingolog

guile 2.0 is out!

Hear ye, hear ye: Guile 2.0 is unleashed upon the world!

Guile is a great implementation of Scheme. It's a joyful programming environment that gives the hacker expressive tools for growing programs.

This release improves Guile tremendously, adding a new compiler and virtual machine, and integrating more powerful hygienic macros in the core of Guile. We managed to pull all of this off with minimal incompatibilities with old code, and maximal awesomeness.

Guile 2.0 is a personal milestone as well, as it is my first stable release as a Guile co-maintainer. I would like to express special thanks to Ludovic Courtès, my co-maintainer, for companionship in the fantastic hack over the last few years. I would also like to express a strange thanks to two people whom I have never met in person, but that, through their code artifacts, really made Guile 2.0 what it is. To Keisuke Nishida, for his initial compiler and VM ten years ago, for teaching me how to write a compiler; and to R. Kent Dybvig, for his psyntax macro expander, for teaching me about macro expanders. Thanks!

Check the release announcement for a short summary of changes, or NEWS for all the delicious details. Start with the manual if you like; it's a great read. Then download and build the thing, and when next you start it up, just think: this could be the beginning of a beautiful hack!

Syndicated 2011-02-16 11:06:21 from wingolog

354 older entries...

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!