diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d20b6c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +a86/native/prebuilt/** binary +a86/native/prebuilt/** -merge diff --git a/.github/workflows/build-prebuilt-linux.yml b/.github/workflows/build-prebuilt-linux.yml new file mode 100644 index 0000000..d0c9ac3 --- /dev/null +++ b/.github/workflows/build-prebuilt-linux.yml @@ -0,0 +1,65 @@ +name: Build Prebuilt Linux Binary +on: [push, workflow_dispatch] + +permissions: + contents: write + pull-requests: write + +jobs: + build-prebuilt-linux: + runs-on: ubuntu-22.04 + name: Build Linux prebuilt / Racket 8.18 + + steps: + - name: Checkout + uses: actions/checkout@main + + - name: Install Racket + uses: Bogdanp/setup-racket@v1.15 + with: + architecture: x64 + distribution: full + variant: CS + version: "8.18" + + - name: Install LLVM + uses: ZhongRuoyu/setup-llvm@v0 + with: + llvm-version: 22 + + - name: Install native build dependencies + run: | + sudo apt update + sudo apt install -y libxml2-dev zlib1g-dev libzstd-dev + + - name: Build native JIT library + run: | + make -C a86/native clean all LLVM_CONFIG=llvm-config + + - name: Stage packaged prebuilt library + run: | + racket a86/native/stage-prebuilt.rkt + + - name: Check packaged shared object linkage + run: | + ldd a86/native/prebuilt/unix/x86_64/liba86_jit.so + + - name: Upload packaged shared object + uses: actions/upload-artifact@v4 + with: + name: a86-prebuilt-linux-x86_64 + path: a86/native/prebuilt/unix/x86_64/liba86_jit.so + if-no-files-found: error + + - name: Create pull request for updated shared object + uses: peter-evans/create-pull-request@v8 + with: + branch: update-prebuilt-linux-x86_64 + delete-branch: true + commit-message: Update Linux x86_64 prebuilt JIT library + title: Update Linux x86_64 prebuilt JIT library + body: | + Rebuilds `a86/native/prebuilt/unix/x86_64/liba86_jit.so` + using the `Build Prebuilt Linux Binary` workflow on GitHub Actions. + add-paths: | + a86/native/prebuilt/unix/x86_64/liba86_jit.so diff --git a/.github/workflows/build-prebuilt-macos.yml b/.github/workflows/build-prebuilt-macos.yml new file mode 100644 index 0000000..3887cdb --- /dev/null +++ b/.github/workflows/build-prebuilt-macos.yml @@ -0,0 +1,61 @@ +name: Build Prebuilt macOS Binary +on: [push, workflow_dispatch] + +permissions: + contents: write + pull-requests: write + +jobs: + build-prebuilt-macos: + runs-on: macos-26-intel + name: Build macOS prebuilt / Racket 8.18 + + steps: + - name: Checkout + uses: actions/checkout@main + + - name: Install Racket + uses: Bogdanp/setup-racket@v1.15 + with: + architecture: 'x64' + distribution: 'full' + variant: 'CS' + version: '8.18' + + - name: Install native build dependencies + run: | + brew install llvm@22 zstd z3 + + - name: Build native JIT library + run: | + export PATH="/usr/local/opt/llvm@22/bin:$PATH" + make -C a86/native clean all LLVM_CONFIG=llvm-config + + - name: Stage packaged prebuilt library + run: | + export PATH="/usr/local/opt/llvm@22/bin:$PATH" + racket a86/native/stage-prebuilt.rkt + + - name: Check packaged dylib linkage + run: | + otool -L a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib + + - name: Upload packaged dylib + uses: actions/upload-artifact@v4 + with: + name: a86-prebuilt-macos-x86_64 + path: a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib + if-no-files-found: error + + - name: Create pull request for updated dylib + uses: peter-evans/create-pull-request@v8 + with: + branch: update-prebuilt-macos-x86_64 + delete-branch: true + commit-message: Update macOS x86_64 prebuilt JIT library + title: Update macOS x86_64 prebuilt JIT library + body: | + Rebuilds `a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib` + using the `Build Prebuilt macOS Binary` workflow on GitHub Actions. + add-paths: | + a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4f1daa9..8c62cb0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,16 +17,17 @@ jobs: uses: actions/checkout@main - name: Install Racket - uses: Bogdanp/setup-racket@v1.14 + uses: Bogdanp/setup-racket@v1.15 with: architecture: 'x64' distribution: 'full' variant: ${{ matrix.racket-variant }} version: ${{ matrix.racket-version }} - - name: Install clang - run: | - sudo apt install -y clang libssl-dev + - name: Install LLVM + uses: ZhongRuoyu/setup-llvm@v0 + with: + llvm-version: 22 - name: Install pandoc run: | diff --git a/.github/workflows/prebuilt-macos.yml b/.github/workflows/prebuilt-macos.yml new file mode 100644 index 0000000..66132bf --- /dev/null +++ b/.github/workflows/prebuilt-macos.yml @@ -0,0 +1,32 @@ +name: Test Prebuilt macOS Binary +on: [push, workflow_dispatch] + +jobs: + test-prebuilt-macos: + runs-on: macos-26-intel + name: macOS prebuilt / Racket 8.18 + + steps: + - name: Checkout + uses: actions/checkout@main + + - name: Install Racket + uses: Bogdanp/setup-racket@v1.15 + with: + architecture: 'x64' + distribution: 'full' + variant: 'CS' + version: '8.18' + + - name: Install a86 package + run: | + raco pkg install --no-docs ../a86/ + + - name: Verify prebuilt library was installed + run: | + test -f a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib + test -f a86/native/lib/liba86_jit.dylib + + - name: Run tests + run: | + raco test -p a86 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 614fc88..ed500b9 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -4,10 +4,11 @@ on: [push, workflow_dispatch] jobs: build-and-test: strategy: + fail-fast: false matrix: os: [ubuntu-22.04, ubuntu-24.04] racket-variant: ['CS'] - racket-version: ['8.6', '8.10', '8.14', '8.18', 'stable', 'current'] + racket-version: ['8.6', '8.10', '8.14', '8.18', '9.1', 'stable', 'current'] runs-on: ${{ matrix.os }} name: OS ${{ matrix.os }} / Racket ${{ matrix.racket-version }} @@ -17,42 +18,35 @@ jobs: uses: actions/checkout@main - name: Install Racket - uses: Bogdanp/setup-racket@v1.14 + uses: Bogdanp/setup-racket@v1.15 with: architecture: 'x64' distribution: 'full' variant: ${{ matrix.racket-variant }} version: ${{ matrix.racket-version }} - - name: Install clang - run: | - sudo apt install -y clang libssl-dev - - name: Version info run: | clang --version gcc --version - - name: Cache Racket packages - uses: actions/cache@v4 - with: - path: | - ~/.racket - ~/.cache/racket - ~/.local/share/racket - ~/Library/Caches/Racket - key: racket-${{ matrix.racket-variant }}-${{ matrix.racket-version }}-${{ matrix.os }} - - name: Install a86 package run: | raco pkg install --no-docs ../a86/ - name: Install langs package run: | - # This *should* use the locally installed a86 - raco pkg install --auto 'https://github.com/cmsc430/langs.git?#main' + git clone --branch "${{ github.ref_name }}" --single-branch https://github.com/cmsc430/langs.git ../langs + raco pkg install --auto --no-docs ../langs/ + + - name: Install libssl + run: | + sudo apt install -y libssl-dev + + - name: Run a86 tests + run: | + raco test -p a86 - - name: Run tests + - name: Run langs tests run: | - xvfb-run raco test -p a86 xvfb-run raco test -p langs diff --git a/.gitignore b/.gitignore index 9d75c0d..09dc302 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,4 @@ Mkfile.old dkms.conf # debug information files -*.dwo \ No newline at end of file +*.dwo diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ae8517 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# a86 + +`a86` is a Racket package for composing, printing, interpreting, and running x86-64 assembly programs from Racket. + +## Installation + +Install from GitHub with: + +```bash +raco pkg install https://github.com/cmsc430/a86.git +``` + +## Notes + +This package includes prebuilt native JIT libraries for Linux and macOS on `x86_64`, so most installs do not need a local native build toolchain. + +Documentation is published at: + +https://cmsc430.github.io/a86/ diff --git a/a86/README.md b/a86/README.md new file mode 100644 index 0000000..98d3f98 --- /dev/null +++ b/a86/README.md @@ -0,0 +1,32 @@ +# Developer Notes + +This directory contains the `a86` collection, including the native JIT support under `native/`. + +## Building Native Libraries Locally + +Local native builds are only needed when you want to rebuild or restage the JIT libraries instead of using the packaged prebuilt artifacts. + +You will need: + +- `x86_64` Racket +- `make` or `gmake` +- a C++17 compiler (`clang++` by default in `native/Makefile`) +- LLVM with `llvm-config` available on `PATH` + +The GitHub workflows currently build against LLVM 22. + +From [`a86/native`](/Users/dvanhorn/git/a86/a86/native), run: + +```bash +make +``` + +This produces the shared library in `a86/native/lib/`. + +If you want to refresh the packaged prebuilt layout after building, run: + +```bash +racket a86/native/stage-prebuilt.rkt +``` + +At package install time, `a86/pre-install.rkt` first tries to copy a matching prebuilt library and only falls back to `make` when no packaged binary is available for the target OS and architecture. diff --git a/a86/check-x86.rkt b/a86/check-x86.rkt deleted file mode 100644 index 743334d..0000000 --- a/a86/check-x86.rkt +++ /dev/null @@ -1,9 +0,0 @@ -#lang racket -(provide pre-installer) -(define (pre-installer _) - (unless (eq? 'x86_64 (system-type 'arch)) - (error 'a86-installer - "This library requires an x86_64 installation of Racket; yours is a ~a (~a)." - (system-type 'arch) - (system-type 'os)))) - diff --git a/a86/info.rkt b/a86/info.rkt index 0f55389..679e8eb 100644 --- a/a86/info.rkt +++ b/a86/info.rkt @@ -4,5 +4,7 @@ (define deps (list "base" "rackunit" "redex-lib" "redex-gui-lib")) (define scribblings '(("scribblings/a86.scrbl"))) (define test-omit-paths '("scribblings/" - "test/expressions.rkt")) -(define pre-install-collection "check-x86.rkt") + "test/expressions.rkt" + "test/sections.rkt" + "stepper.rkt")) +(define pre-install-collection "pre-install.rkt") diff --git a/a86/interp.rkt b/a86/interp.rkt index b4a5778..960eb5e 100644 --- a/a86/interp.rkt +++ b/a86/interp.rkt @@ -1,328 +1,307 @@ #lang racket -(provide/contract - [current-objs (parameter/c (listof path-string?))] - [asm-interp ;(-> (listof instruction?) any/c) - (->* () #:rest (or/c (listof instruction?) - (listof (listof instruction?))) - any/c)] - [asm-interp/io (-> (listof instruction?) string? any/c)]) - -(define-logger a86) - -(require "printer.rkt" "ast.rkt" "callback.rkt" "check-assembler.rkt" - (rename-in ffi/unsafe [-> _->])) -(require (submod "printer.rkt" private)) - -;; Check clang availability when required to fail fast. -(check-clang-available) - -;; Bail out if we're not on an x86_64 Racket. -(unless (eq? 'x86_64 (system-type 'arch)) - (error 'a86 - "This library requires x86_64 Racket, but yours is ~a (~a)." - (system-type 'arch) - (system-type 'os))) - -(define *debug*? - (let ((r (getenv "PLTSTDERR"))) - (and r - (string=? r "info@a86")))) - -;; Assembly code is linked with object files in this parameter -(define current-objs - (make-parameter '())) - -;; Asm ... -> Value -;; Interpret (by assemblying, linking, and loading) x86-64 code -;; Assume: entry point is "entry" -(define (asm-interp . is) - (asm-interp/io is #f)) - -(define fopen - (get-ffi-obj "fopen" (ffi-lib #f) (_fun _path _string/utf-8 _-> _pointer))) -(define fflush - (get-ffi-obj "fflush" (ffi-lib #f) (_fun _pointer _-> _void))) - -(define fclose - (get-ffi-obj "fclose" (ffi-lib #f) (_fun _pointer _-> _void))) +(provide + (contract-out + [struct extern + ([name symbol?] + [value extern-value?] + [ctype ctype?])] + [current-jit (parameter/c jit?)] + [current-externs (parameter/c extern-list/c)] + [current-objects (parameter/c (listof path-string?))] + [reset-jit! (-> void?)] + [asm-load + (->* ((listof instruction?)) + (#:externs (listof extern?) + #:objects (listof path-string?) + #:jit (or/c #f jit?)) + asm-program?)] + [asm-call + (->* (asm-program? symbol?) + () + #:rest (listof machine-word?) + integer?)] + [asm-unload + (-> asm-program? void?)] + [call-with-asm-loaded + (->* ((listof instruction?) (-> asm-program? any/c)) + (#:externs (listof extern?) + #:objects (listof path-string?) + #:jit (or/c #f jit?)) + any/c)] + [asm-interp + (->* () #:rest (or/c (listof instruction?) (listof (listof instruction?))) any/c)] + + [asm-interp/io + (->* () #:rest (or/c (*list/c instruction? string?) (*list/c (listof instruction?) string?)) any/c)])) + + +(require (except-in ffi/unsafe ->) + "ast.rkt" + "jit.rkt" + "printer.rkt" + (submod "printer.rkt" private)) + +(define extern-list/c + (flat-named-contract + 'extern-list/c + (λ (xs) + (and (list? xs) + (andmap extern? xs) + (not (check-duplicates xs #:key extern-name)))))) + +(define (extern-value? x) + (or (procedure? x) (cpointer? x))) + +(define (jit? x) + #t) ; FIXME + +(define (machine-word? x) + (or (exact-integer? x) (cpointer? x))) + +(define A86_EXTERN_FUNCTION 0) +(define A86_EXTERN_GLOBAL 1) + +(define (jit-trace-enabled?) + (define v (getenv "A86_JIT_TRACE")) + (and v (not (member v '("" "0" "false" "FALSE" "False"))))) + +(define (jit-trace fmt . args) + (when (jit-trace-enabled?) + (parameterize ([current-output-port (current-error-port)]) + (apply printf (string-append "a86-jit: " fmt "\n") args) + (flush-output)))) + +(define (trace-extern-procedure name proc) + (if (not (jit-trace-enabled?)) + proc + (lambda args + (jit-trace "extern ~a args=~s" name args) + (with-handlers ([exn:fail? + (lambda (e) + (jit-trace "extern ~a raised: ~a" + name + (exn-message e)) + (raise e))]) + (define vs + (call-with-values + (lambda () (apply proc args)) + list)) + (jit-trace "extern ~a result=~s" name vs) + (apply values vs))))) + +;; ------------------------------------------------------------ +;; current JIT environment + +(define current-jit + (make-parameter (make-jit))) + +(define current-externs + (make-parameter '())) -;; WARNING: The heap is re-used, so make sure you're done with it -;; before calling asm-interp again -(define *heap* - ; IMPROVE ME: hard-coded heap size - (malloc _int64 20000 'raw)) +(define jit-no-unload? + (let ([v (getenv "A86_JIT_NO_UNLOAD")]) + (and v (not (member (string-downcase v) '("0" "false" "no" "")))))) +(define current-objects + (make-parameter '())) -;; Integer64 -> String -(define (int64->binary-string n) - (format "#b~a" - (~r n #:base 2 #:min-width 64 #:pad-string "0"))) +(define (reset-jit!) + (define old (current-jit)) + (when old + (jit-close old)) + (current-jit (make-jit))) -;; Integer64 -> String -(define (int64->octal-string n) - (format "#o~a" - (~r n #:base 8 #:min-width 22 #:pad-string "0"))) +;; ------------------------------------------------------------ +;; higher-level extern representations -;; Integer64 -(define (int64->hex-string n) - (format "#x~a" - (~r n #:base 16 #:min-width 16 #:pad-string "0"))) +(struct extern (name value ctype) #:transparent) -(define (show-state . regs) - (format "\n~a" - (map (lambda (r v) - (format "(~a ~a)" r (int64->hex-string v))) - '(rax rbx rcx rdx rbp rsp rsi rdi - r8 r9 r10 r11 r12 r13 r14 r15 instr flags) - regs))) +(struct cached-callback (name ctype wrapper fptr) #:transparent) -;; Asm ... String -> (cons Value String) -;; Like asm-interp, but uses given string for input and returns -;; result with string output -(define (asm-interp/io a input) +;; ------------------------------------------------------------ +;; loaded program wrapper +;; +;; `ptr` is the raw native program handle. +;; `keepalive` holds any callback pointers so they are not GC'd +;; while the program is live. +;; `jit` is the owning JIT when the program was loaded into an isolated +;; one-shot JIT. `owned?` controls whether unload should close that JIT. - (log-a86-info (~v a)) +(struct asm-program (ptr keepalive jit owned?) #:transparent #:mutable) - (define t.s (make-temporary-file "clang-~a.s")) - (define t.o (path-replace-extension t.s #".o")) - (define t.so (path-replace-extension t.s #".so")) - (define t.in (path-replace-extension t.s #".in")) - (define t.out (path-replace-extension t.s #".out")) +;; ------------------------------------------------------------ +;; helpers - ;; If the initial label is declared global, jump to that, otherwise - ;; generate an initial label at first instruction and jump there +(define (program->asm-string p) + (with-output-to-string + (λ () + (parameterize ([current-shared? #t]) + (asm-display p))))) +(define (resolve-object-path p) + (define path + (cond + [(path? p) p] + [(string? p) (string->path p)])) + (path->string + (simplify-path + (path->complete-path path (current-directory))))) + +;; Hold callback trampolines for the life of the process so long-running test +;; runs do not depend on per-load callback allocation or GC timing. +(define callback-cache (make-hasheq)) + +(define (cached-function-ptr name value ctype) + (define entries (hash-ref callback-cache value '())) + (define hit + (for/or ([entry entries]) + (and (eq? name (cached-callback-name entry)) + (equal? ctype (cached-callback-ctype entry)) + entry))) + (cond + [hit + (cached-callback-fptr hit)] + [else + (define wrapper (trace-extern-procedure name value)) + (define fptr (function-ptr wrapper ctype)) + (hash-set! callback-cache + value + (cons (cached-callback name ctype wrapper fptr) entries)) + fptr])) + +(define (prepare-externs externs) + (define keepalive '()) + (define bindings + (for/vector ([x externs]) + (match-define (extern name value ctype) x) + (cond + [(procedure? value) + (define fptr (cached-function-ptr name value ctype)) + (set! keepalive (cons fptr keepalive)) + (make-jit-extern-binding + (symbol->string name) + A86_EXTERN_FUNCTION + fptr)] + + [(cpointer? value) + (make-jit-extern-binding + (symbol->string name) + A86_EXTERN_GLOBAL + value)]))) + + (values bindings keepalive)) + +(define (prepare-object-files objs) + (for/vector ([p objs]) + (resolve-object-path p))) + +(define (arg->u64 x) + (cond + [(exact-integer? x) + ;; jit.rkt passes _uint64 arguments, so normalize negatives mod 2^64 + (modulo x (arithmetic-shift 1 64))] + [(cpointer? x) + (cast x _pointer _uintptr)])) + +;; ------------------------------------------------------------ +;; public API + +(define (asm-load prog + #:externs [externs (current-externs)] + #:objects [objs (current-objects)] + #:jit [jit #f]) + (define owned? (not jit)) + (define use-jit (or jit (make-jit))) + (define asm-str (program->asm-string prog)) + (define-values (ext-vec keepalive) (prepare-externs externs)) + (define obj-vec (prepare-object-files objs)) + (jit-trace "load externs=~s objects=~s" + (map extern-name externs) + objs) + (define p (jit-load use-jit asm-str obj-vec ext-vec)) + (asm-program p keepalive use-jit owned?)) + +(define (asm-call p label . args) + (define raw (asm-program-ptr p)) + (unless raw + (error 'asm-call "program has already been unloaded")) + (define argv + (list->vector (map arg->u64 args))) + (with-handlers ([exn:fail? + (λ (e) + (jit-trace "call label=~s args=~s failed: ~a" + label + args + (exn-message e)) + (raise e))]) + (jit-call (asm-program-ptr p) label argv))) + +(define (asm-unload p) + (define raw (asm-program-ptr p)) + (when raw + (define maybe-jit (asm-program-jit p)) + (define owned? (asm-program-owned? p)) + (if jit-no-unload? + (jit-trace "skip unload due to A86_JIT_NO_UNLOAD") + (begin + (if (and owned? maybe-jit) + ;; For the default one-program JIT path, avoid explicitly + ;; removing ORC resources before tearing down the whole JIT. + (jit-close maybe-jit) + (jit-unload raw)) + (set-asm-program-ptr! p #f) + (set-asm-program-jit! p #f))))) + +(define (call-with-asm-loaded prog f + #:externs [externs (current-externs)] + #:objects [objs (current-objects)] + #:jit [jit #f]) + (define p (asm-load prog + #:externs externs + #:objects objs + #:jit jit)) + (dynamic-wind + void + (λ () (f p)) + (λ () (asm-unload p)))) + +(define (asm-interp . asm) + (define-values (init-label code) (asm-fixup asm)) + (call-with-asm-loaded code + (λ (p) (asm-call p init-label)))) + +(define (asm-interp/io . asm+in) + (match asm+in + [(list asm ... in-str) + (define in (open-input-string in-str)) + (define out (open-output-string)) + (parameterize ([current-input-port in] + [current-output-port out]) + (define r (apply asm-interp asm)) + (begin0 (cons r (get-output-string out)) + (close-output-port out) + (close-input-port in)))])) + + +;; (listof (or/c instruction? (listof instruction?))) -> (listof instruction?) +(define (asm-fixup asm) + (define a (apply seq asm)) (define init-label (match (findf Label? a) [(Label ($ l)) l] [_ #f])) - (define global? (and init-label (ormap (match-lambda - [(Global g) (eq? g init-label)] + [(Global ($ g)) (eq? g init-label)] [_ #f]) a))) - - (define a* - (cond - [(and init-label global?) (apply prog a)] - [else (let ((i (symbol->label (gensym 'init)))) - (set! init-label i) - (apply prog - (Global i) - (Label i) - a))])) - - (with-output-to-file t.s - #:exists 'truncate - (λ () - (parameterize ((current-shared? #t)) - (asm-display (if *debug*? - (debug-transform a*) - a*))))) - - (clang t.s t.o) - (ld t.o t.so) - - (define libt.so (ffi-lib t.so)) - - - (define entry - (get-ffi-obj init-label libt.so (_fun _pointer _-> _int64))) - - ;; install our own `error_handler` procedure to prevent `exit` calls - ;; from interpreted code bringing down the parent process. All of - ;; these hooks into the runtime need a better API and documentation, - ;; but this is a rough hack to make Extort work for now. - (when (ffi-obj-ref "error_handler" libt.so (thunk #f)) - (set-ffi-obj! "error_handler" libt.so _pointer - (function-ptr (λ () (raise 'err)) (_fun _-> _void)))) - - (when *debug*? - (define log (ffi-obj-ref log-label libt.so (thunk #f))) - (when log - (set-ffi-obj! log-label libt.so _pointer - (function-ptr - (λ () (log-a86-info - (apply show-state - (build-list 18 (lambda (i) (ptr-ref log _int64 (add1 i))))))) - (_fun _-> _void))))) - - (define has-heap? #f) - - (when (ffi-obj-ref "heap" libt.so (thunk #f)) - (set! has-heap? #t) - - ;; This is a GC-enabled run-time so set from, to, and types space - (when (ffi-obj-ref "from" libt.so (thunk #f)) - ;; FIXME: leaks types memory - (set-ffi-obj! "from" libt.so _pointer *heap*) - (set-ffi-obj! "to" libt.so _pointer (ptr-add *heap* 10000 _int64)) - (set-ffi-obj! "types" libt.so _pointer (malloc _int32 10000)))) - - (delete-file t.s) - (delete-file t.o) - (delete-file t.so) - (if input - (let () - (unless (and (ffi-obj-ref "in" libt.so (thunk #f)) - (ffi-obj-ref "out" libt.so (thunk #f))) - (error "asm-interp/io: running in IO mode without IO linkage")) - - (with-output-to-file t.in #:exists 'truncate - (thunk (display input))) - - (define current-in - (make-c-parameter "in" libt.so _pointer)) - (define current-out - (make-c-parameter "out" libt.so _pointer)) - - (current-in (fopen t.in "r")) - (current-out (fopen t.out "w")) - - (define result - (with-handlers ((symbol? identity)) - (guard-foreign-escape - (entry *heap*)))) - - (fflush (current-out)) - (fclose (current-in)) - (fclose (current-out)) - - (define output (file->string t.out)) - (delete-file t.in) - (delete-file t.out) - (cons result output)) - - (with-handlers ((symbol? identity)) - (guard-foreign-escape - (entry *heap*))))) - - -(define (string-splice xs) - (apply string-append - (add-between (map (lambda (s) (string-append "\"" s "\"")) xs) - " "))) - -;;; Utilities for calling clang and linker with informative error messages - -(struct exn:clang exn:fail:user ()) -(define assembly-error-msg - (string-append - "assembly error: make sure to use `prog` to construct an assembly program\n" - "if you did and still get this error; please share with course staff.")) - -(define (clang:error msg) - (raise (exn:clang (format "~a\n\n~a" assembly-error-msg msg) - (current-continuation-marks)))) - -;; run clang on t.s to create t.o -(define (clang t.s t.o) - (define err-port (open-output-string)) - (define fmt (if (eq? (system-type 'os) 'macosx) 'macho64 'elf64)) - (define prefix - (if (eq? (system-type 'os) 'macosx) - "arch -x86_64" - "")) - - (unless (parameterize ((current-error-port err-port)) - (system (format "~a clang -c ~a -o ~a" prefix t.s t.o))) - (clang:error (get-output-string err-port)))) - -(struct exn:ld exn:fail:user ()) -(define (ld:error msg) - (raise (exn:ld (format "link error: ~a" msg) - (current-continuation-marks)))) - -(define (ld:undef-symbol s) - (ld:error - (string-append - (format "symbol ~a not defined in linked objects: ~a\n" s (current-objs)) - "use `current-objs` to link in object containing symbol definition."))) - -;; link together t.o with current-objs to create shared t.so -(define (ld t.o t.so) - (define err-port (open-output-string)) - (define objs (string-splice (current-objs))) - (define -z-defs-maybe - (if (eq? (system-type 'os) 'macosx) - "" - "-z defs ")) - (unless (parameterize ((current-error-port err-port)) - (system (format "gcc ~a-v -shared ~a ~a -o ~a" - -z-defs-maybe - t.o objs t.so))) - (define err-msg - (get-output-string err-port)) - (match (or (regexp-match #rx"Undefined.*\"(.*)\"" err-msg) ; mac - (regexp-match #rx"undefined reference to `(.*)'" err-msg)) ; linux - [(list _ symbol) (ld:undef-symbol symbol)] - [_ (ld:error (format "unknown link error.\n\n~a" err-msg))]))) - - - -;; Debugging facilities - -(define log-label (symbol->label (gensym 'log))) - -(define (Log i) - (seq (save-registers) - (Pushf) - (Mov 'rax i) - (Mov (Mem log-label (* 8 17)) 'rax) - (Mov 'rax (Mem 'rsp 0)) - (Mov (Mem log-label (* 8 18)) 'rax) - (Call (Mem log-label)) - (Popf) - (restore-registers))) - -(define (instrument is) - (for/fold ([ls '()] - #:result (reverse ls)) - ([idx (in-naturals)] - [ins (in-list is)]) - (if (serious-instruction? ins) - (seq ins (reverse (Log idx)) ls) - (seq ins ls)))) - -(define (serious-instruction? ins) - (match ins - [(Label _) #f] - [(Global _) #f] - [(? Comment?) #f] - [_ #t])) - -(define (debug-transform is) - (seq (instrument is) - ;; End of user program - (Data) - (Global log-label) - (Label log-label) - (Dq 0) ; callback placeholder - (static-alloc-registers) - (Dq 0) ; index of instruction - (Dq 0) ; flags - )) - -(define registers - '(rax rbx rcx rdx rbp rsp rsi rdi - r8 r9 r10 r11 r12 r13 r14 r15)) - -(define (static-alloc-registers) - (apply seq - (map (λ (r) (seq (Dq 0) (% (~a r)))) - registers))) - -(define (save-registers) - (apply seq - (map (λ (r i) (seq (Mov (Mem log-label (* 8 i)) r))) - registers - (build-list (length registers) add1)))) - -(define (restore-registers) - (apply seq - (map (λ (r i) (seq (Mov r (Mem log-label (* 8 i))))) - registers - (build-list (length registers) add1)))) + (cond + [(and init-label global?) (values init-label (apply prog a))] + [else (let ((i (symbol->label (gensym 'init)))) + (values i + (apply prog + (Global i) + (Label i) + a)))])) diff --git a/a86/jit.rkt b/a86/jit.rkt new file mode 100644 index 0000000..901617e --- /dev/null +++ b/a86/jit.rkt @@ -0,0 +1,216 @@ +#lang racket + +(provide make-jit + jit-close + jit-load + jit-call + jit-unload + make-jit-extern-binding) + +(require ffi/unsafe + ffi/unsafe/define + racket/runtime-path + "callback.rkt") + +;; ---------------------------------------- +;; Native interface + +(define-runtime-path here ".") + +(define (native-library-name) + (format "liba86_jit~a" (system-type 'so-suffix))) + +(define (native-library-candidates) + (list (build-path here + "native" + "prebuilt" + (symbol->string (system-type 'os)) + (symbol->string (system-type 'arch)) + (native-library-name)) + (build-path here + "native" + "lib" + (native-library-name)))) + +(define (native-library-path) + (or (findf file-exists? (native-library-candidates)) + (car (reverse (native-library-candidates))))) + +(define liba86 + (ffi-lib (native-library-path))) + +(define-ffi-definer define-a86 liba86) + +;; opaque types +(define-cpointer-type _a86_jit_t) +(define-cpointer-type _a86_program_t) + +;; result struct +(define-cstruct _a86_call_result + ([ok _int] + [value _int64] + [error_message _pointer])) + +;; extern binding +(define _a86_extern_kind_t _int) + +(struct jit-extern-binding (name kind value) #:transparent) + +;; native functions +(define-a86 a86_jit_create + (_fun -> _a86_jit_t)) + +(define-a86 a86_jit_destroy + (_fun _a86_jit_t -> _void)) + +(define-a86 a86_jit_last_error + (_fun _a86_jit_t -> _pointer)) + +(define-a86 a86_jit_load + (_fun _a86_jit_t + _pointer + _pointer _int ; object files + _pointer ; extern names + _pointer ; extern kinds + _pointer ; extern values + _int + -> _a86_program_t)) + +(define-a86 a86_program_call + (_fun _a86_program_t + _pointer + _pointer _int + -> _a86_call_result)) + +(define-a86 a86_program_unload + (_fun _a86_program_t -> _void)) + +;; ---------------------------------------- +;; helpers + +(define (decode-error-pointer p) + (if (ptr-equal? p #f) + "unknown error" + (cast p _pointer _string/utf-8))) + +(define (jit-error jit who fallback) + (define p (a86_jit_last_error jit)) + (error who + (if (ptr-equal? p #f) + fallback + (decode-error-pointer p)))) + +(define (vector->raw-c-array vec type) + (define n (vector-length vec)) + (if (zero? n) + #f + (let ([ptr (malloc type n 'raw)]) + (for ([i (in-range n)]) + (ptr-set! ptr type i (vector-ref vec i))) + ptr))) + +(define (copy-cstring s) + (define bs (string->bytes/utf-8 s)) + (define n (add1 (bytes-length bs))) + (define ptr (malloc _byte n 'raw)) + (for ([i (in-range (bytes-length bs))]) + (ptr-set! ptr _byte i (bytes-ref bs i))) + (ptr-set! ptr _byte (sub1 n) 0) + ptr) + +(define (string-vector->raw-c-array vec) + (define n (vector-length vec)) + (if (zero? n) + (values #f '()) + (let ([ptr (malloc _pointer n 'raw)]) + (define keepalive '()) + (for ([i (in-range n)]) + (define str-ptr (copy-cstring (vector-ref vec i))) + (set! keepalive (cons str-ptr keepalive)) + (ptr-set! ptr _pointer i str-ptr)) + (values ptr keepalive)))) + +(define (extern-vector->c-arrays vec) + (define n (vector-length vec)) + (if (zero? n) + (values #f #f #f '()) + (let ([names-ptr (malloc _pointer n 'raw)] + [kinds-ptr (malloc _a86_extern_kind_t n 'raw)] + [values-ptr (malloc _pointer n 'raw)]) + (define keepalive '()) + (for ([i (in-range n)]) + (define binding (vector-ref vec i)) + (define name-ptr (copy-cstring (jit-extern-binding-name binding))) + (set! keepalive (cons name-ptr keepalive)) + (ptr-set! names-ptr _pointer i name-ptr) + (ptr-set! kinds-ptr _a86_extern_kind_t i (jit-extern-binding-kind binding)) + (ptr-set! values-ptr _pointer i (jit-extern-binding-value binding))) + (values names-ptr kinds-ptr values-ptr keepalive)))) + +(define (free-pointers ptrs) + (for ([ptr (in-list ptrs)] + #:when ptr) + (free ptr))) + +;; ---------------------------------------- +;; exported constructors/helpers + +(define (make-jit-extern-binding name kind value) + (jit-extern-binding name kind value)) + +;; ---------------------------------------- +;; exported operations + +(define (make-jit) + (or (a86_jit_create) + (error 'make-jit "failed to create JIT"))) + +(define (jit-close jit) + (a86_jit_destroy jit)) + +(define (jit-load jit code obj-vec ext-vec) + (define code-ptr (copy-cstring code)) + (define-values (obj-ptr obj-keepalive) + (string-vector->raw-c-array obj-vec)) + (define-values (ext-names-ptr ext-kinds-ptr ext-values-ptr ext-keepalive) + (extern-vector->c-arrays ext-vec)) + (define transient-ptrs + (append (list code-ptr obj-ptr ext-names-ptr ext-kinds-ptr ext-values-ptr) + obj-keepalive + ext-keepalive)) + (define p + (dynamic-wind + void + (λ () + (a86_jit_load jit + code-ptr + obj-ptr (vector-length obj-vec) + ext-names-ptr + ext-kinds-ptr + ext-values-ptr + (vector-length ext-vec))) + (λ () + (free-pointers transient-ptrs)))) + (unless p + (jit-error jit 'jit-load "failed to load program")) + p) + +(define (jit-call p label args) + (define argc (vector-length args)) + (define label-ptr (copy-cstring (symbol->string label))) + (define argv (vector->raw-c-array args _uint64)) + (define r + (dynamic-wind + void + (λ () + (guard-foreign-escape + (a86_program_call p label-ptr argv argc))) + (λ () + (free-pointers (list label-ptr argv))))) + (if (= 1 (a86_call_result-ok r)) + (a86_call_result-value r) + (error 'jit-call + (decode-error-pointer (a86_call_result-error_message r))))) + +(define (jit-unload p) + (a86_program_unload p)) diff --git a/a86/native/Makefile b/a86/native/Makefile new file mode 100644 index 0000000..df5c254 --- /dev/null +++ b/a86/native/Makefile @@ -0,0 +1,50 @@ +CXX := clang++ +LLVM_CONFIG ?= llvm-config + +SRC_DIR := src +INC_DIR := include +BUILD_DIR := build +LIB_DIR := lib + +UNAME_S := $(shell uname -s) +SO_SUFFIX := $(shell racket -e '(display (system-type (quote so-suffix)))' 2>/dev/null || echo .so) + +ifeq ($(UNAME_S),Darwin) + SHARED_LDFLAGS := -dynamiclib + LLVM_LINK_MODE := --link-static + SHARED_LDFLAGS += -Wl,-dead_strip -Wl,-dead_strip_dylibs + LLVM_SYSTEM_LIBS := /usr/local/lib/libzstd.a -lz -lxml2 +else + SHARED_LDFLAGS := -shared + LLVM_LINK_MODE := --link-static + LLVM_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --system-libs $(LLVM_LINK_MODE)) +endif + +LIB_NAME := liba86_jit$(SO_SUFFIX) +LIB_PATH := $(LIB_DIR)/$(LIB_NAME) + +SRCS := $(SRC_DIR)/a86_jit.cpp +OBJS := $(BUILD_DIR)/a86_jit.o + +CXXFLAGS := -std=c++17 -O2 -g -fPIC -I$(INC_DIR) $(shell $(LLVM_CONFIG) --cxxflags) +LLVM_COMPONENTS := orcjit executionengine runtimedyld native mcparser asmparser mc object targetparser support core +LDFLAGS := $(SHARED_LDFLAGS) \ + $(shell $(LLVM_CONFIG) --ldflags) \ + $(shell $(LLVM_CONFIG) --libs $(LLVM_COMPONENTS) $(LLVM_LINK_MODE)) \ + $(LLVM_SYSTEM_LIBS) + +.PHONY: all clean dirs + +all: $(LIB_PATH) + +dirs: + mkdir -p $(BUILD_DIR) $(LIB_DIR) + +$(BUILD_DIR)/a86_jit.o: $(SRC_DIR)/a86_jit.cpp $(INC_DIR)/a86_jit.h | dirs + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(LIB_PATH): $(OBJS) | dirs + $(CXX) $(OBJS) $(LDFLAGS) -o $@ + +clean: + rm -rf $(BUILD_DIR) $(LIB_DIR) diff --git a/a86/native/include/a86_jit.h b/a86/native/include/a86_jit.h new file mode 100644 index 0000000..4654c61 --- /dev/null +++ b/a86/native/include/a86_jit.h @@ -0,0 +1,102 @@ +#ifndef A86_JIT_H +#define A86_JIT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct a86_jit a86_jit_t; +typedef struct a86_program a86_program_t; + +typedef struct { + int ok; /* 1 on success, 0 on failure */ + int64_t value; /* return value from called label */ + const char *error_message; +} a86_call_result_t; + +/* + * Extern binding kinds. + * + * FUNCTION: + * `value` is the address of a function to use for the named symbol. + * + * GLOBAL: + * `value` is the address of storage for the named symbol. + * This is for extern data symbols, not for patching already-defined globals. + */ +typedef enum { + A86_EXTERN_FUNCTION = 0, + A86_EXTERN_GLOBAL = 1 +} a86_extern_kind_t; + +/* + * Create / destroy a JIT session. + * + * A JIT session owns the long-lived LLVM/ORC state. Multiple programs may be + * loaded into the same session over time. + */ +a86_jit_t *a86_jit_create(void); +void a86_jit_destroy(a86_jit_t *jit); + +/* + * Load one program into the JIT. + * + * `asm_text` is the assembly source for the program being loaded. + * + * `object_files` is an array of paths to relocatable object files (.o) that + * should be linked with the program. + * + * `extern_names`, `extern_kinds`, and `extern_values` are parallel arrays of + * host-provided external symbol bindings used to resolve names declared with + * `Extern`. + * + * On success, returns a live program handle. On failure, returns NULL and the + * error may be retrieved with `a86_jit_last_error`. + */ +a86_program_t *a86_jit_load(a86_jit_t *jit, + const char *asm_text, + const char *const *object_files, + int object_file_count, + const char *const *extern_names, + const int *extern_kinds, + void *const *extern_values, + int extern_count); + +/* + * Unload a previously loaded program. + * + * It is safe to call this at most once for a given program handle. + */ +void a86_program_unload(a86_program_t *program); + +/* + * Call a label in a loaded program with machine-word arguments. + * + * `label` is the unmangled label name to call, e.g. "entry". + * + * `argv` is an array of machine-word arguments. The first few are passed using + * the platform ABI’s normal argument registers. + * + * The loaded program remains live after the call returns; static data referenced + * by the returned value therefore remains valid until `a86_program_unload`. + */ +a86_call_result_t a86_program_call(a86_program_t *program, + const char *label, + const uint64_t *argv, + int argc); + +/* + * Return the last session-level error string for the JIT, or NULL if none. + * + * The returned pointer is owned by the JIT and remains valid until the next JIT + * operation or until the JIT is destroyed. + */ +const char *a86_jit_last_error(a86_jit_t *jit); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/a86/native/prebuilt/README.md b/a86/native/prebuilt/README.md new file mode 100644 index 0000000..521cf82 --- /dev/null +++ b/a86/native/prebuilt/README.md @@ -0,0 +1,18 @@ +Prebuilt JIT libraries live here under: + +`//liba86_jit.` + +Examples: + +- `macosx/x86_64/liba86_jit.dylib` +- `unix/x86_64/liba86_jit.so` + +To stage the library built in `a86/native/lib/` into this layout: + +```bash +racket a86/native/stage-prebuilt.rkt +``` + +At package install time, `a86/pre-install.rkt` will copy a matching prebuilt +library into `a86/native/lib/` and will only fall back to invoking `make` when +no packaged prebuilt is available for the target OS/architecture. diff --git a/a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib b/a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib new file mode 100755 index 0000000..e5a19de Binary files /dev/null and b/a86/native/prebuilt/macosx/x86_64/liba86_jit.dylib differ diff --git a/a86/native/prebuilt/unix/x86_64/liba86_jit.so b/a86/native/prebuilt/unix/x86_64/liba86_jit.so new file mode 100755 index 0000000..da7be55 Binary files /dev/null and b/a86/native/prebuilt/unix/x86_64/liba86_jit.so differ diff --git a/a86/native/src/a86_jit.cpp b/a86/native/src/a86_jit.cpp new file mode 100644 index 0000000..588eb88 --- /dev/null +++ b/a86/native/src/a86_jit.cpp @@ -0,0 +1,668 @@ +#include "a86_jit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/LLJIT.h" +#include "llvm/MC/MCAsmBackend.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCCodeEmitter.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCObjectWriter.h" +#include "llvm/MC/MCParser/MCAsmParser.h" +#include "llvm/MC/MCParser/MCTargetAsmParser.h" +#include "llvm/MC/MCRegisterInfo.h" +#include "llvm/MC/MCStreamer.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/MCTargetOptions.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/SubtargetFeature.h" + +using namespace llvm; + +namespace { + +std::atomic NextLoadId{0}; + +bool native_trace_enabled() { + const char *v = std::getenv("A86_JIT_TRACE"); + if (!v) { + return false; + } + return std::strcmp(v, "") != 0 && + std::strcmp(v, "0") != 0 && + std::strcmp(v, "false") != 0 && + std::strcmp(v, "FALSE") != 0 && + std::strcmp(v, "False") != 0; +} + +void native_trace(const char *fmt, ...) { + if (!native_trace_enabled()) { + return; + } + + std::fputs("a86-jit-native: ", stderr); + va_list args; + va_start(args, fmt); + std::vfprintf(stderr, fmt, args); + va_end(args); + std::fputc('\n', stderr); + std::fflush(stderr); +} + +struct shared_error_state { + mutable std::mutex mu; + std::string last_error; + std::string last_session_error; + + void clear() { + std::lock_guard lock(mu); + last_error.clear(); + last_session_error.clear(); + } + + void clear_error_only() { + std::lock_guard lock(mu); + last_error.clear(); + } + + void clear_session_error_only() { + std::lock_guard lock(mu); + last_session_error.clear(); + } + + void set_error(std::string msg) { + std::lock_guard lock(mu); + last_error = std::move(msg); + } + + void record_session_error(Error err) { + std::string msg = toString(std::move(err)); + std::lock_guard lock(mu); + if (last_session_error.empty()) { + last_session_error = std::move(msg); + } else { + last_session_error += "\n"; + last_session_error += msg; + } + } + + std::string combine_with_session_error(std::string high_level) const { + std::lock_guard lock(mu); + if (last_session_error.empty()) { + return high_level; + } + if (high_level.empty()) { + return last_session_error; + } + return last_session_error + "\n" + high_level; + } + + const char *error_cstr_unsafe() const { + return last_error.empty() ? nullptr : last_error.c_str(); + } +}; + +struct extern_binding_copy { + std::string name; + a86_extern_kind_t kind; + void *value; +}; + +static std::unique_ptr +assemble_to_object(shared_error_state *errs, StringRef asm_text) { + errs->clear_session_error_only(); + + std::string triple_name = sys::getProcessTriple(); + Triple TT(triple_name); + + std::string lookup_err; + const Target *target = TargetRegistry::lookupTarget(TT, lookup_err); + if (!target) { + errs->set_error("lookupTarget failed: " + lookup_err); + return nullptr; + } + + MCTargetOptions mc_opts; + +#if LLVM_VERSION_MAJOR >= 22 + auto mri = std::unique_ptr(target->createMCRegInfo(TT)); + auto mai = + std::unique_ptr(target->createMCAsmInfo(*mri, TT, mc_opts)); + auto mii = std::unique_ptr(target->createMCInstrInfo()); +#else + auto mri = + std::unique_ptr(target->createMCRegInfo(triple_name)); + auto mai = std::unique_ptr( + target->createMCAsmInfo(*mri, triple_name, mc_opts)); + auto mii = std::unique_ptr(target->createMCInstrInfo()); +#endif + + std::string cpu = sys::getHostCPUName().str(); + auto host_features = sys::getHostCPUFeatures(); + SubtargetFeatures features; + for (const auto &kv : host_features) { + features.AddFeature(kv.getKey(), kv.getValue()); + } + std::string feature_string = features.getString(); + +#if LLVM_VERSION_MAJOR >= 22 + auto sti = std::unique_ptr( + target->createMCSubtargetInfo(TT, cpu, feature_string)); +#else + auto sti = std::unique_ptr( + target->createMCSubtargetInfo(triple_name, cpu, feature_string)); +#endif + + if (!mri || !mai || !mii || !sti) { + errs->set_error("failed to create MC target components"); + return nullptr; + } + + SourceMgr sm; + sm.AddNewSourceBuffer(MemoryBuffer::getMemBufferCopy(asm_text, ""), SMLoc()); + + MCContext ctx(TT, mai.get(), mri.get(), sti.get(), &sm, &mc_opts); + + auto mofi = + std::unique_ptr(target->createMCObjectFileInfo(ctx, true)); + ctx.setObjectFileInfo(mofi.get()); + + SmallVector obj_bytes; + raw_svector_ostream obj_stream(obj_bytes); + + auto mab = std::unique_ptr( + target->createMCAsmBackend(*sti, *mri, mc_opts)); + auto mce = std::unique_ptr(target->createMCCodeEmitter(*mii, ctx)); + if (!mab || !mce) { + errs->set_error("failed to create MC backend or code emitter"); + return nullptr; + } + + auto obj_writer = mab->createObjectWriter(obj_stream); + auto streamer = std::unique_ptr(target->createMCObjectStreamer( + TT, ctx, std::move(mab), std::move(obj_writer), std::move(mce), *sti)); + if (!streamer) { + errs->set_error("failed to create object streamer"); + return nullptr; + } + + auto parser = std::unique_ptr(createMCAsmParser(sm, ctx, *streamer, *mai)); + auto tap = std::unique_ptr( + target->createMCAsmParser(*sti, *parser, *mii, mc_opts)); + if (!parser || !tap) { + errs->set_error("failed to create asm parser"); + return nullptr; + } + + parser->setTargetParser(*tap); + + if (parser->Run(/*NoInitialTextSection=*/false, /*NoFinalize=*/false)) { + errs->set_error(errs->combine_with_session_error("assembly parse/emit failed")); + return nullptr; + } + + return MemoryBuffer::getMemBufferCopy( + StringRef(obj_bytes.data(), obj_bytes.size()), ""); +} + +} // namespace + +struct a86_jit { + std::unique_ptr jit; + orc::JITDylib *support_jd = nullptr; + std::shared_ptr errs = std::make_shared(); + + void clear_error() { errs->clear_error_only(); } + void clear_session_error() { errs->clear_session_error_only(); } + void set_error(std::string msg) { errs->set_error(std::move(msg)); } + + const char *error_cstr() const { + std::lock_guard lock(errs->mu); + return errs->error_cstr_unsafe(); + } + + std::string combine_with_session_error(std::string high_level) const { + return errs->combine_with_session_error(std::move(high_level)); + } +}; + +struct a86_program { + a86_jit *parent = nullptr; + orc::JITDylib *program_jd = nullptr; + orc::JITDylib *extern_jd = nullptr; + orc::ResourceTrackerSP program_tracker; + orc::ResourceTrackerSP extern_tracker; + uint64_t load_id = 0; + std::string jd_name; + std::string extern_jd_name; + std::vector externs; +}; + +extern "C" { + +a86_jit_t *a86_jit_create(void) { + auto jit = std::make_unique(); + + static std::once_flag llvm_initialized; + std::call_once(llvm_initialized, [] { + InitializeNativeTarget(); + InitializeNativeTargetAsmParser(); + InitializeNativeTargetAsmPrinter(); + }); + + auto jtmb_or_err = orc::JITTargetMachineBuilder::detectHost(); + if (!jtmb_or_err) { + jit->set_error(toString(jtmb_or_err.takeError())); + return nullptr; + } + + auto dl_or_err = jtmb_or_err->getDefaultDataLayoutForTarget(); + if (!dl_or_err) { + jit->set_error(toString(dl_or_err.takeError())); + return nullptr; + } + + auto lljit_or_err = orc::LLJITBuilder() + .setJITTargetMachineBuilder(std::move(*jtmb_or_err)) + .setDataLayout(*dl_or_err) + .create(); + if (!lljit_or_err) { + jit->set_error(toString(lljit_or_err.takeError())); + return nullptr; + } + + jit->jit = std::move(*lljit_or_err); + + { + auto errs = jit->errs; + jit->jit->getExecutionSession().setErrorReporter( + [errs](Error err) { + errs->record_session_error(std::move(err)); + }); + } + + auto support_jd_or_err = jit->jit->createJITDylib("a86_support"); + if (!support_jd_or_err) { + jit->set_error(toString(support_jd_or_err.takeError())); + return nullptr; + } + + jit->support_jd = &*support_jd_or_err; + + auto gen_or_err = orc::DynamicLibrarySearchGenerator::GetForCurrentProcess( + jit->jit->getDataLayout().getGlobalPrefix()); + if (!gen_or_err) { + jit->set_error(toString(gen_or_err.takeError())); + return nullptr; + } + + jit->support_jd->addGenerator(std::move(*gen_or_err)); + return jit.release(); +} + +void a86_jit_destroy(a86_jit_t *jit) { + delete jit; +} + +const char *a86_jit_last_error(a86_jit_t *jit) { + if (!jit) { + return "invalid jit handle"; + } + return jit->error_cstr(); +} + +a86_program_t *a86_jit_load(a86_jit_t *jit, + const char *asm_text, + const char *const *object_files, + int object_file_count, + const char *const *extern_names, + const int *extern_kinds, + void *const *extern_values, + int extern_count) { + if (!jit || !jit->jit || !jit->support_jd) { + return nullptr; + } + + jit->clear_error(); + jit->clear_session_error(); + + if (!asm_text) { + jit->set_error("asm_text is null"); + return nullptr; + } + if (object_file_count < 0) { + jit->set_error("object_file_count is negative"); + return nullptr; + } + if (extern_count < 0) { + jit->set_error("extern_count is negative"); + return nullptr; + } + if (object_file_count > 0 && !object_files) { + jit->set_error("object_files is null but object_file_count > 0"); + return nullptr; + } + if (extern_count > 0 && + (!extern_names || !extern_kinds || !extern_values)) { + jit->set_error("extern arrays are null but extern_count > 0"); + return nullptr; + } + + auto obj = assemble_to_object(jit->errs.get(), asm_text); + if (!obj) { + return nullptr; + } + + // Create a fresh JITDylib for this program - no pooling! + uint64_t load_id = NextLoadId++; + std::string jd_name = "a86_prog_" + std::to_string(load_id); + native_trace("load id=%llu jd=%s extern_count=%d object_file_count=%d asm_bytes=%zu obj_bytes=%zu", + static_cast(load_id), + jd_name.c_str(), + extern_count, + object_file_count, + std::strlen(asm_text), + obj->getBufferSize()); + auto jd_or_err = jit->jit->createJITDylib(jd_name); + if (!jd_or_err) { + jit->set_error(toString(jd_or_err.takeError())); + return nullptr; + } + orc::JITDylib *program_jd = &*jd_or_err; + auto program_tracker = program_jd->createResourceTracker(); + std::string extern_jd_name = jd_name + "_externs"; + auto extern_jd_or_err = jit->jit->createJITDylib(extern_jd_name); + if (!extern_jd_or_err) { + jit->set_error(toString(extern_jd_or_err.takeError())); + consumeError(program_tracker->remove()); + return nullptr; + } + orc::JITDylib *extern_jd = &*extern_jd_or_err; + auto extern_tracker = extern_jd->createResourceTracker(); + extern_jd->addToLinkOrder(*jit->support_jd); + program_jd->addToLinkOrder(*extern_jd); + program_jd->addToLinkOrder(*jit->support_jd); + + auto cleanup = [&]() { + if (program_tracker) { + consumeError(program_tracker->remove()); + program_tracker.reset(); + } + if (extern_tracker) { + consumeError(extern_tracker->remove()); + extern_tracker.reset(); + } + // No pooling - JITDylib will be cleaned up when tracker removes all resources + }; + + auto prog = std::make_unique(); + prog->parent = jit; + prog->program_jd = program_jd; + prog->extern_jd = extern_jd; + prog->program_tracker = program_tracker; + prog->extern_tracker = extern_tracker; + prog->load_id = load_id; + prog->jd_name = jd_name; + prog->extern_jd_name = extern_jd_name; + prog->externs.reserve(extern_count); + + // Copy host-provided externs into program-owned storage before defining + // symbols so their full payload remains stable for the program lifetime. + for (int i = 0; i < extern_count; ++i) { + if (!extern_names[i]) { + jit->set_error("extern binding has null name"); + cleanup(); + return nullptr; + } + prog->externs.push_back( + extern_binding_copy{extern_names[i], + static_cast(extern_kinds[i]), + extern_values[i]}); + } + + // Install host-provided externs as absolute symbols. + orc::SymbolMap symbol_map; + for (const auto &ext : prog->externs) { + native_trace("load id=%llu extern-jd=%s define extern name=%s kind=%d addr=%p", + static_cast(load_id), + extern_jd_name.c_str(), + ext.name.c_str(), + static_cast(ext.kind), + ext.value); + auto sym_name = jit->jit->mangleAndIntern(ext.name); + orc::ExecutorAddr addr = orc::ExecutorAddr::fromPtr(ext.value); + symbol_map[sym_name] = orc::ExecutorSymbolDef(addr, JITSymbolFlags::Exported); + } + + if (!symbol_map.empty()) { + native_trace("load id=%llu extern-jd=%s defining %zu absolute symbols", + static_cast(load_id), + extern_jd_name.c_str(), + symbol_map.size()); + if (auto err = extern_jd->define(orc::absoluteSymbols(std::move(symbol_map)), + extern_tracker)) { + jit->set_error(jit->combine_with_session_error(toString(std::move(err)))); + cleanup(); + return nullptr; + } + } + + // Add linked object files. + for (int i = 0; i < object_file_count; ++i) { + if (!object_files[i]) { + jit->set_error("object file path is null"); + cleanup(); + return nullptr; + } + native_trace("load id=%llu jd=%s add object file path=%s", + static_cast(load_id), + jd_name.c_str(), + object_files[i]); + auto mb_or_err = MemoryBuffer::getFile(object_files[i]); + if (!mb_or_err) { + jit->set_error( + std::string("failed to read object file ") + object_files[i] + ": " + + mb_or_err.getError().message()); + cleanup(); + return nullptr; + } + if (auto err = jit->jit->addObjectFile(program_tracker, std::move(*mb_or_err))) { + jit->set_error( + jit->combine_with_session_error( + std::string("failed to add object file ") + object_files[i] + ": " + + toString(std::move(err)))); + cleanup(); + return nullptr; + } + } + + // Add the assembled a86 program object. + native_trace("load id=%llu jd=%s add assembled object bytes=%zu", + static_cast(load_id), + jd_name.c_str(), + obj->getBufferSize()); + if (auto err = jit->jit->addObjectFile(program_tracker, std::move(obj))) { + jit->set_error(jit->combine_with_session_error(toString(std::move(err)))); + cleanup(); + return nullptr; + } + + prog->program_tracker = std::move(program_tracker); + prog->extern_tracker = std::move(extern_tracker); + native_trace("load id=%llu jd=%s complete", + static_cast(load_id), + prog->jd_name.c_str()); + return prog.release(); +} + +void a86_program_unload(a86_program_t *program) { + if (program) { + native_trace("unload id=%llu jd=%s extern-jd=%s", + static_cast(program->load_id), + program->jd_name.c_str(), + program->extern_jd_name.c_str()); + if (program->parent && program->parent->jit) { + if (program->program_tracker) { + consumeError(program->program_tracker->remove()); + program->program_tracker.reset(); + } + if (program->extern_tracker) { + consumeError(program->extern_tracker->remove()); + program->extern_tracker.reset(); + } + // Do NOT return JITDylib to a pool - let ORC manage its lifecycle. + program->program_jd = nullptr; + program->extern_jd = nullptr; + } + delete program; + } +} + +a86_call_result_t a86_program_call(a86_program_t *program, + const char *label, + const uint64_t *argv, + int argc) { + a86_call_result_t result{}; + result.ok = 0; + result.value = 0; + result.error_message = nullptr; + + if (!program || !program->parent || !program->parent->jit || + !program->program_jd) { + result.error_message = "invalid program handle"; + return result; + } + + auto *jit = program->parent; + jit->clear_error(); + jit->clear_session_error(); + + if (!label) { + jit->set_error("label is null"); + result.error_message = jit->error_cstr(); + return result; + } + if (argc < 0) { + jit->set_error("argc is negative"); + result.error_message = jit->error_cstr(); + return result; + } + if (argc > 0 && !argv) { + jit->set_error("argv is null but argc > 0"); + result.error_message = jit->error_cstr(); + return result; + } + + native_trace("lookup id=%llu jd=%s label=%s argc=%d", + static_cast(program->load_id), + program->jd_name.c_str(), + label, + argc); + auto sym_or_err = jit->jit->lookup(*program->program_jd, label); + if (!sym_or_err) { + native_trace("lookup id=%llu jd=%s label=%s failed", + static_cast(program->load_id), + program->jd_name.c_str(), + label); + std::string high = + "lookup of label '" + std::string(label) + "' failed: " + + toString(sym_or_err.takeError()); + jit->set_error(jit->combine_with_session_error(std::move(high))); + result.error_message = jit->error_cstr(); + return result; + } + + native_trace("lookup id=%llu jd=%s label=%s addr=0x%llx", + static_cast(program->load_id), + program->jd_name.c_str(), + label, + static_cast(sym_or_err->getValue())); + + uint64_t value = 0; + switch (argc) { + case 0: { + using Fn = uint64_t (*)(); + auto *fn = sym_or_err->toPtr(); + value = fn(); + break; + } + case 1: { + using Fn = uint64_t (*)(uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0]); + break; + } + case 2: { + using Fn = uint64_t (*)(uint64_t, uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0], argv[1]); + break; + } + case 3: { + using Fn = uint64_t (*)(uint64_t, uint64_t, uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0], argv[1], argv[2]); + break; + } + case 4: { + using Fn = uint64_t (*)(uint64_t, uint64_t, uint64_t, uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0], argv[1], argv[2], argv[3]); + break; + } + case 5: { + using Fn = + uint64_t (*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0], argv[1], argv[2], argv[3], argv[4]); + break; + } + case 6: { + using Fn = + uint64_t (*)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t); + auto *fn = sym_or_err->toPtr(); + value = fn(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); + break; + } + default: + jit->set_error("a86_program_call currently supports at most 6 arguments"); + result.error_message = jit->error_cstr(); + return result; + } + + native_trace("call id=%llu jd=%s label=%s returned value=0x%llx", + static_cast(program->load_id), + program->jd_name.c_str(), + label, + static_cast(value)); + + result.ok = 1; + result.value = value; + result.error_message = nullptr; + return result; +} + +} // extern "C" diff --git a/a86/native/stage-prebuilt.rkt b/a86/native/stage-prebuilt.rkt new file mode 100644 index 0000000..bfa1a50 --- /dev/null +++ b/a86/native/stage-prebuilt.rkt @@ -0,0 +1,35 @@ +#lang racket + +(require racket/runtime-path + setup/cross-system) + +(define-runtime-path native-dir ".") + +(define lib-name + (format "liba86_jit~a" (cross-system-type 'so-suffix))) + +(define src + (build-path native-dir "lib" lib-name)) + +(define dst-dir + (build-path native-dir + "prebuilt" + (symbol->string (cross-system-type 'os)) + (symbol->string (cross-system-type 'arch)))) + +(define dst + (build-path dst-dir lib-name)) + +(define (main) + (unless (file-exists? src) + (error 'stage-prebuilt + (format "expected built library at ~a; run `make -C a86/native all` first" + src))) + + (make-directory* dst-dir) + (copy-file src dst #t) + + (printf "a86: staged prebuilt library to ~a\n" dst)) + +(module+ main + (main)) diff --git a/a86/pre-install.rkt b/a86/pre-install.rkt new file mode 100644 index 0000000..4be11e3 --- /dev/null +++ b/a86/pre-install.rkt @@ -0,0 +1,98 @@ +#lang racket +(require setup/cross-system) + +(provide pre-installer) +(define (pre-installer collects-dir collection-dir) + (define coll-dir + (simplify-path (path->complete-path collection-dir))) + (check-x86) + (install-jit! (build-path coll-dir "native"))) + +(define (check-x86) + (unless (eq? 'x86_64 (system-type 'arch)) + (error 'a86-installer + "This library requires an x86_64 installation of Racket; yours is a ~a (~a)." + (system-type 'arch) + (system-type 'os)))) + +(define (install-jit! jit-dir) + (or (install-prebuilt-jit jit-dir) + (build-jit jit-dir))) + +(define (install-prebuilt-jit jit-dir) + (define src (prebuilt-lib-path jit-dir)) + (define dst (lib-path jit-dir)) + (and (file-exists? src) + (begin + (make-directory* (path-only dst)) + (copy-file src dst #t) + (printf "a86: installed prebuilt native JIT library ~a\n" src) + (flush-output) + #t))) + +(define (build-jit jit-dir) + + (define makefile-path + (build-path jit-dir "Makefile")) + + (define built-lib-path + (lib-path jit-dir)) + + (unless (directory-exists? jit-dir) + (error 'build-jit + (format "expected native directory at ~a" jit-dir))) + + (unless (file-exists? makefile-path) + (error 'build-jit + (format "expected Makefile at ~a" makefile-path))) + + (printf "a86: building native JIT library in ~a for ~a\n" + jit-dir + (cross-system-type 'os)) + (flush-output) + + (parameterize ([current-directory jit-dir]) + (define ok? + (or (find-executable-path "gmake") + (find-executable-path "make"))) + (unless ok? + (error 'build-jit + "could not find 'make' or 'gmake' in PATH")) + + (define make-exe ok?) + + (define status + (system*/exit-code make-exe "all")) + + (unless (zero? status) + (error 'build-jit + (format "native JIT build failed with exit code ~a" status)))) + + (unless (file-exists? built-lib-path) + (error 'build-jit + (format "build completed but did not produce ~a" built-lib-path))) + + (printf "a86: built ~a\n" built-lib-path) + (flush-output)) + +(define (lib-name) + (string->path + (string-append "liba86_jit" (target-so-suffix)))) + +(define (lib-path jit-dir) + (build-path jit-dir "lib" (lib-name))) + +(define (prebuilt-lib-path jit-dir) + (build-path jit-dir + "prebuilt" + (symbol->string (cross-system-type 'os)) + (symbol->string (cross-system-type 'arch)) + (lib-name))) + +(define (target-so-suffix) + (case (cross-system-type 'os) + [(macosx) ".dylib"] + [(unix) ".so"] + [else + (error 'build-jit + (format "unsupported target OS: ~a" (cross-system-type 'os)))])) diff --git a/a86/printer.rkt b/a86/printer.rkt index 5dbb632..fe28851 100644 --- a/a86/printer.rkt +++ b/a86/printer.rkt @@ -4,11 +4,15 @@ [asm-string (-> (listof instruction?) string?)] ; deprecated [asm-display (-> (listof instruction?) any)]) +(define current-os + (make-parameter (system-type 'os))) + (define current-shared? (make-parameter #f)) (module* private #f - (provide current-shared?)) + (provide current-shared?) + (provide current-os)) ;; Asm -> String (define (asm-string a) @@ -26,14 +30,12 @@ ;; Label -> String ;; prefix with _ for Mac -(define label-symbol->string - (match (system-type 'os) +(define (label-symbol->string s) + (match (current-os) ['macosx - (λ (s) - (string-append "_" (symbol->string s)))] + (string-append "\"_" (symbol->string s) "\"")] [_ - (λ (s) - (symbol->string s))])) + (string-append "\"" (symbol->string s) "\"")])) ;(if (and (current-shared?) (memq s (current-extern-labels))) ; hack for ELF64 shared libraries in service of @@ -41,14 +43,7 @@ ;(string-append "$" (symbol->string s) " wrt ..plt") ;(symbol->string s)))])) -(define extern-label-decl-symbol->string - (match (system-type 'os) - ['macosx - (λ (s) - (string-append "_" (symbol->string s)))] - [_ - (λ (s) - (symbol->string s))])) +(define extern-label-decl-symbol->string label-symbol->string) ;; Instruction -> String (define (common-instruction->string i) @@ -187,12 +182,12 @@ (string-append "(" (exp->string e1) " " (symbol->string o) " " (exp->string e2) ")")])) (define (text-section n) - (match (system-type 'os) - ['macosx (format ".section __TEXT,~a\n\t.p2align 4" n)] + (match (current-os) + ['macosx (format ".section __TEXT,~a" n)] [_ (format ".section ~a,\"ax\",@progbits\n\t.p2align 4" n)])) (define (data-section n) - (match (system-type 'os) + (match (current-os) ['macosx (format ".section __DATA,~a\n\t.p2align 3" n)] [_ (format ".section ~a,\"aw\",@progbits\n\t.p2align 3" n)])) @@ -207,7 +202,7 @@ [(Global ($ l)) (string-append tab ".global " (label-symbol->string l))] [(Label ($ l)) (string-append (label-symbol->string l) ":")] [(Align n) - (match (system-type 'os) + (match (current-os) ['macosx (string-append ".p2align " (number->string (let loop ([i 0] [n n]) diff --git a/a86/scribblings/a86.scrbl b/a86/scribblings/a86.scrbl index 34e0f1b..24a8f30 100644 --- a/a86/scribblings/a86.scrbl +++ b/a86/scribblings/a86.scrbl @@ -1,7 +1,8 @@ #lang scribble/manual @(require scribble/bnf) -@(require (for-label (except-in racket compile) +@(require (for-label (except-in racket compile ->) + ffi/unsafe a86/ast a86/registers a86/printer @@ -152,7 +153,7 @@ be well-formed, which means: @defproc[(seq [x (or/c instruction? (listof instruction?))] ...) (listof instruction?)]{ - A convenience function for splicing togeter instructions and lists of instructions. + A convenience function for splicing together instructions and lists of instructions. @ex[ (seq) @@ -228,8 +229,10 @@ correspond to actual execuable @secref{Instructions}. @defstruct*[Extern ([x label?])]{ - Declares an external label. External labels may be used, but not defined - within the program. + Declares an external label. External labels may be used, but not + defined within the program. In order to run a program, all external + labels must be resolved using either @racket[current-externs] or + @racket[current-objects]. } @@ -1842,16 +1845,11 @@ written to with the value 42, before being dereferenced and returned: @defmodule[a86/interp] +@subsection{Running assembly programs} + It is possible to run a86 @secref["Programs"] from within Racket using @racket[asm-interp]. -Using @racket[asm-interp] comes with significant overhead, -so it's unlikely you'll want to implement Racket -functionality in assembly code via @racket[asm-interp]. -Rather this is a utility for interactively exploring the -behavior of assembly code and writing tests for functions -that generate assembly code. - If you have code written in a86 that you would like to execute directly, you should instead use the @seclink["Printing"]{printing} facilities to save the @@ -1925,6 +1923,8 @@ The simplest form of interpreting an a86 program is to use } +@subsection{Resolving external labels} + It is often the case that we want our assembly programs to interact with the oustide or to use functionality implemented in other programming languages. For that reason, @@ -1932,18 +1932,18 @@ it is possible to link in object files to the running of an a86 program. The mechanism for controlling which objects should be linked -in is a parameter called @racket[current-objs], which +in is a parameter called @racket[current-objects], which contains a list of paths to object files which are linked to the assembly code when it is interpreted. -@defparam[current-objs objs (listof path-string?) #:value '()]{ +@defparam[current-objects objs (listof path-string?) #:value '()]{ Parameter that controls object files that will be linked in to assembly code when running @racket[asm-interp]. } -For example, let's implement a GCD function in C: +For example, suppose there's a GCD function in C: @filebox-include[fancy-c a86 "gcd.c"] @@ -1955,12 +1955,12 @@ The option @tt{-fPIC} is important; it causes the C compiler to emit ``position independent code,'' which is what enables Racket to dynamically load and run the code. -Once the object file exists, using the @racket[current-objs] +Once the object file exists, using the @racket[current-objects] parameter, we can run code that uses things defined in the C code: @ex[ -(parameterize ((current-objs '("gcd.o"))) +(parameterize ((current-objects (list "gcd.o"))) (asm-interp (Extern 'gcd) (Mov 'rdi 11571) (Mov 'rsi 1767) @@ -1969,11 +1969,7 @@ code: (Add 'rsp 8) (Ret)))] -This will be particularly relevant for writing a compiler -where emitted code will make use of functionality defined in -a runtime system. - -Note that if you forget to set @racket[current-objs], you will get a +Note that if you forget to set @racket[current-objects], you will get a linking error saying a symbol is undefined: @ex[ @@ -1987,6 +1983,55 @@ linking error saying a symbol is undefined: (Ret)))] +Sometimes that other programming language we want our assembly programs to interact with +is Racket. In this case, we can actually resolve external symbols in the assembly code +to Racket values. + +For example, suppose there's a GCD function in Racket: + +@ex[ +(define (gcd n1 n2) + (if (zero? n2) + n1 + (gcd n2 (modulo n1 n2))))] + +We can define a @racket[host-gcd] function, which essentially attaches +a C-style type declaration (using bindings from the FFI) to this +function and the external symbol @racket['gcd]: + +@defstruct*[extern ([name symbol?] [value any/c] [type ctype?])]{ + +Structure for representing a Racket-hosted external.} + +@ex[ +(require ffi/unsafe) +(define host-gcd + (extern 'gcd gcd (_fun _int64 _int64 -> _int64)))] + + +Then we can inform the interpreter to resolve the @racket['gcd] +external label to @racket[host-gcd] by using the +@racket[current-externs] parameter. + +@defparam[current-externs externs (listof extern?) #:value '()]{ + +Parameter that controls Racket-hosted externs that will be linked in to +assembly code when running @racket[asm-interp]. + + +@ex[ +(parameterize ([current-externs (list host-gcd)]) + (asm-interp (Extern 'gcd) + (Mov 'rdi 11571) + (Mov 'rsi 1767) + (Sub 'rsp 8) + (Call 'gcd) + (Add 'rsp 8) + (Ret)))] + +} + + @defproc[(asm-interp/io [is (listof instruction?)] [in string?]) (cons integer? string?)]{ Like @racket[asm-interp], but uses @racket[in] for input and produce the result along