Continuation labels and variable identifiers may be integers
[bpt/guile.git] / module / system / vm / assembler.scm
index 34abc7e..e6d1851 100644 (file)
@@ -1,6 +1,6 @@
-;;; Guile RTL assembler
+;;; Guile bytecode assembler
 
-;;; Copyright (C) 2001, 2009, 2010, 2012, 2013 Free Software Foundation, Inc.
+;;; Copyright (C) 2001, 2009, 2010, 2012, 2013, 2014 Free Software Foundation, Inc.
 ;;;
 ;;; This library is free software; you can redistribute it and/or
 ;;; modify it under the terms of the GNU Lesser General Public
 ;;; Commentary:
 ;;;
 ;;; This module implements an assembler that creates an ELF image from
-;;; RTL assembly and macro-assembly.  The input can be given in
+;;; bytecode assembly and macro-assembly.  The input can be given in
 ;;; s-expression form, like ((OP ARG ...) ...).  Internally there is a
 ;;; procedural interface, the emit-OP procedures, but that is not
 ;;; currently exported.
 ;;;
-;;; "Primitive instructions" correspond to RTL VM operations.
-;;; Assemblers for primitive instructions are generated programmatically
-;;; from (rtl-instruction-list), which itself is derived from the VM
-;;; sources.  There are also "macro-instructions" like "label" or
-;;; "load-constant" that expand to 0 or more primitive instructions.
+;;; "Primitive instructions" correspond to VM operations.  Assemblers
+;;; for primitive instructions are generated programmatically from
+;;; (instruction-list), which itself is derived from the VM sources.
+;;; There are also "macro-instructions" like "label" or "load-constant"
+;;; that expand to 0 or more primitive instructions.
 ;;;
 ;;; The assembler also handles some higher-level tasks, like creating
 ;;; the symbol table, other metadata sections, creating a constant table
 
 (define-module (system vm assembler)
   #:use-module (system base target)
-  #:use-module (system vm instruction)
   #:use-module (system vm dwarf)
   #:use-module (system vm elf)
   #:use-module (system vm linker)
-  #:use-module (system vm objcode)
+  #:use-module (language bytecode)
   #:use-module (rnrs bytevectors)
   #:use-module (ice-9 binary-ports)
   #:use-module (ice-9 vlist)
   #:use-module (srfi srfi-11)
   #:export (make-assembler
             emit-text
-            link-assembly
-            assemble-program))
+            link-assembly))
 
 
 \f
 
-;;; RTL code consists of 32-bit units, often subdivided in some way.
+;;; Bytecode consists of 32-bit units, often subdivided in some way.
 ;;; These helpers create one 32-bit unit from multiple components.
 
