(load (merge-pathnames "editcore.lisp" *load-pathname*))

(in-package "SB-EDITCORE")

(defglobal +noexec-stack-note+ ".section .note.GNU-stack, \"\", @progbits")

;;; Emit .byte or .quad directives dumping memory from SAP for COUNT units
;;; (bytes or qwords) to STREAM.  SIZE specifies which direcive to emit.
;;; EXCEPTIONS specify offsets at which a specific string should be
;;; written to the file in lieu of memory contents, useful for emitting
;;; expressions involving the assembler '.' symbol (the current PC).
(defun emit-asm-directives (size sap count stream &optional exceptions)
  (declare (optimize speed))
  (declare (stream stream))
  (let ((*print-base* 16)
        (string-buffer (make-array 18 :element-type 'base-char))
        (fmt #.(coerce "0x%lx" 'base-string))
        (per-line 0))
    (declare ((integer 0 32) per-line)
             (fixnum count))
    string-buffer fmt
    (ecase size
      (:qword
       (format stream " .quad")
       (dotimes (i count)
         (declare ((unsigned-byte 20) i))
         (declare (simple-vector exceptions))
         (write-char (if (> per-line 0) #\, #\space) stream)
         (acond ((and (< i (length exceptions)) (aref exceptions i))
                 (write-string it stream))
                (t
                 (write-string "0x" stream)
                 (write (sap-ref-word sap (* i n-word-bytes)) :stream stream)))
         (when (and (= (incf per-line) 16) (< (1+ i) count))
           (format stream "~% .quad")
           (setq per-line 0))))
      (:byte
       (aver (not exceptions))
       (format stream " .byte")
       (dotimes (i count)
         (write-char (if (> per-line 0) #\, #\space) stream)
         (write-string "0x" stream)
         (write (sap-ref-8 sap i) :stream stream)
         (when (and (= (incf per-line) 32) (< (1+ i) count))
           (format stream "~% .byte")
           (setq per-line 0))))))
  (terpri stream))

#+arm64
(defun list-textual-instructions (sap length core load-addr emit-cfi)
  (declare (ignore emit-cfi))
  (let ((dstate (core-dstate core))
        (spaces (core-spacemap core))
        (seg (core-seg core))
        (list)
        (inst-ldr-reg (load-time-value (find-inst #xF940002A (get-inst-space))))
        (inst-bl (load-time-value (find-inst #x97EC8AEB (get-inst-space))))
        (inst-b (load-time-value (find-inst #x17FFFFE4 (get-inst-space))))
        (inst-adrp (load-time-value (find-inst #xB0FFA560 (get-inst-space))))
        (inst-add (load-time-value (find-inst #x91003F7C (get-inst-space))))
        (adrp-reg/addr nil))
    (setf (seg-virtual-location seg) load-addr
          (seg-length seg) length
          (seg-sap-maker seg) (lambda () sap))
    (map-segment-instructions
     (lambda (dchunk inst)
       (cond
         ((eq inst inst-adrp)
          (let ((reg (ldb (byte 5 0) dchunk))
                (next-dchunk (sb-arm64-asm::current-instruction dstate 4))
                (current-page (ash (dstate-cur-addr dstate) -12))
                (page-displacement
                  (sign-extend (+ (ldb (byte 2 29) dchunk)
                                  (ash (ldb (byte 19 5) dchunk) 2))
                               21)))
            (cond ((and (eq inst-add
                            (find-inst next-dchunk (load-time-value (get-inst-space))))
                        (= reg (ldb (byte 5 0) next-dchunk))
                        (= reg (ldb (byte 5 5) next-dchunk)))
                   ;; Rewrite any ADRP, ADD sequences which compute addresses
                   ;; into the linkage table into references into the GOT.
                   (let ((target-addr (+ (ash (+ current-page page-displacement) 12)
                                         (ldb (byte 12 10) next-dchunk))))
                     (when (or (in-bounds-p target-addr (core-fixedobj-bounds core))
                               (in-bounds-p target-addr (core-linkage-bounds core)))
                       (push (list (dstate-cur-offs dstate)
                                   4    ; length
                                   :adrp-gotpcrel
                                   target-addr
                                   (format nil "x~d" reg))
                             list)
                       (push (list (+ 4 (dstate-cur-offs dstate))
                                   4    ; length
                                   :ldr-gotpcrel
                                   target-addr
                                   (format nil "x~d" reg))
                             list))))
                  ((and (eq inst-ldr-reg
                            (find-inst next-dchunk (load-time-value (get-inst-space))))
                        (= reg (ldb (byte 5 0) next-dchunk))
                        (= reg (ldb (byte 5 5) next-dchunk)))
                   ;; Rewrite any ADRP, LDR sequences which load
                   ;; foreign-dataref addresses into the linkage table
                   ;; into references into the GOT.
                   (let ((target-addr (+ (ash (+ current-page page-displacement) 12)
                                         (ash (ldb (byte 12 10) next-dchunk) word-shift))))
                     (when (or (in-bounds-p target-addr (core-fixedobj-bounds core))
                               (in-bounds-p target-addr (core-linkage-bounds core)))
                       (push (list (dstate-cur-offs dstate)
                                   4    ; length
                                   :adrp-gotpcrel
                                   target-addr
                                   (format nil "x~d" reg))
                             list)
                       (push (list (+ 4 (dstate-cur-offs dstate))
                                   4    ; length
                                   :ldr-gotpcrel
                                   target-addr
                                   (format nil "x~d" reg))
                             list)))))))
         ((or (eq inst inst-bl) (eq inst inst-b))
          ;; Rewrite any BLs which jump to the trampoline in linkage
          ;; space to instead jump directly to the alien function in
          ;; the text section.
          (let ((target-addr (+ (dstate-cur-addr dstate)
                                (* 4 (sign-extend (ldb (byte 26 0) dchunk) 26)))))
            (when (or (in-bounds-p target-addr (core-fixedobj-bounds core))
                      (in-bounds-p target-addr (core-linkage-bounds core)))
              (push (list* (dstate-cur-offs dstate)
                           4            ; length
                           (if (eq inst inst-bl) :bl :b)
                           target-addr)
                    list))))))
     seg
     dstate
     nil)
    (nreverse list)))

;;; Disassemble the function pointed to by SAP for LENGTH bytes, returning
;;; all instructions that should be emitted using assembly language
;;; instead of .quad and/or .byte directives including:
;;; - instructions that manipulate the call frame
;;; - lisp-linkage table JMP, CALL, or LEA
;;; - alien-linkage table CALL or LEA
;;; At execution time the function will have virtual address LOAD-ADDR.
#+x86-64
(defun list-textual-instructions (sap length core load-addr emit-cfi)
  (let* ((insts (simple-collect-inst-model sap length load-addr))
         (alien-linkage-bounds
          (make-bounds (- (bounds-high (core-linkage-bounds core)) alien-linkage-space-size)
                       (bounds-high (core-linkage-bounds core))))
         (lisp-linkage-bounds
          (make-bounds (bounds-low (core-linkage-bounds core))
                       (bounds-low alien-linkage-bounds)))
         (result))
    (flet ((pc-relative-ea-p (x)
             (when (consp x) (setq x (car x)))
             (and (typep x 'machine-ea) (eq (machine-ea-base x) :rip)))
           (ea-disp-of (x)
             (when (consp x) (setq x (car x)))
             (machine-ea-disp x)))
      (when (and emit-cfi
                 (equalp (car insts) '(0 pop (#s(machine-ea :disp 8 :base 5) . :qword))))
        (push (list* 0 3 :pop "8(%rbp)") result))
      (do ((insts insts (cdr insts)))
          ((endp insts))
        (let* ((inst (car insts))
               (inst-len (if (cdr insts) (- (caadr insts) (car inst))))
               (ea (car (last inst))))
          (when (or (member (cadr inst) '(sb-x86-64-asm::jmp sb-x86-64-asm::call))
                    (pc-relative-ea-p ea))
            (cond ((and (integerp ea) (in-bounds-p ea (core-linkage-bounds core)))
                   (aver (eq (cadr inst) 'sb-x86-64-asm::call))
                   (aver (in-bounds-p ea alien-linkage-bounds)) ; CALL via alien linkage
                   (aver (= inst-len 5))
                   (push (list* (car inst) 5 :call ea) result))
                  ((pc-relative-ea-p ea)
                   (let* ((next-pc (+ load-addr (caadr insts)))
                          (ea (+ next-pc (ea-disp-of ea)))
                          (table-offset (- ea (bounds-low lisp-linkage-bounds))))
                     (cond ((in-bounds-p ea alien-linkage-bounds)
                            (aver (= inst-len 7))
                            (let ((op (ecase (cadr inst)
                                        (sb-x86-64-asm::mov :mov-gotpcrel)
                                        (sb-x86-64-asm::lea :lea)))
                                  (args (list ea (string-downcase (princ-to-string (third inst))))))
                              (push (list* (car inst) 7 op args) result)))
                           ((and (in-bounds-p ea lisp-linkage-bounds)
                                 (eq (cadr inst) 'sb-x86-64-asm::lea))
                              ;; Get ADDRESS of lisp linkage cell in stepping-enabled code
                            (aver (eq (third inst) (get-gpr :qword 0))) ; %rax
                            (aver (= inst-len 7))
                            (push (list* (car inst) 7 :lea (format nil "(fntbl+~d)(%rip),%rax"
                                                                   table-offset))
                                  result))
                           ((in-bounds-p ea lisp-linkage-bounds) ; lisp CALL or JMP
                            (aver (= inst-len 6))
                            (let ((new-inst
                                   (format nil "~a *(fntbl+~d)(%rip)"
                                           (string-downcase (cadr inst))
                                           table-offset)))
                              (push (list* (car inst) 6 :lispcall new-inst) result)))))))))))
      (nreverse result)))

(defun c-linkage-sym-from-addr (addr core)
  ;; assumption: alien-linkage-table-growth-direction is :UP for the platform
  (let* ((alien-ls-start (- (bounds-high (core-linkage-bounds core)) alien-linkage-space-size))
         (entry-index
          (sb-vm::alien-linkage-index-from-addr
           ;; The host's LINKAGE-SPACE-START is allowed to differ from the address
           ;; implied by the core, but the index-from-addr logic can be reused.
           (+ sb-vm:alien-linkage-space-start (- addr alien-ls-start)))))
    (setf (bit (core-alien-linkage-symbol-usedp core) entry-index) 1)
    (let ((symbol (aref (core-alien-linkage-symbols core) entry-index)))
      (if (listp symbol)
          (values (car symbol) entry-index t) ; data linkage
          (values symbol entry-index nil))))) ; code linkage

;;; Using assembler directives and/or real mnemonics, dump COUNT bytes
;;; of memory at PADDR (physical addr) to STREAM.
;;; The function's address as per the core file is VADDR.
;;; (Its eventual address is indeterminate)
;;; If EMIT-CFI is true, then also emit cfi directives.
;;;
;;; Notice that we can use one fewer cfi directive than usual because
;;; Lisp always carries a frame pointer as set up by the caller.
;;;
;;; C convention
;;; ============
;;; pushq %rbp
;;; .cfi_def_cfa_offset 16   # CFA offset from default register (rsp) is +16
;;; .cfi_offset 6, -16       # old rbp was saved in -16(CFA)
;;; movq %rsp, %rbp
;;; .cfi_def_cfa_register 6  # use rbp as CFA register
;;;
;;; Lisp convention
;;; ===============
;;; popq 8(%rbp) # place saved %rip in its ABI-compatible stack slot
;;;              # making RSP = RBP after the pop, and RBP = CFA - 16
;;; .cfi_def_cfa 6, 16
;;; .cfi_offset 6, -16
;;;
;;; Of course there is a flip-side to this: unwinders think that the new frame
;;; is already begun in the caller. Interruption between these two instructions:
;;;   MOV RBP, RSP / CALL #xzzzzz
;;; will show the backtrace as if two invocations of the caller are on stack.
;;; This is tricky to fix because while we can relativize the CFA to the
;;; known frame size, we can't do that based only on a disassembly.
(defun emit-lisp-function (paddr vaddr count stream emit-cfi core &optional labels)
  (when emit-cfi
    (format stream " .cfi_startproc~%"))
  ;; Any byte offset that appears as a key in the INSTRUCTIONS causes the indicated
  ;; bytes to be written as an assembly language instruction rather than opaquely,
  ;; thereby affecting the ELF data (cfi or relocs) produced.
  (let ((instructions
         (merge 'list labels
                (list-textual-instructions (int-sap paddr) count core vaddr emit-cfi)
                #'< :key #'car))
        (ptr paddr))
    (symbol-macrolet ((cur-offset (- ptr paddr)))
      (loop
        (let ((until (if instructions (caar instructions) count)))
          ;; if we're not aligned, then write some number of bytes
          ;; to cause alignment. But do not write past the next offset
          ;; that needs to be written as an instruction.
          (when (logtest ptr #x7) ; unaligned
            (let ((n (min (- (nth-value 1 (ceiling ptr 8)))
                          (- until cur-offset))))
              (aver (<= 0 n 7))
              (emit-asm-directives :byte (int-sap ptr) n stream)
              (incf ptr n)))
          ;; Now we're either aligned to a multiple of 8, or the current
          ;; offset needs to be written as a textual instruction.
          (let ((n (- until cur-offset)))
            (aver (>= n 0))
            (multiple-value-bind (qwords remainder) (floor n 8)
              (when (plusp qwords)
                (emit-asm-directives :qword (int-sap ptr) qwords stream #())
                (incf ptr (* qwords 8)))
              (when (plusp remainder)
                (emit-asm-directives :byte (int-sap ptr) remainder stream)
                (incf ptr remainder))))
          ;; If the current offset is COUNT, we're done.
          (when (= cur-offset count) (return))
          (aver (= cur-offset until))
          ;; A label and a textual instruction could co-occur.
          ;; If so, the label must be emitted first.
          (when (eq (cadar instructions) :label)
            (destructuring-bind (c-symbol globalp) (cddr (pop instructions))
              ;; The C symbol is global only if the Lisp name is a legal function
              ;; designator and not random noise.
              ;; This is a technique to try to avoid appending a uniquifying suffix
              ;; on all the junky internal things like "(lambda # in srcfile.lisp)"
              (when emit-cfi
                (format stream "~:[~; .globl ~a~:*~%~] .type ~a, @function~%"
                        globalp c-symbol))
              (format stream "~a:~%" c-symbol)))
          ;; If both a label and textual instruction occur here, handle the latter.
          ;; [This could could be simpler if all labels were emitted as
          ;; '.set "thing", .+const' together in a single place, but it's more readable
          ;; to see them where they belong in the instruction stream]
          (when (and instructions (= (caar instructions) cur-offset))
            (destructuring-bind (length opcode . operand) (cdr (pop instructions))
              (ecase opcode
                (#+arm64 (:bl :b) #+x86-64 (:call)
                 ;; XXX Should this AVER that it is in-bounds?
                 (when (in-bounds-p operand (core-linkage-bounds core))
                   (setq operand (c-linkage-sym-from-addr operand core)))
                 (format stream " ~A ~:[0x~X~;~a~:[~;@PLT~]~]~%"
                                   opcode (stringp operand) operand
                                   #+x86-64
                                   (core-enable-pie core)
                                   #+arm64 nil ; arm64 doesn't need the extra @PLT
                                   ))
                ((#+x86-64 :mov-gotpcrel) ; = get address of alien code linkage entry
                 (let ((c-symbol (c-linkage-sym-from-addr (car operand) core)))
                   (format stream " mov ~A@GOTPCREL(%rip), %~(~A~)~%" c-symbol (cadr operand))))
                ((#+arm64 :adrp-gotpcrel)
                 (let ((c-symbol (c-linkage-sym-from-addr (car operand) core)))
                   (format stream " adrp ~A,:got:~A~%" (cadr operand) c-symbol)))
                ((#+arm64 :ldr-gotpcrel)
                 (let ((c-symbol (c-linkage-sym-from-addr (car operand) core)))
                   (format stream " ldr ~A, [~A, #:got_lo12:~A]~%"
                           (cadr operand) (cadr operand) c-symbol)))
                ((#+x86-64 :lea)
                 ;; The THEN case is to calculate a linkage-table cell address for stepping,
                 ;; the ELSE case is FOREIGN-SYMBOL-SAP
                 (if (stringp operand)
                     (format stream " lea ~A~%" operand) ; the "operand" is the whole instructon
                     (let ((c-symbol (c-linkage-sym-from-addr (car operand) core)))
                       ;; lea becomes "mov" with gotpcrel as src, which will become lea
                       (format stream " mov ~A@GOTPCREL(%rip), %~(~A~)~%" c-symbol (cadr operand)))))
                ((#+x86-64 :pop)
                 (format stream " ~A ~A~%" opcode operand)
                 (cond ((string= operand "8(%rbp)")
                        (format stream " .cfi_def_cfa 6, 16~% .cfi_offset 6, -16~%"))
                       ((string= operand "%rbp")
                                        ;(format stream " .cfi_def_cfa 7, 8~%")
                        nil)
                       (t)))
                ((#+x86-64 :lispcall)
                           ;; the so-called "operand" is the entire instruction
                           (write-string operand stream)
                           (terpri stream)))
              (incf ptr length)))
          (when (= cur-offset count) (return)))))
    (when emit-cfi
      (format stream " .cfi_endproc~%"))
    nil))

(defun c-symbol-quote (name)
  (concatenate 'string '(#\") name '(#\")))

(defun c-name (lispname core pp-state &optional (prefix ""))
  (when (typep lispname '(string 0))
    (setq lispname "anonymous"))
  ;; Perform backslash escaping on the exploded string
  ;; Strings were stringified without surrounding quotes,
  ;; but there might be quotes embedded anywhere, so escape them,
  ;; and also remove newlines and non-ASCII.
  (let ((characters
         (mapcan (lambda (char)
                   (cond ((not (typep char 'base-char)) (list #\?))
                         ((member char '(#\\ #\")) (list #\\ char))
                         ((eql char #\newline) (list #\_))
                         (t (list char))))
                 (coerce (cond
                           #+darwin
                           ((and (stringp lispname)
                                 ;; L denotes a symbol which can not be global on macOS.
                                 (char= (char lispname 0) #\L))
                            (concatenate 'string "_" lispname))
                           (t
                            (write-to-string lispname
                              ;; Printing is a tad faster without a pretty stream
                              :pretty (not (typep lispname 'core-sym))
                              :pprint-dispatch *editcore-ppd*
                              ;; FIXME: should be :level 1, however see
                              ;; https://bugs.launchpad.net/sbcl/+bug/1733222
                              :escape nil :level 2 :length 5
                              :case :downcase :gensym nil
                              :right-margin 10000)))
                         'list))))
    (let ((string (concatenate 'string prefix characters)))
      ;; If the string appears in the linker symbols, then string-upcase it
      ;; so that it looks like a conventional Lisp symbol.
      (cond ((find-if (lambda (x) (string= string (if (consp x) (car x) x)))
                      (core-alien-linkage-symbols core))
             (setq string (string-upcase string)))
            ((string= string ".") ; can't use the program counter symbol either
             (setq string "|.|")))
      ;; If the symbol is still nonunique, add a random suffix.
      ;; The secondary value is whether the symbol should be a linker global.
      ;; For now, make nothing global, thereby avoiding potential conflicts.
      (let ((occurs (incf (gethash string (car pp-state) 0))))
        (if (> occurs 1)
            (values (concatenate 'string  string "_" (write-to-string occurs))
                    nil)
            (values string
                    nil))))))

(defun emit-symbols (blobs core pp-state output &aux base-symbol)
  (dolist (blob blobs base-symbol)
    (destructuring-bind (name start . end) blob
      (let ((c-name (c-name (or name "anonymous") core pp-state)))
        (unless base-symbol
          (setq base-symbol c-name))
        (format output " lsym \"~a\", 0x~x, 0x~x~%"
                c-name start (- end start))))))

(defun emit-funs (code vaddr core dumpwords output base-symbol emit-cfi)
  (let* ((ranges (get-text-ranges code core))
         (text-sap (code-instructions code))
         (text (sap-int text-sap))
         ;; Like CODE-INSTRUCTIONS, but where the text virtually was
         (text-vaddr (+ vaddr (* (code-header-words code) n-word-bytes)))
         (max-end 0))
    ;; There is *always* at least 1 word of unboxed data now
    (aver (eq (caar ranges) :data))
    (let ((jump-table-size (code-jump-table-words code))
          (total-nwords (cdr (pop ranges))))
      (cond ((> jump-table-size 1)
             (format output "# jump table~%")
             (format output ".quad ~d" (sap-ref-word text-sap 0))
             (dotimes (i (1- jump-table-size))
               (format output ",\"~a\"+0x~x"
                       base-symbol
                       (- (sap-ref-word text-sap (ash (1+ i) word-shift))
                          vaddr)))
             (terpri output)
             (let ((remaining (- total-nwords jump-table-size)))
               (when (plusp remaining)
                 (funcall dumpwords
                          (sap+ text-sap (ash jump-table-size word-shift))
                          remaining output))))
            (t
             (funcall dumpwords text-sap total-nwords output))))
    (loop
      (destructuring-bind (start . end) (pop ranges)
        (setq max-end end)
        ;; FIXME: it seems like this should just be reduced to emitting 2 words
        ;; now that simple-fun headers don't hold any boxed words.
        ;; (generality here is without merit)
        (funcall dumpwords (sap+ text-sap start) simple-fun-insts-offset output
                 #(nil #.(format nil ".+~D" (* (1- simple-fun-insts-offset)
                                             n-word-bytes))))
        (incf start (* simple-fun-insts-offset n-word-bytes))
        ;; Pass the current physical address at which to disassemble,
        ;; the notional core address (which changes after linker relocation),
        ;; and the length.
        (emit-lisp-function (+ text start) (+ text-vaddr start) (- end start)
                            output emit-cfi core)
        (cond ((not ranges) (return))
              ((eq (caar ranges) :pad)
               (format output " .byte ~{0x~x~^,~}~%"
                       (loop for i from 0 below (cdr (pop ranges))
                             collect (sap-ref-8 text-sap (+ end i))))))))
    ;; Emit bytes from the maximum function end to the object end.
    ;; We can't just round up %CODE-CODE-SIZE to a double-lispword
    ;; because the boxed header could end at an odd word, requiring that
    ;; the unboxed bytes have an odd size in words making the total even.
    (format output " .byte ~{0x~x~^,~}~%"
            (loop for i from max-end
                  below (- (code-object-size code)
                           (* (code-header-words code) n-word-bytes))
                  collect (sap-ref-8 text-sap i)))
    nil))

(defconstant +gf-name-slot+ 5)

(defun output-bignum (label bignum stream)
  (let ((nwords (sb-bignum:%bignum-length bignum)))
    (format stream "~@[~a:~] .quad 0x~x"
            label (logior (ash nwords 8) bignum-widetag))
    (dotimes (i nwords)
      (format stream ",0x~x" (sb-bignum:%bignum-ref bignum i)))
    (when (evenp nwords) ; pad
      (format stream ",0"))
    (format stream "~%")))

(defconstant core-align #+x86-64 4096 #+arm64 65536)

(defun write-preamble (output linkage-bss-size)
  (declare (ignorable linkage-bss-size))
  #+linkage-space (format output
                          " .globl lisp_fun_linkage_space
 .bss~% .align 8~% .size lisp_fun_linkage_space, ~D
lisp_fun_linkage_space: .zero ~:*~D
 .equiv fntbl, lisp_fun_linkage_space~%" linkage-bss-size)

  (format output " .text~% .file \"sbcl.core\"
~:[~; .macro .size sym size # ignore
 .endm
 .macro .type sym type # ignore
 .endm~]
 .macro lasmsym name size
 .set \"\\name\", .
 .size \"\\name\", \\size
 .type \"\\name\", function
 .endm
 .macro lsym name start size
 .set \"\\name\", . + \\start
 .size \"\\name\", \\size
 .type \"\\name\", function
 .endm
 .globl ~alisp_code_start, ~alisp_jit_code, ~alisp_code_end
 .balign ~a~%~alisp_code_start:~%CS: # code space~%"
          (member :darwin *features*)
          label-prefix label-prefix label-prefix
          core-align
          label-prefix))

(defun output-lisp-asm-routines (core spacemap code-addr output &aux (skip 0))
  (write-preamble output
                  #+linkage-space
                  (if (get-space immobile-fixedobj-core-space-id spacemap)
                      (ash 1 (+ sb-vm:n-linkage-index-bits sb-vm:word-shift))
                      ;; a minimal lisp_fun_linkage_space since runtime will
                      ;; actually use the area below static static for it.
                      sb-vm:n-word-bytes))
  (dotimes (i 2)
    (let* ((paddr (int-sap (translate-ptr code-addr spacemap)))
           (word (sap-ref-word paddr 0)))
      ;; After running the converter which moves dynamic-space code to text space,
      ;; the text space starts with an array of uint32 for the offsets to each object
      ;; and an array of uint64 containing some JMP instructions.
      (unless (or (= (%widetag-of word) simple-array-unsigned-byte-32-widetag)
                  (= (%widetag-of word) simple-array-unsigned-byte-64-widetag))
        (return))
      (let* ((array (%make-lisp-obj (logior (sap-int paddr) other-pointer-lowtag)))
             (size (primitive-object-size array))
             (nwords (ash size (- word-shift))))
        (dotimes (i nwords)
          (format output "~A 0x~x"
                  (case (mod i 8)
                    (0 #.(format nil "~% .quad"))
                    (t ","))
                  (sap-ref-word paddr (ash i word-shift))))
        (terpri output)
        (incf skip size)
        (incf code-addr size))))
  (let* ((code-component (make-code-obj code-addr spacemap))
         (obj-sap (int-sap (- (get-lisp-obj-address code-component)
                              other-pointer-lowtag)))
         (header-len (code-header-words code-component))
         (jump-table-count (sap-ref-word (code-instructions code-component) 0)))
    ;; Write the code component header
    (format output "lar: # lisp assembly routines~%")
    (emit-asm-directives :qword obj-sap header-len output #())
    ;; Write the jump table
    (format output " .quad ~D" jump-table-count)
    (dotimes (i (1- jump-table-count))
      (format output ",lar+0x~x"
              (- (sap-ref-word (code-instructions code-component)
                               (ash (1+ i) word-shift))
                 code-addr)))
    (terpri output)
    (let ((name->addr
           ;; the CDR of each alist item is a target cons (needing translation)
           (sort
            (mapcar (lambda (entry &aux (name (translate (undescriptorize (car entry)) spacemap)) ; symbol
                                   ;; VAL is (start end . index)
                                   (val (translate (undescriptorize (cdr entry)) spacemap))
                                   (start (car val))
                                   (end (car (translate (cdr val) spacemap))))
                      (list* (translate (symbol-name name) spacemap) start end))
                    (target-hash-table-alist (%code-debug-info code-component) spacemap))
            #'< :key #'cadr)))
      ;; Possibly unboxed words and/or padding
      (let ((here (ash jump-table-count word-shift))
            (first-entry-point (cadar name->addr)))
        (when (> first-entry-point here)
          (format output " .quad ~{0x~x~^,~}~%"
                  (loop for offs = here then (+ offs 8)
                        while (< offs first-entry-point)
                        collect (sap-ref-word (code-instructions code-component) offs)))))
      ;; Loop over the embedded routines
      (let ((list name->addr)
            (obj-size (code-object-size code-component)))
        (loop
          (destructuring-bind (name start-offs . end-offs) (pop list)
            (let ((nbytes (- (if (endp list)
                                 (- obj-size (* header-len n-word-bytes))
                                 (1+ end-offs))
                             start-offs)))
              (format output " lasmsym ~(\"~a\"~), ~d~%" name nbytes)
              (let ((fixups
                     (emit-lisp-function
                      (+ (sap-int (code-instructions code-component))
                         start-offs)
                      (+ code-addr
                         (ash (code-header-words code-component) word-shift)
                         start-offs)
                      nbytes output nil core)))
                (aver (null fixups)))))
          (when (endp list) (return)))
        (format output "~%# end of lisp asm routines~2%")
        (+ skip obj-size)))))

;;; Return a list of ((NAME START . END) ...)
;;; for each C symbol that should be emitted for this code object.
;;; Start and and are relative to the object's base address,
;;; not the start of its instructions. Hence we add HEADER-BYTES
;;; too all the PC offsets.
(defun code-symbols (code core)
  (let* ((fun-map (extract-fun-map code core))
         (header-bytes (* (code-header-words code) n-word-bytes))
         (start-pc 0)
         (i 1)
         (len (length fun-map))
         (blobs))
    (loop
      (let* ((name (remove-name-junk
                    (sb-c::compiled-debug-fun-name (svref fun-map (1- i)))))
             (end-pc (if (= i len)
                         (code-object-size code)
                         (+ header-bytes (svref fun-map i)))))
        (unless (= end-pc start-pc)
          ;; Collapse adjacent address ranges named the same.
          ;; Use EQUALP instead of EQUAL to compare names
          ;; because instances of CORE-SYMBOL are not interned objects.
          (if (and blobs (equalp (caar blobs) name))
              (setf (cddr (car blobs)) end-pc)
              (push (list* name start-pc end-pc) blobs)))
        (when (= i len)
          (return))
        (setq start-pc end-pc))
      (incf i 2))
    (nreverse blobs)))

;;; Convert immobile text space to an assembly file in OUTPUT.
(defun write-asm-file
    (spacemap linkage-space-info output
     &optional enable-pie (emit-cfi t)
     &aux (code-bounds (space-bounds immobile-text-core-space-id spacemap))
          (fixedobj-bounds (space-bounds immobile-fixedobj-core-space-id spacemap))
          (core (make-core spacemap code-bounds fixedobj-bounds
                           :enable-pie enable-pie :linkage-space-info linkage-space-info))
          (code-addr (bounds-low code-bounds))
          (total-code-size 0)
          (pp-state (cons (make-hash-table :test 'equal) nil))
          (prev-namestring "")
          (n-linker-relocs 0)
          (temp-output (make-string-output-stream :element-type 'base-char))
          end-loc)
  (labels ((dumpwords (sap count stream &optional (exceptions #()) logical-addr)
             (aver (sap>= sap (car spacemap)))
             ;; Add any new "header exceptions" that cause intra-code-space pointers
             ;; to be computed at link time
             (dotimes (i (if logical-addr count 0))
               (unless (and (< i (length exceptions)) (svref exceptions i))
                 (let ((word (sap-ref-word sap (* i n-word-bytes))))
                   (when (and (= (logand word 3) 3) ; is a pointer
                              (in-bounds-p word code-bounds)) ; to code space
                     #+nil
                     (format t "~&~(~x: ~x~)~%" (+ logical-addr  (* i n-word-bytes))
                             word)
                     (incf n-linker-relocs)
                     (setf exceptions (adjust-array exceptions (max (length exceptions) (1+ i))
                                                    :initial-element nil)
                           (svref exceptions i)
                           (format nil "CS+0x~x"
                                   (- word (bounds-low code-bounds))))))))
             (emit-asm-directives :qword sap count stream exceptions)))

    (let ((skip (output-lisp-asm-routines core spacemap code-addr output)))
      (incf code-addr skip)
      (incf total-code-size skip))
    (loop
      (when (>= code-addr (bounds-high code-bounds))
        (setq end-loc code-addr)
        (return))
      (ecase (%widetag-of (sap-ref-word (int-sap (translate-ptr code-addr spacemap)) 0))
        (#.code-header-widetag
         (let* ((code (make-code-obj code-addr spacemap))
                (objsize (code-object-size code)))
           (incf total-code-size objsize)
           (cond
             ((%instancep (%code-debug-info code)) ; assume it's a COMPILED-DEBUG-INFO
              (aver (plusp (code-n-entries code)))
              (let* ((source
                      (sb-c::compiled-debug-info-source
                       (truly-the sb-c::compiled-debug-info
                                  (translate (%code-debug-info code) spacemap))))
                     (namestring
                      (sb-c::debug-source-namestring
                       (truly-the sb-c::debug-source (translate source spacemap)))))
                (setq namestring (if (core-null-p namestring)
                                     "sbcl.core"
                                     (translate namestring spacemap)))
                (unless (string= namestring prev-namestring)
                  (format output " .file \"~a\"~%" namestring)
                  (setq prev-namestring namestring)))
              (let ((code-physaddr (logandc2 (get-lisp-obj-address code) lowtag-mask)))
                (format output "#x~x:~%" code-addr)
                ;; Emit symbols before the code header data, because the symbols
                ;; refer to "." (the current PC) which is the base of the object.
                (let* ((base (emit-symbols (code-symbols code core) core pp-state output))
                       (altered-fixups
                        (emit-funs code code-addr core #'dumpwords temp-output base emit-cfi))
                       (header-exceptions (vector nil nil nil nil)))
                  (aver (null altered-fixups))
                  (dumpwords (int-sap code-physaddr)
                             (code-header-words code) output header-exceptions code-addr)
                  (write-string (get-output-stream-string temp-output) output))))
             (t
              (error "Strange code component: ~S" code)))
           (incf code-addr objsize)))
        (#.filler-widetag
         (let* ((word (sap-ref-word (int-sap (translate-ptr code-addr spacemap)) 0))
                (nwords (ash word -32))
                (nbytes (* nwords n-word-bytes)))
           (format output " .quad 0x~x~% .fill ~d~%" word (- nbytes n-word-bytes))
           (incf code-addr nbytes)))
        ;; This is a trailing array which contains a jump instruction for each
        ;; element of *C-LINKAGE-REDIRECTS* (see "rewrite-asmcalls.lisp").
        (#.simple-array-unsigned-byte-64-widetag
         (let* ((paddr (translate-ptr code-addr spacemap))
                (array (%make-lisp-obj (logior paddr other-pointer-lowtag)))
                (nwords (+ vector-data-offset (align-up (length array) 2))))
           (format output "# alien linkage redirects:~% .quad")
           (dotimes (i nwords (terpri output))
             (format output "~a0x~x" (if (= i 0) " " ",")
                     (sap-ref-word (int-sap paddr) (ash i word-shift))))
           (incf code-addr (ash nwords word-shift))
           (setq end-loc code-addr)
           (return))))))

  ;; coreparse uses the 'lisp_jit_code' symbol to set text_space_highwatermark
  ;; The intent is that compilation to memory can use this reserved area
  ;; (if space remains) so that profilers can associate a C symbol with the
  ;; program counter range. It's better than nothing.
  (format output "~a:~%" (labelize "lisp_jit_code"))

  ;; Pad so that non-lisp code can't be colocated on a GC page.
  ;; (Lack of Lisp object headers in C code is the issue)
  (let ((aligned-end (align-up end-loc core-align)))
    (when (> aligned-end end-loc)
      (multiple-value-bind (nwords remainder)
          (floor (- aligned-end end-loc) n-word-bytes)
        (aver (>= nwords 2))
        (aver (zerop remainder))
        (decf nwords 2)
        (format output " .quad 0x~x, ~d # (simple-array fixnum (~d))~%"
                simple-array-fixnum-widetag
                (ash nwords n-fixnum-tag-bits)
                nwords)
        (when (plusp nwords)
          (format output " .fill ~d~%" (* nwords n-word-bytes))))))
  ;; Extend with 1 MB of filler
  (format output " .fill ~D~%~alisp_code_end:
 .size lisp_jit_code, .-lisp_jit_code~%"
          (* 1024 1024) label-prefix)
  (values core total-code-size n-linker-relocs))

;;;; ELF file I/O

(defconstant +sht-null+     0)
(defconstant +sht-progbits+ 1)
(defconstant +sht-symtab+   2)
(defconstant +sht-strtab+   3)
(defconstant +sht-rela+     4)
(defconstant +sht-rel+      9)

(define-alien-type elf64-ehdr
  (struct elf64-edhr
    (ident     (array unsigned-char 16)) ; 7F 45 4C 46 2 1 1 0 0 0 0 0 0 0 0 0
    (type      (unsigned 16))   ; 1 0
    (machine   (unsigned 16))   ; 3E 0
    (version   (unsigned 32))   ; 1 0 0 0
    (entry     unsigned)        ; 0 0 0 0 0 0 0 0
    (phoff     unsigned)        ; 0 0 0 0 0 0 0 0
    (shoff     unsigned)        ;
    (flags     (unsigned 32))   ; 0 0 0 0
    (ehsize    (unsigned 16))   ; 40 0
    (phentsize (unsigned 16))   ;  0 0
    (phnum     (unsigned 16))   ;  0 0
    (shentsize (unsigned 16))   ; 40 0
    (shnum     (unsigned 16))   ;  n 0
    (shstrndx  (unsigned 16)))) ;  n 0
(defconstant ehdr-size (ceiling (alien-type-bits (parse-alien-type 'elf64-ehdr nil)) 8))
(define-alien-type elf64-shdr
  (struct elf64-shdr
    (name      (unsigned 32))
    (type      (unsigned 32))
    (flags     (unsigned 64))
    (addr      (unsigned 64))
    (off       (unsigned 64))
    (size      (unsigned 64))
    (link      (unsigned 32))
    (info      (unsigned 32))
    (addralign (unsigned 64))
    (entsize   (unsigned 64))))
(defconstant shdr-size (ceiling (alien-type-bits (parse-alien-type 'elf64-shdr nil)) 8))
(define-alien-type elf64-sym
  (struct elf64-sym
    (name  (unsigned 32))
    (info  (unsigned 8))
    (other (unsigned 8))
    (shndx (unsigned 16))
    (value unsigned)
    (size  unsigned)))
(define-alien-type elf64-rela
  (struct elf64-rela
    (offset (unsigned 64))
    (info   (unsigned 64))
    (addend (signed 64))))

(defun make-elf64-sym (name info)
  (let ((a (make-array 24 :element-type '(unsigned-byte 8) :initial-element 0)))
    (with-pinned-objects (a)
      (setf (sap-ref-32 (vector-sap a) 0) name
            (sap-ref-8 (vector-sap a) 4) info))
    a))

;;; Return two values: an octet vector comprising a string table
;;; and an alist which maps string to offset in the table.
(defun string-table (strings)
  (let* ((length (+ (1+ (length strings)) ; one more null than there are strings
                    (reduce #'+ strings :key #'length))) ; data length
         (bytes (make-array length :element-type '(unsigned-byte 8)
                            :initial-element 0))
         (index 1)
         (alist))
    (dolist (string strings)
      (push (cons string index) alist)
      (replace bytes (map 'vector #'char-code string) :start1 index)
      (incf index (1+ (length string))))
    (cons (nreverse alist) bytes)))

(defun write-alien (alien size stream)
  (dotimes (i size)
    (write-byte (sap-ref-8 (alien-value-sap alien) i) stream)))

;;; core header should be an array of words in '.rodata', not a 32K page
(defconstant core-header-size +backend-page-bytes+) ; stupidly large (FIXME)

(defconstant e-machine #+x86-64 #x3E #+arm64 #xB7)

(defun write-elf-header (shdrs-start sections output)
  (let ((shnum (1+ (length sections)))  ; section 0 is implied
        (shstrndx (1+ (position :str sections :key #'car)))
        (ident #.(coerce '(#x7F #x45 #x4C #x46 2 1 1 0 0 0 0 0 0 0 0 0)
                         '(array (unsigned-byte 8) 1))))
    (with-alien ((ehdr elf64-ehdr))
      (dotimes (i (ceiling ehdr-size n-word-bytes))
        (setf (sap-ref-word (alien-value-sap ehdr) (* i n-word-bytes)) 0))
      (with-pinned-objects (ident)
        (%byte-blt (vector-sap ident) 0 (alien-value-sap ehdr) 0 16))
      (setf (slot ehdr 'type)      1
            (slot ehdr 'machine)   e-machine
            (slot ehdr 'version)   1
            (slot ehdr 'shoff)     shdrs-start
            (slot ehdr 'ehsize)    ehdr-size
            (slot ehdr 'shentsize) shdr-size
            (slot ehdr 'shnum)     shnum
            (slot ehdr 'shstrndx)  shstrndx)
      (write-alien ehdr ehdr-size output))))

(defun write-section-headers (placements sections string-table output)
  (with-alien ((shdr elf64-shdr))
    (dotimes (i (ceiling shdr-size n-word-bytes)) ; Zero-fill
      (setf (sap-ref-word (alien-value-sap shdr) (* i n-word-bytes)) 0))
    (dotimes (i (1+ (length sections)))
      (when (plusp i) ; Write the zero-filled header as section 0
        (destructuring-bind (name type flags link info alignment entsize)
            (cdr (aref sections (1- i)))
          (destructuring-bind (offset . size)
              (pop placements)
            (setf (slot shdr 'name)  (cdr (assoc name (car string-table)))
                  (slot shdr 'type)  type
                  (slot shdr 'flags) flags
                  (slot shdr 'off)   offset
                  (slot shdr 'size)  size
                  (slot shdr 'link)  link
                  (slot shdr 'info)  info
                  (slot shdr 'addralign) alignment
                  (slot shdr 'entsize) entsize))))
      (write-alien shdr shdr-size output))))

(defconstant sym-entry-size 24)

;;; Write everything except for the core file itself into OUTPUT-STREAM
;;; and leave the stream padded to a 4K boundary ready to receive data.
(defun prepare-elf (core-size relocs output pie)
  ;; PIE uses coreparse relocs which are 8 bytes each, and no linker relocs.
  ;; Otherwise, linker relocs are 24 bytes each.
  (let* ((reloc-entry-size (if pie 8 24))
         (sections
          ;;        name | type | flags | link | info | alignment | entry size
          `#((:core "lisp.core"       ,+sht-progbits+ 0 0 0 ,core-align 0)
             (:sym  ".symtab"         ,+sht-symtab+   0 3 1 8 ,sym-entry-size)
                          ; section with the strings -- ^ ^ -- 1+ highest local symbol
             (:str  ".strtab"         ,+sht-strtab+   0 0 0 1  0)
             (:rel
              ,@(if pie
              ;; Don't bother with an ELF reloc section; it won't do any good.
              ;; It would apply at executable link time, which is without purpose,
              ;; it just offsets the numbers based on however far the lisp.core
              ;; section is into the physical file. Non-loaded sections don't get
              ;; further relocated on execution, so 'coreparse' has to fix the
              ;; entire dynamic space at execution time anyway.
                  `("lisp.rel"        ,+sht-progbits+ 0 0 0 8 8)
                  `(".relalisp.core"  ,+sht-rela+     0 2 1 8 ,reloc-entry-size)))
                                      ; symbol table -- ^ ^ -- for which section
             (:note ".note.GNU-stack" ,+sht-progbits+ 0 0 0 1  0)))
         (extern-c-symbols
          '("lisp_code_start" #+linkage-space "lisp_fun_linkage_space"))
         (string-table
          (string-table (append extern-c-symbols (map 'list #'second sections))))
         (strings (cdr string-table))
         (padded-strings-size (align-up (length strings) 8))
         ;; 0th symbol is always for string table index 0
         (symbols-size (* (1+ (length extern-c-symbols)) sym-entry-size))
         (shdrs-start (+ ehdr-size symbols-size padded-strings-size))
         (shdrs-end (+ shdrs-start (* (1+ (length sections)) shdr-size)))
         (relocs-size (* (length relocs) reloc-entry-size))
         (relocs-end (+ shdrs-end relocs-size))
         (core-start (align-up relocs-end core-align)))

    (write-elf-header shdrs-start sections output)

    ;; Write symbol table
    (aver (eql (file-position output) ehdr-size))
    (write-sequence (make-elf64-sym 0 0) output)
    (dolist (sym extern-c-symbols)
      (let ((string-table-index (cdr (assoc sym (car string-table) :test 'string=))))
        ;; The symbol type is #x10 given:
        ;;      #define STB_GLOBAL 1
        ;;   and: #define ELF32_ST_BIND(val) ((unsigned char) (val)) >> 4)
        ;; which places the binding in the high 4 bits of the low byte.
        (write-sequence (make-elf64-sym string-table-index #x10) output)))

    ;; Write string table
    (aver (eql (file-position output) (+ ehdr-size symbols-size)))
    (write-sequence strings output) ; an octet vector at this point
    (dotimes (i (- padded-strings-size (length strings)))
      (write-byte 0 output))

    ;; Write section headers
    (aver (eql (file-position output) shdrs-start))
    (write-section-headers
     (map 'list
          (lambda (x)
            (ecase (car x)
              (:note '(0 . 0))
              (:sym  (cons ehdr-size symbols-size))
              (:str  (cons (+ ehdr-size symbols-size) (length strings)))
              (:rel  (cons shdrs-end relocs-size))
              (:core (cons core-start core-size))))
          sections)
     sections string-table output)

    ;; Write relocations
    (aver (eql (file-position output) shdrs-end))
    (let ((buf (make-array relocs-size :element-type '(unsigned-byte 8)))
          (ptr 0))
      (if pie
          (dovector (reloc relocs)
            (setf (%vector-raw-bits buf ptr) reloc)
            (incf ptr))
          (with-alien ((rela elf64-rela))
            (dovector (r relocs)
              (declare (type (simple-vector 4) r))
              (let ((kind (aref r 0)) (place (aref r 1)) (symbol (aref r 2)) (addend (aref r 3)))
                (setf (slot rela 'offset) place
                      (slot rela 'info)   (logior (ash symbol 32) kind)
                      (slot rela 'addend) addend))
              (setf (%vector-raw-bits buf (+ ptr 0)) (sap-ref-word (alien-value-sap rela) 0)
                    (%vector-raw-bits buf (+ ptr 1)) (sap-ref-word (alien-value-sap rela) 8)
                    (%vector-raw-bits buf (+ ptr 2)) (sap-ref-word (alien-value-sap rela) 16))
              (incf ptr 3))))
      (write-sequence buf output))

    ;; Write padding
    (dotimes (i (- core-start (file-position output)))
      (write-byte 0 output))
    (aver (eq (file-position output) core-start))))

(defconstant R_ABS64 #+x86-64 1 #+arm64 257) ; /* Direct 64 bit  */
(defconstant R_ABS32 #+x86-64 10 #+arm64 258) ; /* Direct 32 bit zero extended */

;;; Fill in the FIXUPS vector with a list of places to fixup.
;;; For PIE-enabled cores, each place is just a virtual address.
;;; For non-PIE-enabled, the fixup corresponds to an ELF relocation which will be
;;; applied at link time of the excutable.
;;; Note that while this "works" for PIE, it is fairly inefficient because
;;; fundamentally Lisp objects contain absolute pointers, and there may be
;;; millions of words that need fixing at load (execution) time.
;;; Several techniques can mitigate this:
;;; * for funcallable-instances, put a second copy of funcallable-instance-tramp
;;;   in dynamic space so that funcallable-instances can jump to a known address.
;;; * for each closure, create a one-instruction trampoline in dynamic space,
;;;   - embedded in a (simple-array word) perhaps - which jumps to the correct
;;;   place in the text section. Point all closures over the same function
;;;   to the new closure stub. The trampoline, being pseudostatic, is effectively
;;;   immovable. (And you can't re-save from an ELF core)
;;; * for arbitrary pointers to simple-funs, create a proxy simple-fun in dynamic
;;;   space whose entry point is the real function in the ELF text section.
;;;   The GC might have to learn how to handle simple-funs that point externally
;;;   to themselves. Also there's a minor problem of hash-table test functions
;;; The above techniques will reduce by a huge factor the number of fixups
;;; that need to be applied on startup of a position-independent executable.
;;;
(defun collect-relocations (spacemap fixups pie &key (verbose nil) (print nil))
  (let* ((code-bounds (space-bounds immobile-text-core-space-id spacemap))
         (code-start (bounds-low code-bounds))
         (n-abs 0)
         (n-rel 0)
         (affected-pages (make-hash-table)))
    (labels
        ((abs-fixup (vaddr core-offs referent)
           (incf n-abs)
           (when print
             (format t "~x = 0x~(~x~): (a)~%" core-offs vaddr #+nil referent))
           (touch-core-page core-offs)
           ;; PIE relocations are output as a file section that is
           ;; interpreted by 'coreparse'. The addend is implicit.
           (setf (sap-ref-word (car spacemap) core-offs)
                 (if pie
                     (+ (- referent code-start) +code-space-nominal-address+)
                     0))
           (if pie
               (vector-push-extend vaddr fixups)
               (vector-push-extend
                ;; #(kind, where symbol-index addend)
                `#(,R_ABS64 ,(+ core-header-size core-offs) 1 ,(- referent code-start))
                fixups)))
         (touch-core-page (core-offs)
           ;; use the OS page size, not +backend-page-bytes+
           (setf (gethash (floor core-offs core-align) affected-pages) t))
         ;; Given a address which is an offset into the data pages of the target core,
         ;; compute the logical address which that offset would be mapped to.
         ;; For example core address 0 is the virtual address of static space.
         (core-to-logical (core-offs &aux (page (floor core-offs +backend-page-bytes+)))
           (setf (gethash page affected-pages) t)
           (dolist (space (cdr spacemap)
                          (bug "Can't translate core offset ~x using ~x"
                               core-offs spacemap))
             (let* ((page0 (space-data-page space))
                    (nwords (space-nwords space))
                    (id (space-id space))
                    (npages (ceiling nwords (/ +backend-page-bytes+ n-word-bytes))))
               (when (and (<= page0 page (+ page0 (1- npages)))
                          (/= id immobile-text-core-space-id))
                 (return (+ (space-addr space)
                            (* (- page page0) +backend-page-bytes+)
                            (logand core-offs (1- +backend-page-bytes+))))))))
         (scanptrs (vaddr obj wordindex-min wordindex-max &optional force &aux (n-fixups 0))
           (do* ((base-addr (logandc2 (get-lisp-obj-address obj) lowtag-mask))
                 (sap (int-sap base-addr))
                 ;; core-offs is the offset in the lisp.core ELF section.
                 (core-offs (- base-addr (sap-int (car spacemap))))
                 (i wordindex-min (1+ i)))
                ((> i wordindex-max) n-fixups)
             (let* ((byte-offs (ash i word-shift))
                    (ptr (sap-ref-word sap byte-offs)))
               (when (and (or (is-lisp-pointer ptr) force) (in-bounds-p ptr code-bounds))
                 (abs-fixup (+ vaddr byte-offs) (+ core-offs byte-offs) ptr)
                 (incf n-fixups)))))
         (scanptr (vaddr obj wordindex)
           (plusp (scanptrs vaddr obj wordindex wordindex))) ; trivial wrapper
         (scan-obj (vaddr obj widetag size
                    &aux (core-offs (- (logandc2 (get-lisp-obj-address obj) lowtag-mask)
                                       (sap-int (car spacemap))))
                         (nwords (ceiling size n-word-bytes)))
           (when (listp obj)
             (scanptrs vaddr obj 0 1)
             (return-from scan-obj))
           (case widetag
             (#.instance-widetag
              (let ((type (truly-the layout (translate (%instance-layout obj) spacemap))))
                (do-layout-bitmap (i taggedp type (%instance-length obj))
                  (when taggedp
                    (scanptr vaddr obj (1+ i))))))
             (#.simple-vector-widetag
              (let ((len (length (the simple-vector obj))))
                ;; FIXME: VECTOR-ADDR-HASHING-FLAG must be left-shifted by ARRAY-FLAGS-DATA-POSITION
                ;; for this LOGTEST to be correct.  I think it broke when array-rank was placed adjacent
                ;; to the widetag and the flags bits moved over. The fact that it seems to be useless
                ;; suggests that I need to come up with a way to assert that it does anything, which
                ;; is all but impossible. We'd need a correctly-hashed table containing a key in which
                ;; nothing moves in the final S-L-A-D, but does have a function move during elfination.
                ;; I'm leaving this line broken for now because frankly I think it might be safer to
                ;; assert that IF the table is address-sensitive THEN it has the rehash flag already set
                ;; in the k/v vector. That's not quite true either, because it only needs to rehash if
                ;; any key was actually hashed by address.
                (cond ((logtest (get-header-data obj) vector-addr-hashing-flag) ; BUG
                       (do ((i 2 (+ i 2)) (needs-rehash))
                           ;; Refer to the figure at the top of src/code/hash-table.lisp.
                           ;; LEN is an odd number.
                           ((>= i (1- len))
                            (when needs-rehash
                              (setf (svref obj 1) 1)))
                         ;; A weak or EQ-based hash table any of whose keys is a function
                         ;; or code-component might need the 'rehash' flag set.
                         ;; In practice, it is likely already set, because any object that
                         ;; could move in the final GC probably did move.
                         (when (scanptr vaddr obj (+ vector-data-offset i))
                           (setq needs-rehash t))
                         (scanptr vaddr obj (+ vector-data-offset i 1))))
                      (t
                       (scanptrs vaddr obj 1 (+ len 1))))))
             (#.fdefn-widetag
              (scanptrs vaddr obj 1 2)
              (scanptrs vaddr obj 3 3 t))
             ((#.closure-widetag #.funcallable-instance-widetag)
              ;; read the trampoline slot
              (let ((word (sap-ref-word (int-sap (get-lisp-obj-address obj))
                                        (- n-word-bytes fun-pointer-lowtag))))
                (when (in-bounds-p word code-bounds)
                  (abs-fixup (+ vaddr n-word-bytes)
                             (+ core-offs n-word-bytes)
                             word)))
              ;; untaggged pointers are generally not supported in
              ;; funcallable instances, so scan everything.
              (scanptrs vaddr obj 1 (1- nwords)))
             ;; mixed boxed/unboxed objects
             (#.code-header-widetag
              (aver (not pie))
              (dotimes (i (code-n-entries obj))
                ;; I'm being lazy and not computing vaddr, which is wrong,
                ;; but does not matter if non-pie; and if PIE, we can't get here.
                ;; [PIE requires all code in immobile space, and this reloc
                ;; is for a dynamic space object]
                (scanptrs 0 (%code-entry-point obj i) 2 5))
              (scanptrs vaddr obj 1 (1- (code-header-words obj))))
             (#.symbol-widetag ; HASH is a raw slot, skip it
              (scanptrs vaddr obj 2 (1- nwords)))
             ;; other boxed objects that can reference code/simple-funs
             ((#.value-cell-widetag #.weak-pointer-widetag)
              (scanptrs vaddr obj 1 (1- nwords))))))
      (dolist (space (cdr spacemap))
        (unless (= (space-id space) immobile-text-core-space-id)
          (let* ((logical-addr (space-addr space))
                 (size (space-size space))
                 (physical-addr (space-physaddr space spacemap))
                 (physical-end (sap+ physical-addr size))
                 (vaddr-translation (+ (- (sap-int physical-addr)) logical-addr)))
            (dx-flet ((visit (obj widetag size)
                        ;; Compute the object's intended virtual address
                        (scan-obj (+ (logandc2 (get-lisp-obj-address obj) lowtag-mask)
                                     vaddr-translation)
                                  obj widetag size)))
              (map-objects-in-range
               #'visit
               (ash (sap-int physical-addr) (- n-fixnum-tag-bits))
               (ash (sap-int physical-end) (- n-fixnum-tag-bits))))
            (when (and (plusp (logior n-abs n-rel)) verbose)
              (format t "space @ ~10x: ~6d absolute + ~4d relative fixups~%"
                      logical-addr n-abs n-rel))
            (setq n-abs 0 n-rel 0)))))
    (when verbose
      (format t "total of ~D linker fixups affecting ~D/~D pages~%"
              (length fixups)
              (hash-table-count affected-pages)
              (/ (reduce #'+ (cdr spacemap) :key #'space-nbytes-aligned)
                 core-align))))
  fixups)

(defun force-fntbl-ref-p (spacemap)
  (let ((features (detect-target-features spacemap)))
    (not (find :immobile-code features))))

;;; Given a native SBCL '.core' file, or one attached to the end of an executable,
;;; separate it into pieces.
;;; ASM-PATHNAME is the name of the assembler file that will hold all the Lisp code.
;;; The other two output pathnames are implicit: "x.s" -> "x.core" and "x-core.o"
;;; The ".core" file is a native core file used for starting a binary that
;;; contains the asm code using the "--core" argument.  The "-core.o" file
;;; is for linking in to a binary that needs no "--core" argument.
(defun really-split-core
    (input-pathname asm-pathname
     &key enable-pie (verbose nil) dynamic-space-size
     &aux (elf-core-pathname
           (merge-pathnames
            (make-pathname :name (concatenate 'string (pathname-name asm-pathname) "-core")
                           :type "o")
            asm-pathname))
          (core-header (make-array +backend-page-bytes+ :element-type '(unsigned-byte 8)))
          (original-total-npages 0)
          (core-offset 0)
          (page-adjust 0)
          (linkage-space-info (vector 0 0 0 0 0))
          (code-start-fixup-ofs 0) ; where to fixup the core header
          (static-consts-fixup-ofs 0)
          (space-list)
          (copy-actions)
          (fixedobj-range) ; = (START . SIZE-IN-BYTES)
          (relocs (make-array 100000 :adjustable t :fill-pointer 1)))

  (declare (ignorable fixedobj-range))
  ;; Remove old files
  (ignore-errors (delete-file asm-pathname))
  (ignore-errors (delete-file elf-core-pathname))
  ;; Ensure that all files can be opened
  (with-open-file (input input-pathname :element-type '(unsigned-byte 8))
    (with-open-file (asm-file asm-pathname :direction :output :if-exists :supersede)
      ;;(with-open-file (split-core split-core-pathname :direction :output
      ;;                            :element-type '(unsigned-byte 8) :if-exists :supersede)
      (let ((split-core nil))
        (setq core-offset (read-core-header input core-header verbose))
        ;; TODO: just use PARSE-CORE-HEADER. Any day now. (This logic got here first)
        (do-core-header-entry ((id len ptr) core-header)
          (case id
            (#.build-id-core-entry-type-code
             (when verbose
               (let* ((ptr (+ ptr 3))
                      (string (make-string (%vector-raw-bits core-header ptr)
                                           :element-type 'base-char)))
                 (%byte-blt core-header (* (1+ ptr) n-word-bytes) string 0 (length string))
                 (format t "Build ID [~a]~%" string))))
            (#.directory-core-entry-type-code
             (do-directory-entry ((index ptr len) core-header)
               (incf original-total-npages npages)
               (push (make-space id addr data-page page-adjust nwords) space-list)
               (when verbose
                 (format t "id=~d page=~5x + ~5x addr=~10x words=~8x~:[~; (drop)~]~%"
                         id data-page npages addr nwords
                         (= id immobile-text-core-space-id)))
               (cond ((= id immobile-text-core-space-id)
                      (setq code-start-fixup-ofs (+ index 3))
                      ;; Keep this entry but delete the page count. We need to know
                      ;; where the space was supposed to be mapped and at what size.
                      ;; Subsequent core entries will need to adjust their start page
                      ;; downward (just the PTEs's start page now).
                      (setq page-adjust npages data-page 0 npages 0))
                     (t
                      ;; Keep track of where the fixedobj space wants to be.
                      (when (= id immobile-fixedobj-core-space-id)
                        (setq fixedobj-range (cons addr (ash nwords word-shift))))
                      (when (plusp npages) ; enqueue
                        (push (cons data-page (* npages +backend-page-bytes+)) copy-actions))
                      ;; adjust this entry's start page in the new core
                      (decf data-page page-adjust)))))
            (#.lisp-linkage-space-core-entry-type-code
             (symbol-macrolet ((count (%vector-raw-bits core-header (+ ptr 0)))
                               (data-page (%vector-raw-bits core-header (+ ptr 1))))
               (let ((npages (ceiling (ash count word-shift) +backend-page-bytes+)))
                 (setq linkage-space-info
                       (vector (+ ptr 2) data-page npages count
                               (make-array count :element-type 'word)))
                 (push (cons data-page (* npages +backend-page-bytes+)) copy-actions)
                 (incf original-total-npages npages))))
            (#.static-constants-core-entry-type-code
             (setq static-consts-fixup-ofs ptr))
            (#.page-table-core-entry-type-code
             (aver (= len 3))
             (symbol-macrolet ((n-ptes (%vector-raw-bits core-header (+ ptr 0)))
                               (nbytes (%vector-raw-bits core-header (+ ptr 1)))
                               (data-page (%vector-raw-bits core-header (+ ptr 2))))
               (aver (= data-page original-total-npages))
               (aver (= (ceiling (space-nwords
                                  (find dynamic-core-space-id space-list :key #'space-id))
                                 (/ gencgc-page-bytes n-word-bytes))
                        n-ptes))
               (when verbose
                 (format t "PTE: page=~5x~40tbytes=~8x~%" data-page nbytes))
               (push (cons data-page nbytes) copy-actions)
               (decf data-page page-adjust)))))
        (let ((buffer (make-array +backend-page-bytes+
                                  :element-type '(unsigned-byte 8)))
              (filepos))
          ;; Write the new core file
          (when split-core
            (write-sequence core-header split-core))
          (dolist (action (reverse copy-actions)) ; nondestructive
            ;; page index convention assumes absence of core header.
            ;; i.e. data page 0 is the file page immediately following the core header
            (let ((offset (* (1+ (car action)) +backend-page-bytes+))
                  (nbytes (cdr action)))
              (when verbose
                (format t "File offset ~10x: ~10x bytes~%" offset nbytes))
              (setq filepos (+ core-offset offset))
              (cond (split-core
                     (file-position input filepos)
                     (copy-bytes input split-core nbytes buffer))
                    (t
                     (file-position input (+ filepos nbytes))))))
          ;; Trailer (runtime options and magic number)
          (let ((nbytes (read-sequence buffer input)))
            ;; expect trailing magic number
            (let ((ptr (floor (- nbytes n-word-bytes) n-word-bytes)))
              (aver (= (%vector-raw-bits buffer ptr) core-magic)))
            ;; File position of the core header needs to be set to 0
            ;; regardless of what it was
            (setf (%vector-raw-bits buffer 4) 0)
            (when verbose
              (format t "Trailer words:(~{~X~^ ~})~%"
                      (loop for i below (floor nbytes n-word-bytes)
                            collect (%vector-raw-bits buffer i))))
            (when split-core
              (write-sequence buffer split-core :end nbytes)
              (finish-output split-core)))
          ;; Sanity test
          (when split-core
            (aver (= (+ core-offset
                        (* page-adjust +backend-page-bytes+)
                        (file-length split-core))
                     (file-length input))))
          ;; Seek back to the PTE pages so they can be copied to the '.o' file
          (file-position input filepos)))

      ;; If we're going to write memory size options and they weren't already
      ;; present, then they will be inserted after the core magic,
      ;; and the remainder of the core header moves over by 5 words.
      (when (and dynamic-space-size
                 (/= (%vector-raw-bits core-header 1) runtime-options-magic))
        (let ((added-words 5))
          (incf (linkage-space-header-ptr linkage-space-info) added-words)
          (incf static-consts-fixup-ofs added-words)
          (incf code-start-fixup-ofs added-words)))
      (unless enable-pie
        ;; This fixup sets the 'address' field of the core directory entry
        ;; for code space. If PIE-enabled, we'll figure it out in the C code
        ;; because space relocation is going to happen no matter what.
        (setf (aref relocs 0) `#(,R_ABS64 ,(ash code-start-fixup-ofs word-shift) 1 0)))
      #+linkage-space
      (let ((where (ash (linkage-space-header-ptr linkage-space-info) word-shift)))
        ;; LINKAGE_SPACE core entry gets a linker fixup to the second ELF symbol
        (vector-push-extend `#(,R_ABS64 ,where 2 0) relocs)
        (read-linkage-cells input linkage-space-info core-offset))
      ;; Map the original core file to memory
      (with-mapped-core (sap core-offset original-total-npages input)
        (let* ((data-spaces
                (delete immobile-text-core-space-id (reverse space-list)
                        :key #'space-id))
               (spacemap (cons sap (sort (copy-list space-list) #'> :key #'space-addr)))
               (new-header (change-dynamic-space-size core-header dynamic-space-size))
               (pte-nbytes (cdar copy-actions)))
          (collect-relocations spacemap relocs enable-pie)
          (with-open-file (output elf-core-pathname
                                  :direction :output :if-exists :supersede
                                  :element-type '(unsigned-byte 8))
            #+x86-64 ; adjust the words of the core header that fill in *ASM-ROUTINE-VECTOR*
            (let ((code-start (bounds-low (space-bounds immobile-text-core-space-id spacemap)))
                  (static-asm-jmpvec-nelts
                   (ash (%vector-raw-bits new-header (1+ static-consts-fixup-ofs))
                        (- n-fixnum-tag-bits))))
              (aver (= (%vector-raw-bits new-header static-consts-fixup-ofs)
                       simple-array-unsigned-byte-64-widetag))
              (loop for i from (+ static-consts-fixup-ofs vector-data-offset)
                    repeat static-asm-jmpvec-nelts
                    do (let ((word (%vector-raw-bits new-header i)))
                         (unless (= word 0)
                           (setf (%vector-raw-bits new-header i) 0)
                           (vector-push-extend
                            `#(,R_ABS64 ,(ash i word-shift) 1 ,(- word code-start))
                            relocs)))))
            (prepare-elf (+ (apply #'+ (mapcar #'space-nbytes-aligned data-spaces))
                            (* (linkage-space-npages linkage-space-info) +backend-page-bytes+)
                            +backend-page-bytes+ ; core header
                            pte-nbytes)
                         relocs output enable-pie)
            ;; This word will be fixed up by the system linker
            (setf (%vector-raw-bits new-header code-start-fixup-ofs)
                  (if enable-pie +code-space-nominal-address+ 0))
            (write-sequence new-header output)
            (force-output output)
            ;; Change SB-C::*COMPILE-FILE-TO-MEMORY-SPACE* to :DYNAMIC
            ;; and SB-C::*COMPILE-TO-MEMORY-SPACE* to :AUTO
            ;; in case the resulting executable needs to compile anything.
            ;; (Call frame info will be missing, but at least it's something.)
            (dolist (item '(("*COMPILE-FILE-TO-MEMORY-SPACE*" . "DYNAMIC")
                            ("*COMPILE-TO-MEMORY-SPACE*" . "DYNAMIC")))
              (destructuring-bind (symbol . value) item
                (awhen (%find-target-symbol (package-id "SB-C") symbol spacemap)
                  (%set-symbol-global-value
                   it (find-target-symbol (package-id "KEYWORD") value spacemap :logical)))))
            #+linkage-space
            (let ((start (sb-sys:sap+ (car spacemap)
                                      (* (linkage-space-data-page linkage-space-info)
                                         +backend-page-bytes+)))
                  (size (* (linkage-space-npages linkage-space-info) +backend-page-bytes+))
                  (code-bounds (space-bounds immobile-text-core-space-id spacemap)))
              ;; Words pointing to text space get the space base address subtracted.
              ;; And we toggle the low bit to signify that it needs correction at startup.
              (dotimes (i (linkage-space-count linkage-space-info))
                (let ((val (sap-ref-word start (ash i word-shift))))
                  (when (in-bounds-p val code-bounds)
                    (setf (sap-ref-word start (ash i word-shift))
                          (logior (- val (bounds-low code-bounds)) 1)))))
              (aver (eql (sb-unix:unix-write (sb-sys:fd-stream-fd output) start 0 size)
                         size)))
            (let ((fd (sb-sys:fd-stream-fd output)))
              (dolist (space data-spaces) ; Copy pages from memory
                (let ((start (space-physaddr space spacemap))
                      (size (space-nbytes-aligned space)))
                  (aver (eql (sb-unix:unix-write fd start 0 size) size)))))
            (when verbose
              (format t "Copying ~d bytes (#x~x) from ptes = ~d PTEs~%"
                      pte-nbytes pte-nbytes (floor pte-nbytes 10)))
            (copy-bytes input output pte-nbytes)) ; Copy PTEs from input
          (let ((core (write-asm-file spacemap linkage-space-info asm-file enable-pie)))
            (format asm-file " .section .rodata~% .p2align 4~%lisp_fixups:~%")
            ;; Sort the hash-table in emit order.
            (dolist (x (sort (%hash-table-alist (core-new-fixups core)) #'< :key #'cdr))
              (output-bignum nil (car x) asm-file))
            (cond
              (t ; (get-space immobile-fixedobj-core-space-id spacemap)
               (format asm-file "~% .section .data
 .globl ~A~%~:*~A:~%" (labelize "alien_linkage_values"))
               (dovector (s (core-alien-linkage-symbols core))
                 (format asm-file " .quad ~a~%" (if (consp s) (car s) s)))
               ;; dark magic to ensure that the elf linkage space is emitted
               (when (force-fntbl-ref-p spacemap)
                 (format asm-file "# this is not an alien symbol~% .quad fntbl~%"))
               )
              (t
               (format asm-file "~% .section .rodata~%")
               (format asm-file " .globl anchor_junk~%")
               (format asm-file "anchor_junk: .quad lseek_largefile, get_timezone, compute_udiv_magic32~%"))))))
      (when (member :linux *features*)
        (format asm-file "~% ~A~%" +noexec-stack-note+)))))

;;; Copy the input core into an ELF section without splitting into code & data.
;;; Also force a linker reference to each C symbol that the Lisp core mentions.
(defun copy-to-elf-obj (input-pathname output-pathname)
  ;; Remove old files
  (ignore-errors (delete-file output-pathname))
  ;; Ensure that all files can be opened
  (with-open-file (input input-pathname :element-type '(unsigned-byte 8))
    (with-open-file (output output-pathname :direction :output
                            :element-type '(unsigned-byte 8) :if-exists :supersede)
      (let* ((core-header (make-array +backend-page-bytes+
                                      :element-type '(unsigned-byte 8)))
             (core-offset (read-core-header input core-header nil))
             (parsed-header (parse-core-header input core-header core-offset))
             (space-list (core-header-space-list parsed-header)))
        ;; Map the core file to memory
        (with-mapped-core (sap core-offset (core-header-total-npages parsed-header) input)
          (let* ((spacemap (cons sap (sort (copy-list space-list) #'> :key #'space-addr)))
                 (core (make-core spacemap
                                  (space-bounds immobile-text-core-space-id spacemap)
                                  (space-bounds immobile-fixedobj-core-space-id spacemap)))
                 (c-symbols (map 'list (lambda (x) (if (consp x) (car x) x))
                                 (core-alien-linkage-symbols core)))
                 (sections `#((:str  ".strtab"         ,+sht-strtab+   0 0 0 1  0)
                              (:sym  ".symtab"         ,+sht-symtab+   0 1 1 8 ,sym-entry-size)
                              ;;             section with the strings -- ^ ^ -- 1+ highest local symbol
                              (:core "lisp.core"       ,+sht-progbits+ 0 0 0 ,core-align 0)
                              (:note ".note.GNU-stack" ,+sht-progbits+ 0 0 0 1  0)))
                 (string-table (string-table (append (map 'list #'second sections)
                                                     c-symbols)))
                 (packed-strings (cdr string-table))
                 (strings-start (+ ehdr-size (* (1+ (length sections)) shdr-size)))
                 (strings-end (+ strings-start (length packed-strings)))
                 (symbols-start (align-up strings-end 8))
                 (symbols-size (* (1+ (length c-symbols)) sym-entry-size))
                 (symbols-end (+ symbols-start symbols-size))
                 (core-start (align-up symbols-end core-align))
                 (core-size (+ (* (1+ (core-header-total-npages parsed-header))
                                  +backend-page-bytes+)
                               (core-header-pte-nbytes parsed-header))))
            (write-elf-header ehdr-size sections output)
            (write-section-headers `((,strings-start . ,(length packed-strings))
                                     (,symbols-start . ,symbols-size)
                                     (,core-start    . ,core-size)
                                     (0 . 0))
                                   sections string-table output)
            (write-sequence packed-strings output)
            ;; Write symbol table
            (file-position output symbols-start)
            (write-sequence (make-elf64-sym 0 0) output)
            (dolist (sym c-symbols)
              (let ((name-ptr (cdr (assoc sym (car string-table)))))
                (write-sequence (make-elf64-sym name-ptr #x10) output)))
            ;; Copy core
            (file-position output core-start)
            (file-position input core-offset)
            (let ((remaining core-size))
              (loop (let ((n (read-sequence core-header input
                                            :end (min +backend-page-bytes+ remaining))))
                      (write-sequence core-header output :end n)
                      (unless (plusp (decf remaining n)) (return))))
              (aver (zerop remaining)))))))))

;; The extra copy of ASM routines, particularly C-calling trampolines, that will reside in text
;; space have to be modified to correctly reference their C functions. They assume that static
;; space is near alien-linkage space, and so they use this form:
;;   xxxx: E8A1F0EFFF  CALL #x50000060 ; alloc
;; which unfortunately means that after relocating to text space, that instruction refers
;; to random garbage, and more unfortunately there is no room to squeeze in an instruction
;; that encodes to 8 bytes.
;; So we have to create an extra jump "somewhere" that indirects through the linkage table
;; but is callable from the text-space code.
;;; I don't feel like programmatically scanning the asm code to determine these.
;;; Hardcoded is good enough (until it isn't)
(defglobal *c-linkage-redirects*
  (mapcar (lambda (x) (cons nil (concatenate 'string #+darwin "_" x)))
          '("switch_to_arena"
            "alloc"
            "alloc_list"
            "listify_rest_arg"
            "make_list"
            "alloc_funinstance"
            "allocation_tracker_counted"
            "allocation_tracker_sized")))

(defun patch-asm-codeblob (core &aux (spacemap (core-spacemap core)))
  (binding* ((static-space (get-space static-core-space-id spacemap))
             (text-space (get-space immobile-text-core-space-id spacemap))
             ((new-code-vaddr new-code) (get-text-space-asm-code-replica text-space spacemap))
             ((old-code-vaddr old-code) (get-static-space-asm-code static-space spacemap))
             (code-offsets-vector
              (%make-lisp-obj (logior (sap-int (space-physaddr text-space spacemap))
                                      other-pointer-lowtag)))
             (header-bytes (ash (code-header-words old-code) word-shift))
             (old-insts-vaddr (+ old-code-vaddr header-bytes))
             (new-insts-vaddr (+ new-code-vaddr header-bytes))
             (inst-buffer (make-array 8 :element-type '(unsigned-byte 8)))
             (code-offsets-vector-size (primitive-object-size code-offsets-vector))
             (c-linkage-vector-vaddr (+ (space-addr text-space) code-offsets-vector-size))
             (c-linkage-vector ; physical
              (%make-lisp-obj (logior (sap-int (sap+ (space-physaddr text-space spacemap)
                                                     code-offsets-vector-size))
                                      other-pointer-lowtag)))
             (alien-ls-start (- (space-addr (or #+immobile-space text-space static-space))
                                alien-linkage-space-size))
             (alien-ls-end (1- (+ alien-ls-start alien-linkage-space-size))))
    (aver (<= (length *c-linkage-redirects*) (length c-linkage-vector)))
    (dolist (x *c-linkage-redirects*)
      (let* ((index (position (cdr x) (core-alien-linkage-symbols core)
                              :test (lambda (a b) (and (stringp b) (string= a b)))))
             (addr (+ alien-ls-start
                      (sb-vm::alien-linkage-index-to-addr index)
                      (- sb-vm:alien-linkage-space-start))))
        (rplaca x addr)))
    (with-pinned-objects (inst-buffer)
      (do ((sap (vector-sap inst-buffer))
           (item-index 0 (1+ item-index))
           (items *c-linkage-redirects* (cdr items)))
          ((null items))
        (let ((disp (+ (- (caar items) *nil-taggedptr*) 8)))
          ;; Each new quasi-linkage-table entry takes 8 bytes to encode.
          (setf (sap-ref-32 sap 0) #x24A4FF41 ; JMP [R12-disp]
                (signed-sap-ref-32 sap 4) disp) ; gets to the addr within the real linkage entry
          (setf (aref c-linkage-vector item-index) (%vector-raw-bits inst-buffer 0)))))

    ;; Produce a model of the instructions. It doesn't really matter whether we scan
    ;; OLD-CODE or NEW-CODE since we're supplying the proper virtual address either way.
    (let* ((skip (ash (code-jump-table-words old-code) word-shift))
           (insts-start (sap+ (code-instructions old-code) skip))
           (insts-len (- (%code-text-size old-code) skip))
           (insts (simple-collect-inst-model insts-start insts-len (+ old-insts-vaddr skip)))
           (textspace-insts-sap (code-instructions new-code)))
      (dolist (inst insts)
        ;; Look for any call to an alien linkage table entry.
        (when (eq (second inst) 'call)
          (let ((operand (third inst)))
            (when (and (integerp operand) (<= alien-ls-start operand alien-ls-end))
              (let* ((index (position operand *c-linkage-redirects* :key #'car))
                     (branch-target (+ c-linkage-vector-vaddr
                                       (ash vector-data-offset word-shift)
                                       ;; each new linkage entry takes up exactly 1 word
                                       (* index n-word-bytes)))
                     (rel-pc (+ skip (+ (car inst) 1))))
                (setf (signed-sap-ref-32 textspace-insts-sap rel-pc)
                      ;; I played with this math until it came out right.
                      (- branch-target (+ new-insts-vaddr rel-pc 4)))))))))))

(defconstant smallvec-elts
  (- (ash gencgc-page-bytes (- word-shift)) vector-data-offset))
(defvar *name-map* nil)
(defun linkage-index-to-name (index spacemap)
  (unless *name-map*
    (let ((sym (find-target-symbol (package-id "SB-VM") "*LINKAGE-NAME-MAP*"
                                   spacemap :physical)))
      (setf *name-map* (translate (symbol-global-value sym) spacemap))))
  (multiple-value-bind (hi lo) (floor index smallvec-elts)
    (let ((inner (translate (svref *name-map* hi) spacemap)))
      (translate (weak-vector-ref inner lo) spacemap))))
#+x86-64
(defun use-indirection-p (index spacemap)
  (let ((name (linkage-index-to-name index spacemap)))
    (if (symbolp name)
        (let ((str (translate (symbol-name name) spacemap)))
          (case (symbol-package-id name)
            (#.(sb-impl::package-id (find-package "CL"))
             ;; Users like to encapsulate this apparently
             (string= str "FIND-PACKAGE"))
            ;; Functions below sometimes get redefined on startup
            (#.(sb-impl::package-id (find-package "SB-BIGNUM"))
             (string= str "MULTIPLY-BIGNUM-AND-FIXNUM"))
            (#.(sb-impl::package-id (find-package "SB-VM"))
             (member str '(sb-vm::simd-reverse32 sb-vm::simd-reverse8
                           sb-vm::simd-nreverse32 sb-vm::simd-nreverse8
                           #+sb-unicode
                           sb-vm::simd-copy-utf8-crlf-to-character-string
                           #+sb-unicode
                           sb-vm::simd-copy-utf8-crlf-to-base-string
                           sb-vm::simd-copy-character-string-to-utf8
                           sb-vm::simd-position32 sb-vm::simd-position32-from-end
                           sb-vm::simd-position8 sb-vm::simd-position8-from-end)
                     :test 'string=)))))))
#+x86-64
(defun bypass-indirection-cells
    (code vaddr core has-immobile-space
     &optional print
     &aux (insts (get-code-instruction-model code vaddr core))
          (spacemap (core-spacemap core))
          (text-bounds (space-bounds immobile-text-core-space-id spacemap))
          (linkage-bounds (core-linkage-bounds core))
          (lisp-linkage-bounds
           (make-bounds (bounds-low linkage-bounds)
                        (+ (bounds-low linkage-bounds)
                           (ash 1 (+ sb-vm:n-linkage-index-bits sb-vm:word-shift)))))
          (indices))
  (declare (simple-vector insts))
  (labels ((linkage-index (ea inst)
             (cond ((and (eq (machine-ea-base ea) :rip) ; RIP+n format
                         has-immobile-space)
                    (let* ((next-pc (+ (range-vaddr (car inst)) (range-bytecount (car inst))))
                           (addr (+ next-pc (machine-ea-disp ea))))
                      (when (in-bounds-p addr lisp-linkage-bounds)
                        (ash (- addr (bounds-low lisp-linkage-bounds)) (- word-shift)))))
                   ((and (eq (machine-ea-base ea) sb-vm:card-table-reg) ; R12-n format
                         (not has-immobile-space))
                    (let ((addr (+ *nil-taggedptr* (machine-ea-disp ea))))
                      (when (and (null (machine-ea-index ea))
                                 (in-bounds-p addr lisp-linkage-bounds))
                        (ash (- addr (bounds-low lisp-linkage-bounds)) (- word-shift))))))))
    (do ((i 0 (1+ i)))
        ((>= i (length insts)))
      (let* ((inst (svref insts i))
             (op (second inst))
             (ea (third inst)))
        (when (and (member op '(call jmp)) (typep ea 'machine-ea))
          (binding* ((linkage-index (linkage-index ea inst) :exit-if-null)
                     (cell (assoc linkage-index indices)))
            (unless cell
              (setq cell (list linkage-index))
              (push cell indices))
            ;; Collect list of insts to replace for the particular linkage-index.
            (push inst (cdr cell)))))))
  ;; Change each linkage table call to instead go directly to the target
  ;; but only if the target uniquely identifies its linkage index within
  ;; this code component for purposes of undoing the optimization.
  (let* ((linkage-cells (linkage-space-cells (core-linkage-space-info core)))
         (indices (coerce indices 'vector))
         (values (map 'vector (lambda (x) (aref linkage-cells (car x)))
                      indices)))
    (dotimes (i (length indices))
      (let ((linkage-index (car (aref indices i))) (value (aref values i)))
        (cond
          ((not (in-bounds-p value text-bounds))
           (if print (format t "Can't patch ~X: outside of text space~%" value)))
          ((> (count value values) 1)
           (if print (format t "Can't patch ~X: not unique~%" value)))
          ((use-indirection-p linkage-index spacemap)
           (if print (format t "Won't direct call ~D~%" linkage-index)))
          (t
           (dolist (inst (cdr (aref indices i)))
             (let* ((range (car inst))
                    (sap (sap+ (sap+ (int-sap (get-lisp-obj-address code))
                                     (- other-pointer-lowtag))
                               (- (range-vaddr range) vaddr)))
                    (next-pc (+ (range-vaddr range) (range-bytecount range)))
                    (disp (the (signed-byte 32) (- value next-pc))))
               (if has-immobile-space
                   (ecase (cadr inst)
                     (call
                      (setf (sap-ref-8 sap 0) #x40 ; add a do-nothing prefix
                            (sap-ref-8 sap 1) #xe8 ; CALL rel32
                            (signed-sap-ref-32 sap 2) disp))
                     (jmp
                      (setf (sap-ref-8 sap 0) #xe9 ; JMP rel32
                            (signed-sap-ref-32 sap 1) (1+ disp)
                            (sap-ref-8 sap 5) #x90))) ; followed by NOP
                   (ecase (cadr inst)
                     (call ; put in 3 redundant prefixes
                      (setf (sap-ref-32 sap 0) #x2e2e2e
                            (sap-ref-8 sap 3) #xe8 ; CALL rel32
                            (signed-sap-ref-32 sap 4) disp))
                     (jmp
                      (setf (sap-ref-32 sap 4) #x001f0f00)
                      ;;                         ^^^^^^ this is a 3-byte NOP
                      (setf (sap-ref-8 sap 0) #xe9 ; JMP rel32
                            (signed-sap-ref-32 sap 1) (+ disp 3)))))))))))))


#+x86-64
(defun redirect-text-space-calls (pathname)
  (with-open-file (stream pathname :element-type '(unsigned-byte 8)
                         :direction :io :if-exists :overwrite)
    (let* ((core-header (make-array +backend-page-bytes+ :element-type '(unsigned-byte 8)))
           (core-offset (read-core-header stream core-header))
           (parsed-header (parse-core-header stream core-header core-offset))
           (space-list (core-header-space-list parsed-header)))
      (with-mapped-core (sap core-offset (core-header-total-npages parsed-header) stream)
        (let* ((spacemap (cons sap (sort (copy-list space-list) #'> :key #'space-addr)))
               (core (make-core spacemap (make-bounds 0 0) (make-bounds 0 0)
                                :linkage-space-info (core-header-linkage-space-info parsed-header)))
               (features (detect-target-features spacemap)))
          (cond
            ((not (member :immobile-space features))
             (let* ((text-space (get-space immobile-text-core-space-id spacemap))
                    (offsets-vector (%make-lisp-obj (logior (sap-int (space-physaddr text-space spacemap))
                                                            lowtag-mask))))
               (assert text-space)
               (patch-asm-codeblob core)
               ;; offset 0 is the offset of the ASM routine codeblob which was already processed.
               (loop for j from 1 below (length offsets-vector)
                  do (let ((vaddr (+ (space-addr text-space) (aref offsets-vector j)))
                           (physobj (%make-lisp-obj
                                     (logior (sap-int (sap+ (space-physaddr text-space spacemap)
                                                            (aref offsets-vector j)))
                                             other-pointer-lowtag))))
                       (bypass-indirection-cells physobj vaddr core nil)))))
            (t
             (let* ((text-space (get-space immobile-text-core-space-id spacemap))
                    (delta (- (translate-ptr (space-addr text-space) spacemap)
                              (space-addr text-space))))
               (walk-target-space (lambda (obj widetag size
                                        &aux (vaddr (- (get-lisp-obj-address obj)
                                                       other-pointer-lowtag delta)))
                                 (declare (ignore widetag size))
                                 (bypass-indirection-cells obj vaddr core t))
                               immobile-text-core-space-id spacemap))))
          (persist-to-file spacemap core-offset stream))))))

(defun split-core (input-pathname asm-pathname &rest args)
  (let ((tmp (flet ((try-directory (dir)
                      (when (and dir (string/= "" dir) (sb-unix:unix-access dir sb-unix:w_ok))
                        dir)))
               (format nil "~a/sbcl~D.tmpcore"
                       (or (try-directory (posix-getenv "TMPDIR"))
                           (try-directory "/tmp")
                           (try-directory (user-homedir-pathname))
                           (error "Can't find a writeable directory for our split core."))
                       (sb-unix:unix-getpid)))))
    (with-open-file (stream input-pathname :element-type '(unsigned-byte 8))
      (let* ((core-header (make-array +backend-page-bytes+ :element-type '(unsigned-byte 8)))
             (core-offset (read-core-header stream core-header)))
        (parse-core-header stream core-header core-offset)))
    (unwind-protect
         (progn
           (ecase *heap-arrangement*
             (:gencgc
              ;; FIXME: should depend on target features but oh well
              #-immobile-space (move-dynamic-code-to-text-space input-pathname tmp)
              #+immobile-space
              ;; input core could be readonly
              (run-program "cp" `("--no-preserve=mode" ,input-pathname ,tmp)
                           :search t))
             (:mark-region-gc
              ;; Assume that the free space in the core hasn't been squashed out yet.
              ;; I'm not sure which of these steps can operate in-place,
              ;; so use an intermediate temp file for the reorg.
              (let ((other-temp (concatenate 'string tmp "0")))
                (reorganize-core input-pathname other-temp)
                (move-dynamic-code-to-text-space other-temp tmp)
                (delete-file other-temp))))
           #+x86-64 (redirect-text-space-calls tmp)
           (apply #'really-split-core tmp asm-pathname args))
      (delete-file tmp))))

(defun cl-user::elfinate (&optional (args (cdr *posix-argv*)))
  (cond ((string= (car args) "split")
         (pop args)
         (let (pie dss)
           (loop (cond ((string= (car args) "--pie")
                        (setq pie t)
                        (pop args))
                       ((string= (car args) "--dynamic-space-size")
                        (pop args)
                        (setq dss (parse-integer (pop args))))
                       (t
                        (return))))
           (destructuring-bind (input asm) args
             (split-core input asm :enable-pie pie :dynamic-space-size dss))))
        ((string= (car args) "copy")
         (apply #'copy-to-elf-obj (cdr args)))
        ((string= (car args) "extract")
         (apply #'move-dynamic-code-to-text-space (cdr args)))
        (t
         (error "Unknown command: ~S" args))))

;; If loaded as a script, do this
(eval-when (:execute)
  (let ((args (cdr *posix-argv*)))
    (when args
      (let ((*print-pretty* nil))
        (format t "Args: ~S~%" args)
        (cl-user::elfinate args)))))
