1 (in-package :avm2-compiler
)
3 ;;;; special forms (and probably some things that are techically
4 ;;;; macros/functions according to CL, but implemented directly here
7 ;; official list of special operators:
8 ;; http://www.lispworks.com/documentation/HyperSpec/Body/03_ababa.htm#clspecialops
37 ;; multiple-value-call
38 ;; multiple-value-prog1
47 (define-special* progn
(cdr)
51 append
(scompile form
)
52 ;; ignore return values from intermediate steps
53 when
(or next
(and (consp form
) (eql (car form
) 'return
)))
57 ;; (scompile '(progn "foo" "bar" :true))
59 #+nil
(define-special return
(value)
63 ;;; fixme: this adds a :pop after :return-value, is that correct?
64 ;; (scompile '(progn "foo" (return :false) :true))
69 (define-special let
(bindings &rest body
)
70 (let ((bindings-indices
71 (loop for binding in bindings
72 for j from
(last-local-index)
73 for init
= (if (listp binding
) (second binding
) nil
)
74 for name
= (if (listp binding
) (first binding
) binding
)
75 collect
`(,init
,name .
,j
))))
76 (with-cleanup ((gensym "LET-CLEANUP")
77 `(%asm
(:comment
"let-kill")
78 ,@(loop for
(nil nil . index
) in bindings-indices
79 collect
`(:kill
,index
))
83 (loop for
(init nil . index
) in bindings-indices
84 append
(scompile init
)
85 collect
`(:set-local
,index
))
86 (with-local-vars ((mapcar 'cdr bindings-indices
))
87 ;; compile the body as a progn, and kill the locals on exit
90 ,@(loop for
(nil nil . index
) in bindings-indices
91 collect
`(:kill
,index
))))))))
92 ;; (with-lambda-context (:args '(foo)) (scompile '(let ((foo 1.23) (bar foo)) foo)))
94 ;;; let* is uglier to implement without modifying lambda context stuff
95 ;;; directly, so implementing in terms of let with a macro in cl lib
97 ;;(define-special let* (bindings &rest body)
98 ;; (with-nested-lambda-context
100 ;; ;; set up bindings
101 ;; (loop for binding in bindings
102 ;; for j from (length (locals *current-lambda*))
103 ;; if (consp binding)
104 ;; append (scompile (second binding))
105 ;; and collect `(:set-local ,j )
106 ;; and do (push (cons (car binding) j) (locals *current-lambda*))
108 ;; do (push (cons binding j) (locals *current-lambda*)))
109 ;; ;; compile the body as a progn, and kill the locals on exit
110 ;; `(,@(scompile `(progn ,@body))
111 ;; ,@(loop for binding in bindings
112 ;; for name = (if (consp binding) (car binding) binding)
113 ;; collect `(:kill ,(get-lambda-local-index name)))))))
114 ;; (with-simple-lambda-context (foo) (scompile '(let* ((foo 1.23) (bar foo)) foo)))
117 (define-special %set-local
(local value
)
118 ;; (%set-local var value) -> value
119 `(,@(scompile value
) ;; calculate value
120 (:dup
) ;; copy value so we can reurn it
121 (:set-local
,(or (get-lambda-local-index local
) (break)))))
122 ;; (with-lambda-context (foo) (scompile '(%set-local foo 2.3)))
124 (define-special %asm
(&rest cdr
)
125 ;; (%asm (op1 args) (op2 ...) ... )
128 (:@ `(:get-local
,(get-lambda-local-index (second x
))))
129 (:@kill
`(:kill
,(get-lambda-local-index (second x
))))
132 (define-special %asm
* (args &rest cdr
)
133 ;; (%asm* (arg list) (op1 args) (op2 ...) ... )
135 (loop for arg in args
136 append
(scompile arg
))
140 (define-special %label
(target)
141 ;; (%label name) ;; for reverse jumps only
143 ;; hack since we always pop after each statement in a progn, gets
144 ;; removed later by peephole pass
147 (define-special %dlabel
(target)
148 ;; (%dlabel name) ;; for forward jumps only
152 (define-special %go
(target)
157 (define-special* tagbody
(body)
158 (let ((tags (loop for tag-or-form in body
159 when
(atom tag-or-form
)
160 collect
(cons tag-or-form
161 (gensym (format nil
"TAGBODY-~a-" tag-or-form
))))))
162 (with-nested-lambda-tags (tags)
163 ;; fixme: use dlabel for forward jumps
164 `(,@(loop for tag-or-form in body
165 if
(atom tag-or-form
)
166 collect
`(:%label
,(get-lambda-tag tag-or-form
))
168 append
(scompile tag-or-form
)
172 (define-special go
(tag)
173 (scompile-cons '%go
(list (get-lambda-tag tag
))))
175 (define-special %go-when
(cond tag
)
176 (scompile-cons '%when
(list cond
(get-lambda-tag tag
))))
178 ;; (with-lambda-context () (scompile '(tagbody foo (go baz) bar 1 baz 2)))
180 (define-special %when
(cond label
)
181 ;; (%when cond label)
186 (define-special %if
(cond false-test true-branch false-branch
)
187 (let (#+nil
(true-label (gensym "%IF-TRUE-"))
188 (false-label (gensym "%IF-FALSE-"))
189 (end-label (gensym "%IF-END-")))
191 (,false-test
,false-label
)
192 ,@(scompile true-branch
)
194 (:%dlabel
,false-label
)
195 ,@(scompile false-branch
)
196 (:%dlabel
,end-label
))))
198 (define-special if
(cond true-branch false-branch
)
199 `(,@(scompile `(%if
,cond
:if-false
,true-branch
,false-branch
))))
201 ;; (avm2-asm::with-assembler-context (avm2-asm::code (avm2-asm:assemble-method-body (scompile '(when :true 1)) )))
204 (define-special %inc-local-i
(var)
205 ;; (%inc-local-i var)
206 `((:inc-local-i
,(get-lambda-local-index var
))
207 ;; hack since we always pop after each statement in a progn :/
208 (:get-local
,(get-lambda-local-index var
))))
210 #+nil
(define-special dotimes
((var count
&optional result
) &rest body
)
211 ;; (dotimes (var count &optional result) body)
213 ;; set local for counter
214 ;; set local for limit
215 ;;(format t "dotimes : var=~s count=~s result=~s~%body=~s~%" var count result body)
216 (let ((label (gensym "LABEL-"))
217 (label2 (gensym "LABEL2-"))
218 (max (gensym "MAX-")))
219 (scompile ; format t "~s"
221 ;; var should not be valid while evaluating max
226 ;(%set-local ,var (+ ,var 1))
229 (%when
(%
2< ,var
,max
) ,label
)
230 ;; fixme: make sure var is still valid, and = max while evaluating result
239 #+nil
(defmethod scompile-cons ((car (eql 'and
)) cdr
)
242 (1 (scompile (first cdr
)))
244 (let ((true-label (gensym "true-"))
245 (false-label (gensym "false-")))
247 (loop for first
= t then nil
249 unless first collect
`(:pop
)
252 collect
`(:if-false
,false-label
))
253 `((:jump
,true-label
)
254 (:%dlabel
,false-label
)
257 (:%dlabel
,true-label
)))))))
260 ;;(scompile '(and 1))
261 ;;(scompile '(and 1 2))
264 (define-special* %array
(args)
265 ;; (%array ... ) -> array
266 `(,@(loop for i in args
267 append
(scompile i
)) ;; calculate args
268 (:new-array
,(length args
))))
271 (define-special %error
(value)
275 #+nil
(define-special %typep
(object type
)
276 `(,@(scompile object
)
279 (define-special %typep
(object type
)
280 `(,@(scompile object
)
281 (:get-lex
,(or (swf-name (find-swf-class type
)) type
))
285 (define-special %type-of
(object)
286 `(,@(scompile object
)
290 ;;; block/return-from
292 ;;; store list of blocks in context, each block has cleanup code and a jump target?
294 ;; return-from needs to be careful with stack, if it isn't just
295 ;; calling :Return-foo
298 (block foo
(return-from foo
1))
299 push block foo
, label
= (gensym block-foo
)
301 ,@compile return-value
307 (block bleh
(unwind-protect (return-from bleh
1) 2))
308 push block bleh
, label1
= gensym
309 push block uwp
, label2
= gensym
, cleanup
= gensym
310 ,@compile return-value
= 1
311 set-local foo
<index of goto to come back here
>
316 ,@compile cleanup
= 2
318 computed-goto back to %foo
325 (block bleh
(unwind-protect (unwind-protect 1 2) 3))
329 (define-special block
(name &body body
)
330 (let ((end (gensym "BLOCK-END-")))
331 (with-nested-lambda-block ((cons name
(make-lambda-block name end nil end
))
333 `(,@(scompile `(progn ,@body
))
334 (:set-local
,(get-lambda-local-index end
))
336 (:get-local
,(get-lambda-local-index end
))))))
338 (define-special %flet
((fn-name (&rest fn-args
) &body fn-body
) &body body
)
339 "limited version of flet, only handles 1 function, can't manipulate
340 the function directly, can only call it within the current function,
341 only normal args (no &rest,&key,&optional,etc)
342 call with %flet-call, which sets up hidden return label arg
344 ;; todo: handle multiple functions?
345 ;; fixme:would be nicer to put these at the end with the continuation table,
346 ;; but just compiling inline with a jump over it for now...
347 (let* ((end-label (gensym "%FLET-END-"))
348 (return-arg (gensym "%FLET-CONTINUATION-"))
349 (locals (loop for arg in
(cons return-arg fn-args
)
350 for j from
(last-local-index)
351 collect
(cons arg j
))))
352 ;; locals for a flet are ugly, since they need to keep their
353 ;; indices allocated during body, but names are only valid during
354 ;; fn-body, so we wrap both in with-local-vars, but kill the names
356 ;; we also add an implicit 'return' param to specify the continuation
357 (with-local-vars (locals)
358 ;;fixme: hack- write real code for this
359 (push (cons fn-name locals
) (%flets
*current-lambda
*))
362 ;; load parameters into regs
363 #+nil
,@(loop for
(nil . i
) in locals
364 collect
`(:set-local
,i
) into temp
365 finally
(return (nreverse temp
)))
366 ;; compile %flet body
367 ,@(scompile `(progn ,@fn-body
))
368 ;; store return value
369 (:set-local
,(get-lambda-local-index (local-return-var *current-lambda
*)))
370 ;; push return address index
371 (:get-local
,(get-lambda-local-index return-arg
))
372 (:set-local
,(get-lambda-local-index (continuation-var *current-lambda
*)))
374 ,@(loop for
(nil . i
) in locals
376 ;; return through continuation table
377 (:jump
,(continuation-var *current-lambda
*))
378 ;; remove local variable names from current scope (keeping indices used)
379 ,@(progn (kill-lambda-local-names fn-args
)
381 (:%dlabel
,end-label
)
383 ,@(scompile `(progn ,@body
))))))
385 (define-special call-%flet
(name &rest args
)
386 (let* ((continuation-label (gensym "CALL-%FLET-CONTINUATION-"))
387 (continuation-index (add-lambda-local-continuation continuation-label
))
388 (arg-indices (cdr (assoc name
(%flets
*current-lambda
*)))))
389 `((:push-int
,continuation-index
)
391 (:set-local
,(cdr (car arg-indices
)))
392 ,@(loop for arg in args
393 for
(nil . i
) in
(cdr arg-indices
)
394 append
(scompile arg
)
395 collect
`(:set-local
,i
))
396 (:comment
"call-%flet" ,name
,(%flets
*current-lambda
*) ,(unless name
(break)))
398 ;; need real label instead of dlabel, since we jump backwards
399 ;; from lookupswitch at end
400 (:%label
,continuation-label
)
402 (:get-local
,(get-lambda-local-index (local-return-var *current-lambda
*))))))
404 (define-special return-from
(name &optional value
)
405 (let ((block (get-lambda-block name
))
406 (cleanups (get-lambda-cleanups name
)))
408 (:set-local
,(get-lambda-local-index (return-var block
)))
409 ,@(loop for i in cleanups
410 collect
`(:comment
"return-from cleanup" ,i
,cleanups
,(blocks *current-lambda
*))
412 collect
`(:comment
"return-from cleanup done")
414 (:jump
,(end-label block
)))))
416 (define-special prog1
(value-form &body body
)
417 (let ((temp (gensym "PROG1-VALUE-")))
419 `(let ((,temp
,value-form
))
424 (define-special %with-cleanup
((name code
) form
)
425 (with-cleanup (name code
)
428 (define-special unwind-protect
(protected &body cleanup
)
429 (let ((cleanup-name (gensym "UWP-CLEANUP-")))
431 `(%flet
(,cleanup-name
() ,@cleanup
)
432 (%with-cleanup
(,cleanup-name
(call-%flet
,cleanup-name
))
435 (call-%flet
,cleanup-name
)))))))
437 (define-special* list
(rest)
438 (labels ((expand-rest (rest)
440 (list 'cons
(car rest
) (expand-rest (cdr rest
)))
442 (scompile (expand-rest rest
))))
443 ;;(scompile '(list (list 1) (list 2)))
444 ;;(scompile '(list 1))
445 ;;(scompile '(quote (1 2 3)))
446 ;;(scompile '(list '(list 1 2 3)))
448 (define-special* list
* (rest)
449 (labels ((expand-rest (rest)
450 (if (consp (cdr rest
))
451 (list 'cons
(car rest
) (expand-rest (cdr rest
)))
454 (error "not enough arguments to LIST*"))
455 (scompile (expand-rest rest
))))
457 ;;; internal aref, handles single dimensional flash::Array
458 (define-special %aref-1
(array index
)
461 (:get-property
(:multiname-l
"" ""))))
463 (swf-defmacro aref
(array &rest subscripts
)
465 (if (= 1 (length subscripts
))
467 (if (%typep
,a %flash
:array
)
468 (%aref-1
,a
,(first subscripts
))
469 (if (%typep
,a %flash
:string
)
470 (%flash
:char-at
,a
1)
471 (%aref-n
,array
,@subscripts
))))
472 `(%aref-n
,array
,@subscripts
))))
474 (define-special %set-aref-1
(array index value
)
478 (:set-property
(:multiname-l
"" ""))))
480 ;;; temporary hack to get inlined cons/car/cdr, speeds up tests noticeably
481 ;;; types and better compilation should give a few orders of magnitude though
482 (define-special cons
(a b
)
483 `((:find-property-strict cons-type
)
486 (:construct-prop cons-type
2)
489 ;;; coercing to cons-type before accessing slots is ~2x faster
490 ;;; using get-slot instead of get-property is maybe a few % faster
491 ;;; checking type explicitly is slow, so just using built-in check for now
492 ;;; (which works, but doesn't throw the CL specified error type)
493 ;;; :get-lex might be the slow part, so putting cons-type in a global
494 ;;; might help speed of proper type check
495 (define-special car
(a) ;;; FIXME: handle non-cons properly
496 (let ((temp (gensym "CAR-TEMP-")))
503 #+nil
(:get-property %car
)
506 (define-special cdr
(a) ;;; FIXME: handle non-cons properly
507 (let ((temp (gensym "CDR-TEMP-")))
514 #+nil
(:get-property %cdr
)
519 ;;(scompile '(list* 1 2 3 4 5))
520 ;;(scompile '(list* 1))
522 (define-special function
(arg &optional object
)
523 ;; fixme: not all branches tested yet...
526 ;; if OPERATOR is a known method, call with %call-property
527 ;; (prop obj args...) === obj.prop(args)
528 ((setf tmp
(find-swf-method arg
*symbol-table
*))
529 (break "f-s-m ~s" tmp
)
530 (scompile `(%get-property
,(swf-name tmp
) ,object
)))
532 ;; if OPERATOR is a known static method, call with %call-lex-prop
533 ;; (prop obj args...) === obj.prop(args)
534 ((setf tmp
(find-swf-static-method arg
*symbol-table
*))
535 (scompile `(%get-lex-prop
,(first tmp
) ,(second tmp
))))
537 ;; todo: decide if we should do something for the pretend accessors?
539 ;; normal function call, find-prop-strict + call-property
540 ((setf tmp
(find-swf-function arg
*symbol-table
*))
541 (break "f-s-f ~s" tmp
)
542 (scompile `(%get-property-without-object
,tmp
)))
544 ;; default = normal call?
545 ;; fixme: might be nicer if we could detect unknown functions
547 (scompile `(%get-property-without-object
,arg
))))))
549 (define-special quote
(object)
552 #+nil
(dump-defun-asm (&arest rest
) 'a
)
553 #+nil
(dump-defun-asm (&arest rest
) '1)
556 #+nil
(with-lambda-context ()
557 (scompile '(block foo
2 (if nil
(return-from foo
4) 5) 3)))
561 (avm2-asm::avm2-disassemble
563 (avm2-asm::with-assembler-context
564 (avm2-asm::assemble-method-body
565 (with-simple-lambda-context ()
568 (scompile '(%flet
(bleh (a b c
) (+ a b c
))
569 (+ (call-%flet bleh
1 2 3)
570 (call-%flet bleh
5 6 7))))
571 (compile-lambda-context-cleanup 'foo
)))))))
575 (avm2-asm::avm2-disassemble
577 (avm2-asm::with-assembler-context
578 (avm2-asm::assemble-method-body
579 (dump-defun-asm () (let ((s2 "<"))
583 (return-from foo
"-ret-")