-(define-syntax-rule (pack-u8-u24 x y)
+(define-inlinable (pack-u8-u24 x y)
+  (unless (<= 0 x 255)
+    (error "out of range" x))
   (logior x (ash y 8)))
 
-(define-syntax-rule (pack-u8-s24 x y)
+(define-inlinable (pack-u8-s24 x y)
+  (unless (<= 0 x 255)
+    (error "out of range" x))
   (logior x (ash (cond
                   ((< 0 (- y) #x800000)
                    (+ y #x1000000))
                   (else (error "out of range" y)))
                  8)))
 
-(define-syntax-rule (pack-u1-u7-u24 x y z)
+(define-inlinable (pack-u1-u7-u24 x y z)
+  (unless (<= 0 x 1)
+    (error "out of range" x))
+  (unless (<= 0 y 127)
+    (error "out of range" y))
   (logior x (ash y 1) (ash z 8)))
 
-(define-syntax-rule (pack-u8-u12-u12 x y z)
+(define-inlinable (pack-u8-u12-u12 x y z)
+  (unless (<= 0 x 255)
+    (error "out of range" x))
+  (unless (<= 0 y 4095)
+    (error "out of range" y))
   (logior x (ash y 8) (ash z 20)))
 
-(define-syntax-rule (pack-u8-u8-u16 x y z)
+(define-inlinable (pack-u8-u8-u16 x y z)
+  (unless (<= 0 x 255)
+    (error "out of range" x))
+  (unless (<= 0 y 255)
+    (error "out of range" y))
   (logior x (ash y 8) (ash z 16)))
 
-(define-syntax-rule (pack-u8-u8-u8-u8 x y z w)
+(define-inlinable (pack-u8-u8-u8-u8 x y z w)
+  (unless (<= 0 x 255)
+    (error "out of range" x))
+  (unless (<= 0 y 255)
+    (error "out of range" y))
+  (unless (<= 0 z 255)
+    (error "out of range" z))
   (logior x (ash y 8) (ash z 16) (ash w 24)))
 
 (define-syntax pack-flags
 \f
 
 ;;; A <meta> entry collects metadata for one procedure.  Procedures are
-;;; written as contiguous ranges of RTL code.
+;;; written as contiguous ranges of bytecode.
 ;;;
 (define-syntax-rule (assert-match arg pattern kind)
   (let ((x arg))
   (arities meta-arities set-meta-arities!))
 
 (define (make-meta label properties low-pc)
-  (assert-match label (? symbol?) "symbol")
+  (assert-match label (or (? exact-integer?) (? symbol?)) "symbol")
   (assert-match properties (((? symbol?) . _) ...) "alist with symbolic keys")
   (%make-meta label properties low-pc #f '()))
 
 ;;; also maintains ancillary information such as the constant table, a
 ;;; relocation list, and so on.
 ;;;
-;;; RTL code consists of 32-bit units.  We emit RTL code using native
+;;; Bytecode consists of 32-bit units.  We emit bytecode using native
 ;;; endianness.  If we're targeting a foreign endianness, we byte-swap
 ;;; the bytevector as a whole instead of conditionalizing each access.
 ;;;
             word-size endianness
             constants inits
             shstrtab next-section-number
-            meta sources)
+            meta sources
+            dead-slot-maps)
   asm?
 
-  ;; We write RTL code into what is logically a growable vector,
+  ;; We write bytecode into what is logically a growable vector,
   ;; implemented as a list of blocks.  asm-cur is the current block, and
   ;; asm-idx is the current index into that block, in 32-bit units.
   ;;
   ;; beginning of an instruction (in u32 units).  It is updated after
   ;; writing all the words for one primitive instruction.  It models the
   ;; position of the instruction pointer during execution, given that
-  ;; the RTL VM updates the IP only at the end of executing the
-  ;; instruction, and is thus useful for computing offsets between two
-  ;; points in a program.
+  ;; the VM updates the IP only at the end of executing the instruction,
+  ;; and is thus useful for computing offsets between two points in a
+  ;; program.
   ;;
   (start asm-start set-asm-start!)
 
   ;;
   (constants asm-constants set-asm-constants!)
 
-  ;; A list of RTL instructions needed to initialize the constants.
-  ;; Will run in a thunk with 2 local variables.
+  ;; A list of instructions needed to initialize the constants.  Will
+  ;; run in a thunk with 2 local variables.
   ;;
   (inits asm-inits set-asm-inits!)
 
   ;; is relative to the beginning of the text section, and SOURCE is in
   ;; the same format that source-properties returns.
   ;;
-  (sources asm-sources set-asm-sources!))
+  (sources asm-sources set-asm-sources!)
+
+  ;; A list of (pos . dead-slot-map) pairs, indicating dead slot maps.
+  ;; POS is relative to the beginning of the text section.
+  ;; DEAD-SLOT-MAP is a bitfield of slots that are dead at call sites,
+  ;; as an integer.
+  ;;
+  (dead-slot-maps asm-dead-slot-maps set-asm-dead-slot-maps!))
 
 (define-inlinable (fresh-block)
   (make-u32vector *block-size*))
 @var{endianness}, falling back to appropriate values for the configured
 target."
   (make-asm (fresh-block) 0 0 '() 0
-            '() '()
+            (make-hash-table) '()
             word-size endianness
             vlist-null '()
             (make-string-table) 1
-            '() '()))
+            '() '() '()))
 
 (define (intern-section-name! asm string)
   "Add a string to the section name table (shstrtab)."
@@ -301,11 +329,6 @@ reference that needs to be fixed up by the linker."
   "Reset the asm-start after writing the words for one instruction."
   (set-asm-start! asm (asm-pos asm)))
 
-(define (emit-exported-label asm label)
-  "Define a linker symbol associating @var{label} with the current
-asm-start."
-  (set-asm-labels! asm (acons label (asm-start asm) (asm-labels asm))))
-
 (define (record-label-reference asm label)
   "Record an x8-s24 local label reference.  This value will get patched
 up later by the assembler."
@@ -327,7 +350,7 @@ later by the linker."
 
 ;;;
 ;;; Primitive assemblers are defined by expanding `assembler' for each
-;;; opcode in `(rtl-instruction-list)'.
+;;; opcode in `(instruction-list)'.
 ;;;
 
 (eval-when (expand compile load eval)
@@ -461,7 +484,7 @@ later by the linker."
       ((visit-opcodes macro arg ...)
        (with-syntax (((inst ...)
                       (map (lambda (x) (datum->syntax #'macro x))
-                           (rtl-instruction-list))))
+                           (instruction-list))))
          #'(begin
              (macro arg ... . inst)
              ...))))))
@@ -470,8 +493,8 @@ later by the linker."
 
 (define (emit-text asm instructions)
   "Assemble @var{instructions} using the assembler @var{asm}.
-@var{instructions} is a sequence of RTL instructions, expressed as a
-list of lists.  This procedure can be called many times before calling
+@var{instructions} is a sequence of instructions, expressed as a list of
+lists.  This procedure can be called many times before calling
 @code{link-assembly}."
   (for-each (lambda (inst)
               (apply (or (hashq-ref assemblers (car inst))
@@ -508,17 +531,32 @@ list of lists.  This procedure can be called many times before calling
   static-procedure?
   (code static-procedure-code))
 
+(define-record-type <uniform-vector-backing-store>
+  (make-uniform-vector-backing-store bytes element-size)
+  uniform-vector-backing-store?
+  (bytes uniform-vector-backing-store-bytes)
+  (element-size uniform-vector-backing-store-element-size))
+
 (define-record-type <cache-cell>
   (make-cache-cell scope key)
   cache-cell?
   (scope cache-cell-scope)
   (key cache-cell-key))
 
+(define (simple-vector? obj)
+  (and (vector? obj)
+       (equal? (array-shape obj) (list (list 0 (1- (vector-length obj)))))))
+
+(define (simple-uniform-vector? obj)
+  (and (array? obj)
+       (symbol? (array-type obj))
+       (equal? (array-shape obj) (list (list 0 (1- (array-length obj)))))))
+
 (define (statically-allocatable? x)
   "Return @code{#t} if a non-immediate constant can be allocated
 statically, and @code{#f} if it would need some kind of runtime
 allocation."
-  (or (pair? x) (vector? x) (string? x) (stringbuf? x) (static-procedure? x)))
+  (or (pair? x) (string? x) (stringbuf? x) (static-procedure? x) (array? x)))
 
 (define (intern-constant asm obj)
   "Add an object to the constant table, and return a label that can be
@@ -529,17 +567,17 @@ table, its existing label is used directly."
   (define (field dst n obj)
     (let ((src (recur obj)))
       (if src
-          (list (if (statically-allocatable? obj)
-                    `(make-non-immediate 1 ,src)
-                    `(static-ref 1 ,src))
-                `(static-set! 1 ,dst ,n))
+          (if (statically-allocatable? obj)
+              `((static-patch! ,dst ,n ,src))
+              `((static-ref 1 ,src)
+                (static-set! 1 ,dst ,n)))
           '())))
   (define (intern obj label)
     (cond
      ((pair? obj)
       (append (field label 0 (car obj))
               (field label 1 (cdr obj))))
-     ((vector? obj)
+     ((simple-vector? obj)
       (let lp ((i 0) (inits '()))
         (if (< i (vector-length obj))
             (lp (1+ i)
@@ -548,16 +586,14 @@ table, its existing label is used directly."
             (reverse inits))))
      ((stringbuf? obj) '())
      ((static-procedure? obj)
-      `((make-non-immediate 1 ,label)
-        (link-procedure! 1 ,(static-procedure-code obj))))
+      `((static-patch! ,label 1 ,(static-procedure-code obj))))
      ((cache-cell? obj) '())
      ((symbol? obj)
       `((make-non-immediate 1 ,(recur (symbol->string obj)))
         (string->symbol 1 1)
         (static-set! 1 ,label 0)))
      ((string? obj)
-      `((make-non-immediate 1 ,(recur (make-stringbuf obj)))
-        (static-set! 1 ,label 1)))
+      `((static-patch! ,label 1 ,(recur (make-stringbuf obj)))))
      ((keyword? obj)
       `((static-ref 1 ,(recur (keyword->symbol obj)))
         (symbol->keyword 1 1)
@@ -566,6 +602,22 @@ table, its existing label is used directly."
       `((make-non-immediate 1 ,(recur (number->string obj)))
         (string->number 1 1)
         (static-set! 1 ,label 0)))
+     ((uniform-vector-backing-store? obj) '())
+     ((simple-uniform-vector? obj)
+      (let ((width (case (array-type obj)
+                     ((vu8 u8 s8) 1)
+                     ((u16 s16) 2)
+                     ;; Bitvectors are addressed in 32-bit units.
+                     ;; Although a complex number is 8 or 16 bytes wide,
+                     ;; it should be byteswapped in 4 or 8 byte units.
+                     ((u32 s32 f32 c32 b) 4)
+                     ((u64 s64 f64 c64) 8)
+                     (else
+                      (error "unhandled array type" obj)))))
+        `((static-patch! ,label 2
+                         ,(recur (make-uniform-vector-backing-store
+                                  (uniform-array->bytevector obj)
+                                  width))))))
      (else
       (error "don't know how to intern" obj))))
   (cond
@@ -638,6 +690,41 @@ returned instead."
   (let ((loc (intern-constant asm (make-static-procedure label))))
     (emit-make-non-immediate asm dst loc)))
 
+(define-syntax-rule (define-tc7-macro-assembler name tc7)
+  (define-macro-assembler (name asm slot invert? label)
+    (emit-br-if-tc7 asm slot invert? tc7 label)))
+
+;; Keep in sync with tags.h.  Part of Guile's ABI.  Currently unused
+;; macro assemblers are commented out.  See also
+;; *branching-primcall-arities* in (language cps primitives), the set of
+;; macro-instructions in assembly.scm, and
+;; disassembler.scm:code-annotation.
+;;
+;; FIXME: Define all tc7 values in Scheme in one place, derived from
+;; tags.h.
+(define-tc7-macro-assembler br-if-symbol 5)
+(define-tc7-macro-assembler br-if-variable 7)
+(define-tc7-macro-assembler br-if-vector 13)
+;(define-tc7-macro-assembler br-if-weak-vector 13)
+(define-tc7-macro-assembler br-if-string 21)
+;(define-tc7-macro-assembler br-if-heap-number 23)
+;(define-tc7-macro-assembler br-if-stringbuf 39)
+(define-tc7-macro-assembler br-if-bytevector 77)
+;(define-tc7-macro-assembler br-if-pointer 31)
+;(define-tc7-macro-assembler br-if-hashtable 29)
+;(define-tc7-macro-assembler br-if-fluid 37)
+;(define-tc7-macro-assembler br-if-dynamic-state 45)
+;(define-tc7-macro-assembler br-if-frame 47)
+;(define-tc7-macro-assembler br-if-vm 55)
+;(define-tc7-macro-assembler br-if-vm-cont 71)
+;(define-tc7-macro-assembler br-if-rtl-program 69)
+;(define-tc7-macro-assembler br-if-weak-set 85)
+;(define-tc7-macro-assembler br-if-weak-table 87)
+;(define-tc7-macro-assembler br-if-array 93)
+(define-tc7-macro-assembler br-if-bitvector 95)
+;(define-tc7-macro-assembler br-if-port 125)
+;(define-tc7-macro-assembler br-if-smob 127)
+
 (define-macro-assembler (begin-program asm label properties)
   (emit-label asm label)
   (let ((meta (make-meta label properties (asm-start asm))))
@@ -659,11 +746,11 @@ returned instead."
   (assert-match req ((? symbol?) ...) "list of symbols")
   (assert-match opt ((? symbol?) ...) "list of symbols")
   (assert-match rest (or #f (? symbol?)) "#f or symbol")
-  (assert-match kw-indices (((? symbol?) . (? integer?)) ...)
-                "alist of symbol -> integer")
+  (assert-match kw-indices (((? keyword?) . (? integer?)) ...)
+                "alist of keyword -> integer")
   (assert-match allow-other-keys? (? boolean?) "boolean")
   (assert-match nlocals (? integer?) "integer")
-  (assert-match alternate (or #f (? symbol?)) "#f or symbol")
+  (assert-match alternate (or #f (? exact-integer?) (? symbol?)) "#f or symbol")
   (let* ((meta (car (asm-meta asm)))
          (arity (make-arity req opt rest kw-indices allow-other-keys?
                             (asm-start asm) #f))
@@ -714,7 +801,10 @@ returned instead."
 (define-macro-assembler (kw-prelude asm nreq nopt rest? kw-indices
                                     allow-other-keys? nlocals alternate)
   (if alternate
-      (emit-br-if-nargs-lt asm nreq alternate)
+      (begin
+        (emit-br-if-nargs-lt asm nreq alternate)
+        (unless rest?
+          (emit-br-if-npos-gt asm nreq (+ nreq nopt) alternate)))
       (emit-assert-nargs-ge asm nreq))
   (let ((ntotal (fold (lambda (kw ntotal)
                         (match kw
@@ -726,11 +816,11 @@ returned instead."
                       (pack-flags allow-other-keys? rest?)
                       (+ nreq nopt)
                       ntotal
-                      kw-indices)
+                      (intern-constant asm kw-indices))
     (emit-alloc-frame asm nlocals)))
 
 (define-macro-assembler (label asm sym)
-  (set-asm-labels! asm (acons sym (asm-start asm) (asm-labels asm))))
+  (hashq-set! (asm-labels asm) sym (asm-start asm)))
 
 (define-macro-assembler (source asm source)
   (set-asm-sources! asm (acons (asm-start asm) source (asm-sources asm))))
@@ -752,6 +842,12 @@ returned instead."
          (cell-label (intern-cache-cell asm key sym)))
     (emit-module-box asm dst cell-label mod-name-label sym-label bound?)))
 
+(define-macro-assembler (dead-slot-map asm proc-slot dead-slot-map)
+  (unless (zero? dead-slot-map)
+    (set-asm-dead-slot-maps! asm
+                             (cons
+                              (cons* (asm-start asm) proc-slot dead-slot-map)
+                              (asm-dead-slot-maps asm)))))
 
 \f
 
@@ -824,7 +920,9 @@ should be .data or .rodata), and return the resulting linker object.
   (define tc7-wide-stringbuf
     (+ tc7-stringbuf stringbuf-shared-flag stringbuf-wide-flag))
   (define tc7-ro-string (+ 21 #x200))
-  (define tc7-rtl-program 69)
+  (define tc7-program 69)
+  (define tc7-bytevector 77)
+  (define tc7-bitvector 95)
 
   (let ((word-size (asm-word-size asm))
         (endianness (asm-endianness asm)))
@@ -843,8 +941,12 @@ should be .data or .rodata), and return the resulting linker object.
         (* 4 word-size))
        ((pair? x)
         (* 2 word-size))
-       ((vector? x)
+       ((simple-vector? x)
         (* (1+ (vector-length x)) word-size))
+       ((simple-uniform-vector? x)
+        (* 4 word-size))
+       ((uniform-vector-backing-store? x)
+        (bytevector-length (uniform-vector-backing-store-bytes x)))
        (else
         word-size)))
 
@@ -890,10 +992,10 @@ should be .data or .rodata), and return the resulting linker object.
        ((static-procedure? obj)
         (case word-size
           ((4)
-           (bytevector-u32-set! buf pos tc7-rtl-program endianness)
+           (bytevector-u32-set! buf pos tc7-program endianness)
            (bytevector-u32-set! buf (+ pos 4) 0 endianness))
           ((8)
-           (bytevector-u64-set! buf pos tc7-rtl-program endianness)
+           (bytevector-u64-set! buf pos tc7-program endianness)
            (bytevector-u64-set! buf (+ pos 8) 0 endianness))
           (else (error "bad word size"))))
 
@@ -919,7 +1021,7 @@ should be .data or .rodata), and return the resulting linker object.
         (write-constant-reference buf pos (car obj))
         (write-constant-reference buf (+ pos word-size) (cdr obj)))
 
-       ((vector? obj)
+       ((simple-vector? obj)
         (let* ((len (vector-length obj))
                (tag (logior tc7-vector (ash len 8))))
           (case word-size
@@ -942,6 +1044,40 @@ should be .data or .rodata), and return the resulting linker object.
        ((number? obj)
         (write-immediate asm buf pos #f))
 
+       ((simple-uniform-vector? obj)
+        (let ((tag (if (bitvector? obj)
+                       tc7-bitvector
+                       (let ((type-code (array-type-code obj)))
+                         (logior tc7-bytevector (ash type-code 7))))))
+          (case word-size
+            ((4)
+             (bytevector-u32-set! buf pos tag endianness)
+             (bytevector-u32-set! buf (+ pos 4)
+                                  (if (bitvector? obj)
+                                      (bitvector-length obj)
+                                      (bytevector-length obj))
+                                  endianness)                 ; length
+             (bytevector-u32-set! buf (+ pos 8) 0 endianness) ; pointer
+             (write-immediate asm buf (+ pos 12) #f))         ; owner
+            ((8)
+             (bytevector-u64-set! buf pos tag endianness)
+             (bytevector-u64-set! buf (+ pos 8)
+                                  (if (bitvector? obj)
+                                      (bitvector-length obj)
+                                      (bytevector-length obj))
+                                  endianness)                  ; length
+             (bytevector-u64-set! buf (+ pos 16) 0 endianness) ; pointer
+             (write-immediate asm buf (+ pos 24) #f))          ; owner
+            (else (error "bad word size")))))
+
+       ((uniform-vector-backing-store? obj)
+        (let ((bv (uniform-vector-backing-store-bytes obj)))
+          (bytevector-copy! bv 0 buf pos (bytevector-length bv))
+          (unless (or (= 1 (uniform-vector-backing-store-element-size obj))
+                      (eq? endianness (native-endianness)))
+            ;; Need to swap units of element-size bytes
+            (error "FIXME: Implement byte order swap"))))
+
        (else
         (error "unrecognized object" obj))))
 
@@ -952,7 +1088,7 @@ should be .data or .rodata), and return the resulting linker object.
                                      (+ (byte-length k) (align len 8)))
                                    0 data))
              (buf (make-bytevector byte-len 0)))
-        (let lp ((i 0) (pos 0) (labels '()))
+        (let lp ((i 0) (pos 0) (symbols '()))
           (if (< i (vlist-length data))
               (let* ((pair (vlist-ref data i))
                      (obj (car pair))
@@ -960,8 +1096,8 @@ should be .data or .rodata), and return the resulting linker object.
                 (write buf pos obj)
                 (lp (1+ i)
                     (align (+ (byte-length obj) pos) 8)
-                    (cons (make-linker-symbol obj-label pos) labels)))
-              (make-object asm name buf '() labels
+                    (cons (make-linker-symbol obj-label pos) symbols)))
+              (make-object asm name buf '() symbols
                            #:flags (match name
                                      ('.data (logior SHF_ALLOC SHF_WRITE))
                                      ('.rodata SHF_ALLOC))))))))))
@@ -978,11 +1114,12 @@ these may be @code{#f}."
      ((stringbuf? x) #t)
      ((pair? x)
       (and (immediate? (car x)) (immediate? (cdr x))))
-     ((vector? x)
+     ((simple-vector? x)
       (let lp ((i 0))
         (or (= i (vector-length x))
             (and (immediate? (vector-ref x i))
                  (lp (1+ i))))))
+     ((uniform-vector-backing-store? x) #t)
      (else #f)))
   (let* ((constants (asm-constants asm))
          (len (vlist-length constants)))
@@ -1012,7 +1149,7 @@ relocations for references to symbols defined outside the text section."
    (lambda (reloc tail)
      (match reloc
        ((type label base word)
-        (let ((abs (assq-ref labels label))
+        (let ((abs (hashq-ref labels label))
               (dst (+ base word)))
           (case type
             ((s32)
@@ -1034,11 +1171,11 @@ relocations for references to symbols defined outside the text section."
    relocs))
 
 (define (process-labels labels)
-  "Define linker symbols for the label-offset pairs in @var{labels}.
+  "Define linker symbols for the label-offset map in @var{labels}.
 The offsets are expected to be expressed in words."
-  (map (lambda (pair)
-         (make-linker-symbol (car pair) (* (cdr pair) 4)))
-       labels))
+  (hash-map->list (lambda (label loc)
+                    (make-linker-symbol label (* loc 4)))
+                  labels))
 
 (define (swap-bytes! buf)
   "Patch up the text buffer @var{buf}, swapping the endianness of each
@@ -1076,19 +1213,88 @@ needed."
 
 \f
 
+;;;
+;;; Create the frame maps.  These maps are used by GC to identify dead
+;;; slots in pending call frames, to avoid marking them.  We only do
+;;; this when frame makes a non-tail call, as that is the common case.
+;;; Only the topmost frame will see a GC at any other point, but we mark
+;;; top frames conservatively as serializing live slot maps at every
+;;; instruction would take up too much space in the object file.
+;;;
+
+;; The .guile.frame-maps section starts with two packed u32 values: one
+;; indicating the offset of the first byte of the .rtl-text section, and
+;; another indicating the relative offset in bytes of the slots data.
+(define frame-maps-prefix-len 8)
+
+;; Each header is 8 bytes: 4 for the offset from .rtl_text, and 4 for
+;; the offset of the slot map from the beginning of the
+;; .guile.frame-maps section.  The length of a frame map depends on the
+;; frame size at the call site, and is not encoded into this section as
+;; it is available at run-time.
+(define frame-map-header-len 8)
+
+(define (link-frame-maps asm)
+  (define (map-byte-length proc-slot)
+    (ceiling-quotient (- proc-slot 2) 8))
+  (define (make-frame-maps maps count map-len)
+    (let* ((endianness (asm-endianness asm))
+           (header-pos frame-maps-prefix-len)
+           (map-pos (+ header-pos (* count frame-map-header-len)))
+           (bv (make-bytevector (+ map-pos map-len) 0)))
+      (bytevector-u32-set! bv 4 map-pos endianness)
+      (let lp ((maps maps) (header-pos header-pos) (map-pos map-pos))
+        (match maps
+          (()
+           (make-object asm '.guile.frame-maps bv
+                        (list (make-linker-reloc 'abs32/1 0 0 '.rtl-text))
+                        '() #:type SHT_PROGBITS #:flags SHF_ALLOC))
+          (((pos proc-slot . map) . maps)
+           (bytevector-u32-set! bv header-pos (* pos 4) endianness)
+           (bytevector-u32-set! bv (+ header-pos 4) map-pos endianness)
+           (let write-bytes ((map-pos map-pos)
+                             (map map)
+                             (byte-length (map-byte-length proc-slot)))
+             (if (zero? byte-length)
+                 (lp maps (+ header-pos frame-map-header-len) map-pos)
+                 (begin
+                   (bytevector-u8-set! bv map-pos (logand map #xff))
+                   (write-bytes (1+ map-pos) (ash map -8)
+                                (1- byte-length))))))))))
+  (match (asm-dead-slot-maps asm)
+    (() #f)
+    (in
+     (let lp ((in in) (out '()) (count 0) (map-len 0))
+       (match in
+         (() (make-frame-maps out count map-len))
+         (((and head (pos proc-slot . map)) . in)
+          (lp in (cons head out)
+              (1+ count)
+              (+ (map-byte-length proc-slot) map-len))))))))
+
+\f
+
 ;;;
 ;;; Linking other sections of the ELF file, like the dynamic segment,
 ;;; the symbol table, etc.
 ;;;
 
-(define (link-dynamic-section asm text rw rw-init)
-  "Link the dynamic section for an ELF image with RTL text, given the
-writable data section @var{rw} needing fixup from the procedure with
-label @var{rw-init}.  @var{rw-init} may be false.  If @var{rw} is true,
-it will be added to the GC roots at runtime."
+;; FIXME: Define these somewhere central, shared with C.
+(define *bytecode-major-version* #x0202)
+(define *bytecode-minor-version* 4)
+
+(define (link-dynamic-section asm text rw rw-init frame-maps)
+  "Link the dynamic section for an ELF image with bytecode @var{text},
+given the writable data section @var{rw} needing fixup from the
+procedure with label @var{rw-init}.  @var{rw-init} may be false.  If
+@var{rw} is true, it will be added to the GC roots at runtime."
   (define-syntax-rule (emit-dynamic-section word-size %set-uword! reloc-type)
     (let* ((endianness (asm-endianness asm))
-           (bv (make-bytevector (* word-size (if rw (if rw-init 12 10) 6)) 0))
+           (words 6)
+           (words (if rw (+ words 4) words))
+           (words (if rw-init (+ words 2) words))
+           (words (if frame-maps (+ words 2) words))
+           (bv (make-bytevector (* word-size words) 0))
            (set-uword!
             (lambda (i uword)
               (%set-uword! bv (* i word-size) uword endianness)))
@@ -1099,29 +1305,25 @@ it will be added to the GC roots at runtime."
                                                     (* i word-size) 0 label)
                                  relocs))
               (%set-uword! bv (* i word-size) 0 endianness))))
-      (set-uword! 0 DT_GUILE_RTL_VERSION)
-      (set-uword! 1 #x02020000)
+      (set-uword! 0 DT_GUILE_VM_VERSION)
+      (set-uword! 1 (logior (ash *bytecode-major-version* 16)
+                            *bytecode-minor-version*))
       (set-uword! 2 DT_GUILE_ENTRY)
       (set-label! 3 '.rtl-text)
-      (cond
-       (rw
+      (when rw
         ;; Add roots to GC.
         (set-uword! 4 DT_GUILE_GC_ROOT)
         (set-label! 5 '.data)
         (set-uword! 6 DT_GUILE_GC_ROOT_SZ)
         (set-uword! 7 (bytevector-length (linker-object-bv rw)))
-        (cond
-         (rw-init
+        (when rw-init
           (set-uword! 8 DT_INIT)        ; constants
-          (set-label! 9 rw-init)
-          (set-uword! 10 DT_NULL)
-          (set-uword! 11 0))
-         (else
-          (set-uword! 8 DT_NULL)
-          (set-uword! 9 0))))
-       (else
-        (set-uword! 4 DT_NULL)
-        (set-uword! 5 0)))
+          (set-label! 9 rw-init)))
+      (when frame-maps
+        (set-uword! (- words 4) DT_GUILE_FRAME_MAPS)
+        (set-label! (- words 3) '.guile.frame-maps))
+      (set-uword! (- words 2) DT_NULL)
+      (set-uword! (- words 1) 0)
       (make-object asm '.dynamic bv relocs '()
                    #:type SHT_DYNAMIC #:flags SHF_ALLOC)))
   (case (asm-word-size asm)
@@ -1194,8 +1396,8 @@ it will be added to the GC roots at runtime."
 ;;;
 ;;; All of the offsets and addresses are 32 bits.  We can expand in the
 ;;; future to use 64-bit offsets if appropriate, but there are other
-;;; aspects of RTL that constrain us to a total image that fits in 32
-;;; bits, so for the moment we'll simplify the problem space.
+;;; aspects of bytecode that constrain us to a total image that fits in
+;;; 32 bits, so for the moment we'll simplify the problem space.
 ;;;
 ;;; The following flags values are defined:
 ;;;
@@ -1203,6 +1405,7 @@ it will be added to the GC roots at runtime."
 ;;;    #x2: allow-other-keys?
 ;;;    #x4: has-keyword-args?
 ;;;    #x8: is-case-lambda?
+;;;    #x10: is-in-case-lambda?
 ;;;
 ;;; Functions with a single arity specify their number of required and
 ;;; optional arguments in nreq and nopt, and do not have the
@@ -1222,10 +1425,10 @@ it will be added to the GC roots at runtime."
 ;;; Functions with multiple arities are preceded by a header with
 ;;; is-case-lambda? set.  All other fields are 0, except low-pc and
 ;;; high-pc which should be the bounds of the whole function.  Headers
-;;; for the individual arities follow.  In this way the whole headers
-;;; array is sorted in increasing low-pc order, and case-lambda clauses
-;;; are contained within the [low-pc, high-pc] of the case-lambda
-;;; header.
+;;; for the individual arities follow, with the is-in-case-lambda? flag
+;;; set.  In this way the whole headers array is sorted in increasing
+;;; low-pc order, and case-lambda clauses are contained within the
+;;; [low-pc, high-pc] of the case-lambda header.
 
 ;; Length of the prefix to the arities section, in bytes.
 (define arities-prefix-len 4)
@@ -1237,11 +1440,13 @@ it will be added to the GC roots at runtime."
 (define arity-header-offset-offset (* 2 4))
 
 (define-syntax-rule (pack-arity-flags has-rest? allow-other-keys?
-                                      has-keyword-args? is-case-lambda?)
+                                      has-keyword-args? is-case-lambda?
+                                      is-in-case-lambda?)
   (logior (if has-rest? (ash 1 0) 0)
           (if allow-other-keys? (ash 1 1) 0)
           (if has-keyword-args? (ash 1 2) 0)
-          (if is-case-lambda? (ash 1 3) 0)))
+          (if is-case-lambda? (ash 1 3) 0)
+          (if is-in-case-lambda? (ash 1 4) 0)))
 
 (define (meta-arities-size meta)
   (define (lambda-size arity)
@@ -1262,19 +1467,20 @@ it will be added to the GC roots at runtime."
 
 (define (write-arity-headers metas bv endianness)
   (define (write-arity-header* pos low-pc high-pc flags nreq nopt)
-    (bytevector-u32-set! bv pos low-pc endianness)
-    (bytevector-u32-set! bv (+ pos 4) high-pc endianness)
+    (bytevector-u32-set! bv pos (* low-pc 4) endianness)
+    (bytevector-u32-set! bv (+ pos 4) (* high-pc 4) endianness)
     (bytevector-u32-set! bv (+ pos 8) 0 endianness) ; offset
     (bytevector-u32-set! bv (+ pos 12) flags endianness)
     (bytevector-u32-set! bv (+ pos 16) nreq endianness)
     (bytevector-u32-set! bv (+ pos 20) nopt endianness))
-  (define (write-arity-header pos arity)
+  (define (write-arity-header pos arity in-case-lambda?)
     (write-arity-header* pos (arity-low-pc arity)
                          (arity-high-pc arity)
                          (pack-arity-flags (arity-rest arity)
                                            (arity-allow-other-keys? arity)
                                            (pair? (arity-kw-indices arity))
-                                           #f)
+                                           #f
+                                           in-case-lambda?)
                          (length (arity-req arity))
                          (length (arity-opt arity))))
   (let lp ((metas metas) (pos arities-prefix-len) (offsets '()))
@@ -1287,7 +1493,7 @@ it will be added to the GC roots at runtime."
        (match (meta-arities meta)
          (() (lp metas pos offsets))
          ((arity)
-          (write-arity-header pos arity)
+          (write-arity-header pos arity #f)
           (lp metas
               (+ pos arity-header-len)
               (acons arity (+ pos arity-header-offset-offset) offsets)))
@@ -1295,13 +1501,13 @@ it will be added to the GC roots at runtime."
           ;; Write a case-lambda header, then individual arities.
           ;; The case-lambda header's offset link is 0.
           (write-arity-header* pos (meta-low-pc meta) (meta-high-pc meta)
-                               (pack-arity-flags #f #f #f #t) 0 0)
+                               (pack-arity-flags #f #f #f #t #f) 0 0)
           (let lp* ((arities arities) (pos (+ pos arity-header-len))
                     (offsets offsets))
             (match arities
               (() (lp metas pos offsets))
               ((arity . arities)
-               (write-arity-header pos arity)
+               (write-arity-header pos arity #t)
                (lp* arities
                     (+ pos arity-header-len)
                     (acons arity
@@ -1372,9 +1578,9 @@ it will be added to the GC roots at runtime."
 ;;; The .guile.docstrs section is a packed, sorted array of (pc, str)
 ;;; values.  Pc and str are both 32 bits wide.  (Either could change to
 ;;; 64 bits if appropriate in the future.)  Pc is the address of the
-;;; entry to a program, relative to the start of the text section, and
-;;; str is an index into the associated .guile.docstrs.strtab string
-;;; table section.
+;;; entry to a program, relative to the start of the text section, in
+;;; bytes, and str is an index into the associated .guile.docstrs.strtab
+;;; string table section.
 ;;;
 
 ;; The size of a docstrs entry, in bytes.
@@ -1390,7 +1596,7 @@ it will be added to the GC roots at runtime."
                     (and tail
                          (not (find-tail is-documentation? (cdr tail)))
                          (string? (cdar tail))
-                         (cons (meta-low-pc meta) (cdar tail)))))
+                         (cons (* 4 (meta-low-pc meta)) (cdar tail)))))
                 (reverse (asm-meta asm))))
   (let* ((endianness (asm-endianness asm))
          (docstrings (find-docstrings))
@@ -1456,7 +1662,7 @@ it will be added to the GC roots at runtime."
     (filter-map (lambda (meta)
                   (let ((props (props-without-name-or-docstring meta)))
                     (and (pair? props)
-                         (cons (meta-low-pc meta) props))))
+                         (cons (* 4 (meta-low-pc meta)) props))))
                 (reverse (asm-meta asm))))
   (let* ((endianness (asm-endianness asm))
          (procprops (find-procprops))
@@ -1520,7 +1726,7 @@ it will be added to the GC roots at runtime."
 
   (define (put-sleb128 port val)
     (let lp ((val val))
-      (if (<= 0 (+ val 64) 128)
+      (if (<= 0 (+ val 64) 127)
           (put-u8 port (logand val #x7f))
           (begin
             (put-u8 port (logior #x80 (logand val #x7f)))
@@ -1643,15 +1849,18 @@ it will be added to the GC roots at runtime."
            ;; uleb128 for each of directory the file was found in, the
            ;; modification time, and the file's size in bytes.  We pass
            ;; zero for the latter three fields.
-           (vlist-for-each (match-lambda
-                            ((file . code)
-                             (put-bytevector line-port (string->utf8 file))
-                             (put-u8 line-port 0)
-                             (put-uleb128 line-port 0) ; directory
-                             (put-uleb128 line-port 0) ; mtime
-                             (put-uleb128 line-port 0) ; size
-                             ))
-                           files)
+           (vlist-fold-right
+            (lambda (pair seed)
+              (match pair
+                ((file . code)
+                 (put-bytevector line-port (string->utf8 file))
+                 (put-u8 line-port 0)
+                 (put-uleb128 line-port 0) ; directory
+                 (put-uleb128 line-port 0) ; mtime
+                 (put-uleb128 line-port 0))) ; size
+              seed)
+            #f
+            files)
            (put-u8 line-port 0) ; 0 byte terminating file list.
 
            ;; Patch prologue length.
@@ -1663,7 +1872,7 @@ it will be added to the GC roots at runtime."
            ;; Now write the statement program.
            (let ()
              (define (extended-op opcode payload-len)
-               (put-u8 line-port 0) ; extended op
+               (put-u8 line-port 0)                     ; extended op
                (put-uleb128 line-port (1+ payload-len)) ; payload-len + opcode
                (put-uleb128 line-port opcode))
              (define (set-address sym)
@@ -1685,7 +1894,7 @@ it will be added to the GC roots at runtime."
                   (put-u64 line-port 0))))
              (define (end-sequence pc)
                (let ((pc-inc (- (asm-pos asm) pc)))
-                 (put-u8 line-port 2) ; advance-pc
+                 (put-u8 line-port 2)   ; advance-pc
                  (put-uleb128 line-port pc-inc))
                (extended-op 1 0))
              (define (advance-pc pc-inc line-inc)
@@ -1718,7 +1927,12 @@ it will be added to the GC roots at runtime."
 
              (let lp ((in out) (pc 0) (file 1) (line 1) (col 0))
                (match in
-                 (() (end-sequence pc))
+                 (()
+                  (when (null? out)
+                    ;; There was no source info in the first place.  Set
+                    ;; file register to 0 before adding final row.
+                    (set-file 0))
+                  (end-sequence pc))
                  (((pc* file* line* col*) . in*)
                   (cond
                    ((and (eqv? file file*) (eqv? line line*) (eqv? col col*))
@@ -1747,6 +1961,7 @@ it will be added to the GC roots at runtime."
       (cond
        ((string? val) 'strp)
        ((eq? attr 'stmt-list) 'sec-offset)
+       ((eq? attr 'low-pc) 'addr)
        ((exact-integer? code)
         (cond
          ((< code 0) 'sleb128)
@@ -1755,7 +1970,6 @@ it will be added to the GC roots at runtime."
          ((<= code #xffffffff) 'data4)
          ((<= code #xffffffffffffffff) 'data8)
          (else 'uleb128)))
-       ((symbol? val) 'addr)
        (else (error "unhandled case" attr val code))))
 
     (define (add-die-relocation! kind sym)
@@ -1835,7 +2049,8 @@ it will be added to the GC roots at runtime."
                 ;; Link text object after constants, so that the
                 ;; constants initializer gets included.
                 ((text) (link-text-object asm))
-                ((dt) (link-dynamic-section asm text rw rw-init))
+                ((frame-maps) (link-frame-maps asm))
+                ((dt) (link-dynamic-section asm text rw rw-init frame-maps))
                 ((symtab strtab) (link-symtab (linker-object-section text) asm))
                 ((arities arities-strtab) (link-arities asm))
                 ((docstrs docstrs-strtab) (link-docstrs asm))
@@ -1844,7 +2059,8 @@ it will be added to the GC roots at runtime."
                 ;; sections adds entries to the string table.
                 ((shstrtab) (link-shstrtab asm)))
     (filter identity
-            (list text ro rw dt symtab strtab arities arities-strtab
+            (list text ro frame-maps rw dt symtab strtab
+                  arities arities-strtab
                   docstrs docstrs-strtab procprops
                   dinfo dabbrev dstrtab dloc dline
                   shstrtab))))
@@ -1862,11 +2078,3 @@ The result is a bytevector, by default linked so that read-only and
 writable data are on separate pages.  Pass @code{#:page-aligned? #f} to
 disable this behavior."
   (link-elf (link-objects asm) #:page-aligned? page-aligned?))
-
-(define (assemble-program instructions)
-  "Take the sequence of instructions @var{instructions}, assemble them
-into RTL code, link an image, and load that image from memory.  Returns
-a procedure."
-  (let ((asm (make-assembler)))
-    (emit-text asm instructions)
-    (load-thunk-from-memory (link-assembly asm #:page-aligned? #f))))