Continuation labels and variable identifiers may be integers
[bpt/guile.git] / module / system / vm / assembler.scm
index b15a893..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 (language bytecode)
   #:use-module (rnrs bytevectors)
   #:use-module (ice-9 binary-ports)
   #:use-module (ice-9 vlist)
@@ -63,7 +63,7 @@
 
 \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-inlinable (pack-u8-u24 x y)
 \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*))
@@ -280,7 +288,7 @@ target."
             word-size endianness
             vlist-null '()
             (make-string-table) 1
-            '() '()))
+            '() '() '()))
 
 (define (intern-section-name! asm string)
   "Add a string to the section name table (shstrtab)."
@@ -342,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)
@@ -476,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)
              ...))))))
@@ -485,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))
@@ -596,14 +604,20 @@ table, its existing label is used directly."
         (static-set! 1 ,label 0)))
      ((uniform-vector-backing-store? obj) '())
      ((simple-uniform-vector? obj)
-      `((static-patch! ,label 2
-                       ,(recur (make-uniform-vector-backing-store
-                                (uniform-array->bytevector obj)
-                                (if (bitvector? obj)
-                                    ;; Bitvectors are addressed in
-                                    ;; 32-bit units.
-                                    4
-                                    (uniform-vector-element-size 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
@@ -701,11 +715,9 @@ returned instead."
 ;(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-objcode 53)
 ;(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-program 79)
 ;(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)
@@ -738,7 +750,7 @@ returned instead."
                 "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))
@@ -830,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
 
@@ -902,7 +920,7 @@ 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)
 
@@ -974,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"))))
 
@@ -1029,7 +1047,7 @@ should be .data or .rodata), and return the resulting linker object.
        ((simple-uniform-vector? obj)
         (let ((tag (if (bitvector? obj)
                        tc7-bitvector
-                       (let ((type-code (uniform-vector-element-type-code obj)))
+                       (let ((type-code (array-type-code obj)))
                          (logior tc7-bytevector (ash type-code 7))))))
           (case word-size
             ((4)
@@ -1195,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)))
@@ -1218,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)
@@ -1313,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:
 ;;;
@@ -1579,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))
@@ -1878,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)
@@ -1886,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)
@@ -1966,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))
@@ -1975,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))))