[HN Gopher] Compiling a Lisp to x86-64: Let expressions ___________________________________________________________________ Compiling a Lisp to x86-64: Let expressions Author : todsacerdoti Score : 99 points Date : 2020-10-01 16:26 UTC (6 hours ago) (HTM) web link (bernsteinbear.com) (TXT) w3m dump (bernsteinbear.com) | timonoko wrote: | Metoo 1982: C:\nokolisp | (ncompile (macroexpand '(let ((x 1)) (+ x 1)))) | $09CB:$7188: JMP ?? ; see below | $09CB:$718B: CALL $0ECD ; CALL ONEARG | $09CB:$718E: PUSH [$012C] | $09CB:$7192: MOV [$012C],AX | $09CB:$7195: JMP ?? ; see below | $09CB:$7198: PUSH [STACKMARK] | $09CB:$719C: MOV [STACKMARK],SP | $09CB:$71A0: MOV AX,[$012C] | $09CB:$71A3: CALL $0F1D ; CALL NUMVAL | $09CB:$71A6: MOV BX,$01 | $09CB:$71A9: ADD AX,BX | $09CB:$71AB: CALL $05C9 ; CALL MAKNUM | $09CB:$71AE: JMP $2080 | $09CB:$7195: JMP $71B1 | $09CB:$71B1: CALL $7198 | $09CB:$71B4: POP [$012C] | $09CB:$71B8: JMP $1DA7 | $09CB:$7188: JMP $71BB | $09CB:$71BB: MOV AX,$04 ; S-object 1 | $09CB:$71BE: CALL $718E | $09CB:$71C1: JMP $1DA7 (subru: | eval=$7188, compile=$3B6F) | | One pass, but corrects forward jumps afterwards. | tekknolagi wrote: | Thanks for posting! I'm the author and can answer any questions | and receive any constructive criticism. | anaphor wrote: | I don't have any questions, but I just started reading it a few | days ago, and I'm re-implementing it in Nim (which is an | interesting experience). One issue I ran into right away was | that my code from step 1 was segfaulting, and the issue was | that I accidentally called `mprotect` before copying the memory | into the buffer. Easy mistake to make, so you might want to add | a warning for people who aren't just copy-pasting the code. | tekknolagi wrote: | Excellent! I'm so happy you're following along in another | language. If you're developing in the open, can I link to it | somewhere on the series? | | It's a good point and I'll try and remember to add a notice | about order being important later. | anaphor wrote: | Once I'm ready I'm planning on throwing it up on github for | sure. I'll make a note to send you an email with it :) | tekknolagi wrote: | Awesome. Looking forward to it. | emmanueloga_ wrote: | Critique: the TOC is only shown in first post and not clickable, | makes it hard to navigate the series. It would also be nice to | have the TOC repeated in each post. | | Question: I saw a chapter about unary functions. In some | interpreters let bindings are explained as "sugar", or syntactic | extensions on top of function calls. For instance `let` is not | present in the core of scheme [1]. So in this way one could | transform a let binding from: (let ((a 1) (b | 2)) (+ a b)) | | to: ((((lambda (a) (lambda (b) (+ a b))) 1) 2) | | through: (define-syntax let (syntax- | rules () [(_ ((x e) ...) b1 b2 ...) | ((lambda (x ...) b1 b2 ...) e ...)])) | | ... then you wouldn't need to compile let bindings, just function | application, which some people call "desugaring". | | I was wondering if this kind of desugaring is used in practical | lisp compilers or not... without knowing too much about lisp | compilers, I imagine one reason to not go this way is that it | could potentially generate way more code than compiling higher | level concepts like `let` directly. | | 1: https://scheme.com/tspl4/further.html | abecedarius wrote: | Yes, real compilers do handle `((lambda (x) ...) ...)` as well | as a `let`, because as a Lisp programmer you want to be able to | define and use new macros freely, without having to worry too | much about low-level optimizations. Kent Dybvig gave a talk on | this sort of thing called the "macro-writer's Bill of Rights". | It's a similar concept to the recent talk about what | optimizations need to be guaranteed for "zero-cost abstraction" | in Rust and C++. | moonchild wrote: | Most lisp compilers do exactly as you suggest. Generally, lisp | is implemented on a small core of maybe 20~30 functions, with | everything else as macros. If you're curious about how lisps | are implemented, I recommend checking out this series: | https://www.youtube.com/watch?v=Wa81OJnlsoI | Joker_vD wrote: | Well, you've implemented let* : (let* ((a 1) | (b a)) (+ a b)) | | is equivalent to: ((((lambda (a) (lambda (b) | (+ a b))) a) 1) | | And anyway, implementing 'letrec' in C/Asm is really not that | hard: allocate cells, assign names to them, compute the values. | Compare that with the machinery of making closures (flat or | linked?) and function invocation. | | I personally prefer to have 'letrec' as a primitive and | implement non-recursive 'let' on top of it by checking whether | any newly bound names are referenced from the bound values. | lioeters wrote: | Thanks to the author for an enjoyable intellectual journey, it's | a wonderful series of articles. | | Having tried my hand at following the Make a Lisp project 1, | implementing an interpreter in a couple of languages, it's | fascinating to see the process of designing the language and | compiler from the ground up, discussing various tradeoffs for | simplicity, reasons for internal data structure, etc. | | In fact, I think I'm learning more about C99 than Lisp - there's | something about creating a language (or just following along like | me), by necessity it leads to the foundations of | computing/programming, the concepts that make up the basis of all | languages. It's a perfect (meta)subject for an educational | project. | | 1 https://github.com/kanaka/mal | amelius wrote: | I wish these series on compilers would focus more on the harder | parts, like JIT compilation or the virtual machine and concurrent | garbage collecting. | sillysaurusx wrote: | (lets a 1 b 2 (+ a b)) is such a nice macro. Everything but the | last expression are bindings. ___________________________________________________________________ (page generated 2020-10-01 23:01 UTC)