feeley on master
Fix GVM jump instruction mishan… Universal backend: avoid possib… (compare)
the multiple imports of _test
are due to the way the macros are written… for example test-equal
is defined like this :
(##define-syntax test-equal (lambda (src)
(##import _test/test-expand)
(test-expand src 'test-equal)))
so every call to test-equal
will cause a import
to be processed
Here is an extract from an article I am writing:
I experimented with Gambit JavaScript target, looked at the
benchmarks (but I did
not run them myself, yet), read on the Termite library that is inspired from
Erlang, and the upcoming Scheme Infix eXpression (SIX) already
available in master
branch: In the years to come, Gambit Scheme
will become the goto programming stack for industrial use. Here is
why:
Gambit Scheme is a Scheme;
Termite is what you need to build an application distributed in a
controlled environnement, without requiring a change of programming
language (see Erlang);
Gambit can also target the web browser;
Otherwise stated: we will be able to build full-stack applications
that can scale painlessly thanks to Termite, with a single great
programming language, the same compiler, and interpreter backend-side
and frontend-side.
foo.sld
that includes foo/body.scm
. Also, the ability to specify a git repository is great, it alleviates the need for a package management tool.
unsyntax
(or fork) it support all macro systems (I will give it try at some point).
-exe
… and if you want that wrapped in a trivial HTML you can -exe -o foo.html
-:r7rs
continuation-capture
to switch threads)
fetch
?
fetch
and it will all be concurrent (it is one of the examples in the paper I shared with you, which will be presented at ELS21 by the way)
try this on https://gambitscheme.org/try
(define-syntax future
(lambda (stx)
(syntax-case stx ()
((future expr)
#'(thread (lambda () expr))))))
(define touch thread-join!)
(define (pmap f lst) ;; "parallel" map
(map touch (map (lambda (x) (future (f x))) lst)))
(define memo
(string-append
"Scheme_-_An_interpreter_for_extended_"
"lambda_calculus.djvu"))
(define (page n)
(string-append
"https://upload.wikimedia.org/wikipedia"
"/commons/thumb/1/1e/" memo
"/page" (number->string n) "-593px-" memo ".jpg"))
(define (fetch-blob url)
\fetch(`url).then(function (r) { return r.blob(); }))
(define (->URL blob)
\URL.createObjectURL(`blob))
(define (show url)
\document.body.insertAdjacentHTML(
"beforeend",
"<img src='"+(`url)+"' width=200px>"))
(define images
(pmap (lambda (n) (->URL (fetch-blob (page n))))
(iota 43 1)))
(for-each show images)
you should see all the pages appear quickly… if you replace pmap
by map
it will be 10x slower
@amirouche it depends what kind of documentation you are looking for… there are academic papers about how the universal backend works (see for example https://www.researchgate.net/publication/304551221_Compiling_for_multi-language_task_migration). There is no “software developper” documentation however. Here’s a quick overview. The files implementing the universal backend are gsc/_t-univ-*.scm
. In essence they implement a translator from the GVM (Gambit Virtual Machine) code and the target language (JavaScript, Python, etc). There are 2 main entry point to the universal backend: the procedure univ-dump-code
(which implements GVM -> target) and the procedure univ-link
(which implements the static module linker). Both of these procedures, and other methods of the backend are contained in a target
object that is initialized by the univ-setup
procedure:
(univ-setup 'js '((".js" . JavaScript)) '() '())
(univ-setup 'python '((".py" . Python)) '((pre3)) '())
(univ-setup 'ruby '((".rb" . Ruby)) '() '())
(univ-setup 'php '((".php" . PHP)) '((pre53)) '())
These calls define the targets that can be used on the gsc command line, i.e. -target js
, -target python
, etc The fields of the target
object are documented in gsc/_back.scm
. For example:
;; nb-regs Integer denoting the maximum number of GVM registers
;; that should be used when generating GVM code for this
;; target machine.
;;
;; nb-arg-regs Integer denoting the maximum number of procedure call
;; arguments that are passed in GVM registers.
;;
;; compactness Integer denoting the level of compactness of the
;; generated code. Levels from 0 to 5 cause the
;; generation of increasingly compact code with little or
;; no impact on execution speed. Lower values tend to
;; make the generated code more humanly readable. Above
;; a level of 5 the compiler will trade execution speed
;; for saving code space. The detailed meaning of this
;; option depends on the target and some targets may
;; ignore it.
The universal backend implements the code generation for multiple target languages by using code generation macros. These have names starting with a ^
, for example (^if test true [false])
generates a 1 or 2 branch “if” construct in the target language and (^ X Y Z)
concatenates the target code X, Y and Z. The ^if
macro is implemented through the univ-emit-if
procedure which dispatches on the target language:
(define (univ-emit-if ctx test true #!optional (false #f))
(case (target-name (ctx-target ctx))
((js php java)
(^ "if (" test ") {\n"
(^indent true)
(if false
(^ "} else {\n"
(^indent false))
(^))
"}\n"))
((python)
(^ "if " test ":\n"
(^indent true)
(if false
(^ "else:\n"
(^indent false))
(^))))
((ruby)
(^ "if " test "\n"
(^indent true)
(if false
(^ "else\n"
(^indent false))
(^))
"end\n"))
((go)
(^ "if " test " {\n"
(^indent true)
(if false
(^ "} else {\n"
(^indent false))
(^))
"}\n"))
(else
(compiler-internal-error
"univ-emit-if, unknown target"))))
The implementation of Scheme primitives is done through the univ-define-prim
macro. For example here is the implementation of ##fxmin
that returns the minimum of two fixnums:
(univ-define-prim "##fxmin" #t
(make-translated-operand-generator
(lambda (ctx return arg1 arg2)
(return (^if-expr 'scmobj
(^< (^fixnum-unbox arg1) (^fixnum-unbox arg2))
arg1
arg2)))))
^if-expr
macro generates conditional expressions. Note that the first parameter is a type (the type of the value returned by the conditional expression) because some targets need to have this information, namely the go
target implements conditional expressions using a function call:(define (univ-emit-if-expr ctx type expr1 expr2 expr3)
(case (target-name (ctx-target ctx))
((js ruby java)
(^ expr1 " ? " expr2 " : " expr3))
((php)
(^parens (^ expr1 " ? " expr2 " : " expr3)))
((python)
(^ expr2 " if " expr1 " else " expr3))
((go)
(^apply (univ-emit-fn-decl
ctx
#f
type
'()
(^if expr1
(^return expr2)
(^return expr3)))
'()))
(else
(compiler-internal-error
"univ-emit-if-expr, unknown target"))))