(module xform mzscheme (require mzlib/list mzlib/etc mzlib/process) (provide xform) (define (xform quiet? cpp file-in file-out keep-lines? palm? pgc? pgc-really? precompiling-header? precompiled-header show-info? output-depends-info? gc-variable-stack-through-funcs?) (parameterize ([current-output-port (current-output-port)] ; because we mutate these... [error-escape-handler (error-escape-handler)] [current-inspector (current-inspector)]) (begin-with-definitions (define power-inspector (current-inspector)) (current-inspector (make-inspector)) (define check-arith? #t) ;; Selects whether to reset GC_variable_stack on return, ;; or to just reset it on every call. (define callee-restore? #t) (define palm-out #f) (define (filter-false s) (if (equal? s "-") #f s)) (define source-is-c++? (regexp-match #rx"([.]cc$)|([.]cxx$)" file-in)) (define (change-suffix filename new) (path-replace-suffix filename new)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; "AST" structures ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-struct tok (n line file) (make-inspector)) (define-struct (sysheader-tok tok) ()) (define-struct (seq tok) (close in) (make-inspector)) (define-struct (parens seq) () (make-inspector)) (define-struct (brackets seq) ()) (define-struct (braces seq) ()) (define-struct (callstage-parens parens) ()) (define-struct (creation-parens parens) ()) (define-struct (nosrc-parens parens) ()) (define-struct (call tok) (func args live tag nonempty?)) ;; a converted function call (define-struct (block-push tok) (vars tag super-tag top?)) (define-struct (note tok) (s)) (define-struct (nested-setup tok) ()) (define nested-pushable (make-nested-setup 'nested #f #f)) (define no-nested-pushable (make-nested-setup 'no-nested #f #f)) (define undefine-nested-pushable (make-nested-setup 'undefine #f #f)) (define-struct pragma (s file line)) ;; For very long lists, it's worth the effort to use a vector instead ;; of a list to save space: (define (seq->list s) (if (vector? s) (vector->list s) s)) (define (list->seq s) (if (or (null? s) (null? (cdr s)) (null? (cddr s))) s (list->vector s))) (define seqce vector) ;; A cheap way of getting rid of unneeded prototypes: (define used-symbols (make-hash-table)) (hash-table-put! used-symbols (string->symbol "GC_variable_stack") 1) (hash-table-put! used-symbols (string->symbol "GC_cpp_delete") 1) (hash-table-put! used-symbols (string->symbol "GC_get_variable_stack") 1) (hash-table-put! used-symbols (string->symbol "GC_set_variable_stack") 1) (hash-table-put! used-symbols (string->symbol "memset") 1) (hash-table-put! used-symbols (string->symbol "scheme_thread_local_key") 1) (hash-table-put! used-symbols (string->symbol "scheme_thread_locals") 1) (hash-table-put! used-symbols (string->symbol "pthread_getspecific") 1) ;; For dependency tracking: (define depends-files (make-hash-table 'equal)) (define (make-triple v src line sysheader?) (when (symbol? v) (hash-table-put! used-symbols v (add1 (hash-table-get used-symbols v (lambda () 0))))) (when (and src output-depends-info?) (hash-table-put! depends-files src #t)) (if sysheader? (make-sysheader-tok v line src) (make-tok v line src))) (define (make-a-seq opener src line body) ((case opener [(#\() make-parens] [(#\[) make-brackets] [(#\{) make-braces]) (case opener [(#\() "("] [(#\[) "["] [(#\{) "{"]) line src (case opener [(#\() ")"] [(#\[) "]"] [(#\{) "}"]) (list->seq body))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tokenizer ;; Relies on make-triple, make-a-seq, and make-pragma ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (trans pattern) (byte-regexp (string->bytes/utf-8 (format "^(~a)" pattern)))) (define (translations . t) (let loop ([t t]) (if (null? t) null (let ([pattern (car t)] [result (cadr t)]) (cons (cons (trans pattern) result) (loop (cddr t))))))) (define (a-regexp-match-positions re s p) (regexp-match-positions re s p)) (define seqs string-append) (define startseq seqs) (define (arbno s) (format "(?:~a)*" s)) (define (arbno/ s) (format "~a*" s)) (define (one+ s) (format "(?:~a)+" s)) (define (one+/ s) (format "~a+" s)) (define (maybe s) (format "(?:~a)?" s)) (define (maybe/ s) (format "~a?" s)) (define (alt a b) (format "~a|~a" a b)) (define (alt* . l) (let loop ([l l]) (if (null? (cdr l)) (format "(?:~a)" (car l)) (format "(?:~a)|~a" (car l) (loop (cdr l)))))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (line-comment s p) (let loop ([p (add1 p)]) (let ([c (bytes-ref s p)]) (if (or (equal? c 10) (equal? c 13)) (add1 p) (loop (add1 p)))))) (define re:line #rx#"^#[^\n\r]* ([0-9]+) \"([^\"]*)\"([^\r\n]*)" ) (define re:pragma #rx#"^#pragma ([^\r\n]*)") (define (do-cpp s p) (let ([m (regexp-match re:line s p)]) (when m (set! source-line (string->number (bytes->string/utf-8 (cadr m)))) (set! source-file (caddr m)) (set! source-sysheader? (regexp-match? #px#"\\b3\\b" (cadddr m))))) (let ([pragma (regexp-match re:pragma s p)]) (if (and pragma (not (regexp-match-positions re:boring (car pragma)))) (values (make-pragma (cadr pragma) source-file source-line) (line-comment s p)) (values #f (line-comment s p))))) (define (result s) (make-triple s source-file source-line source-sysheader?)) (define (symbol s) (result (string->symbol (bytes->string/utf-8 s)))) (define re:octal #rx#"^0[0-9]+$") (define re:int #rx#"^[0-9]*$") (define (number s) (result (cond [(regexp-match-positions re:octal s) (string->number (bytes->string/utf-8 s) 8)] [(regexp-match-positions re:int s) (string->number (bytes->string/utf-8 s))] [else (string->symbol (bytes->string/utf-8 s))]))) (define (character s) (count-newlines s) (symbol s)) (define (character? s) (and (symbol? s) (regexp-match #rx"'[\\]?.'" (symbol->string s)))) (define (mk-string s) (count-newlines s) (result (bytes->string/utf-8 (subbytes s 1 (sub1 (bytes-length s)))))) (define (start s) 'start) (define (stop s) #f) (define (count-newlines s) (let loop ([p (sub1 (bytes-length s))]) (unless (= p -1) (when (= 10 (bytes-ref s p)) (set! source-line (add1 source-line))) (loop (sub1 p))))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define D "[0-9]") (define L "[a-zA-Z_]") (define H "[a-fA-F0-9]") (define E (format "[Ee][+-]?~a+" D)) (define FS "(?:f|F|l|L)") (define IS "(?:u|U|l|L)*") (define symbol-complex (trans (seqs L (arbno (alt L D))))) (define number-complex (trans (alt* (seqs (arbno/ D) "[.]" (one+/ D) (maybe E) (maybe/ FS)) (seqs (one+/ D) "[.]" (arbno D) (maybe E) (maybe/ FS)) (seqs (one+/ D) E (maybe/ FS)) "0x1[.]0p2047" ;; strange thing in huge_val.h (seqs "0" "[xX]" (one+/ H) IS) ;; hex (seqs "0" (one+/ D) IS) ;; octal (seqs (one+/ D) IS)))) ;; integer (define char-complex (trans (seqs (maybe L) "'([^\\']|\\\\.)+'"))) (define string-complex (trans (seqs (maybe L) "\"([^\\\"]|\\\\.)*\""))) (define simple-table (make-vector 256 #f)) (define (simple-translations . l) (let loop ([l l]) (unless (null? l) (loop (cddr l)) (let* ([pattern (car l)] [result (cadr l)] [n (bytes-ref pattern 0)]) (vector-set! simple-table n (cons (list* pattern (bytes-length pattern) result) (or (vector-ref simple-table n) null))))))) (simple-translations #"#" symbol #"##" symbol #"..." symbol #">>=" symbol #"<<=" symbol #"+=" symbol #"-=" symbol #"*=" symbol #"/=" symbol #"%=" symbol #"&=" symbol #"^=" symbol #"|=" symbol #">>" symbol #"<<" symbol #"++" symbol #"--" symbol #"->" symbol #"&&" symbol #"||" symbol #"<=" symbol #">=" symbol #"==" symbol #"!=" symbol #";" symbol #"{" start #"}" stop #"," symbol #"::" symbol #":" symbol #"=" symbol #"(" start #")" stop #"[" start #"]" stop #"." #f ; => symbol/num #"&" symbol #"!" symbol #"~" symbol #"-" #f ; => symbol/num #"+" #f ; => symbol/num #"*" symbol #"/" symbol #"%" symbol #"<" symbol #">" symbol #"^" symbol #"|" symbol #"?" symbol) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define source-file #f) (define source-line 0) (define source-sysheader? #f) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (read-all p) (let loop ([l null]) (let ([s (read-bytes 4096 p)]) (if (eof-object? s) (apply bytes-append (reverse l)) (loop (cons s l)))))) (define (tokenize) (let* ([s (read-all (current-input-port))] [len (bytes-length s)]) (let loop ([p 0][result null]) (if (= p len) (cons (reverse result) p) (let ([char (bytes-ref s p)]) (when (eq? char 10) (set! source-line (add1 source-line))) (cond [(char-whitespace? (integer->char char)) (loop (add1 p) result)] [(eq? char (char->integer #\#)) ;; We assume only #-based preprocessor left (let-values ([(pragma p) (do-cpp s p)]) (if pragma (loop p (cons pragma result)) (loop p result)))] [else (let ([simple (let ([sl (vector-ref simple-table char)]) (and sl (ormap (lambda (t) (and (or (= 1 (cadr t)) (bytes=? (car t) (subbytes s p (+ p (cadr t))))) (let ([f (cddr t)]) (if f (cons (f (car t)) (+ p (cadr t))) (let ([m (regexp-match-positions number-complex s p)]) (if m (cons (number (subbytes s (caar m) (cdar m))) (cdar m)) (cons (symbol (car t)) (+ p (cadr t))))))))) sl)))]) (cond [(not simple) (cond [(regexp-match-positions symbol-complex s p) => (lambda (m) (loop (cdar m) (cons (symbol (subbytes s (caar m) (cdar m))) result)))] [(regexp-match-positions number-complex s p) => (lambda (m) (loop (cdar m) (cons (number (subbytes s (caar m) (cdar m))) result)))] [(regexp-match-positions char-complex s p) => (lambda (m) (loop (cdar m) (cons (character (subbytes s (caar m) (cdar m))) result)))] [(regexp-match-positions string-complex s p) => (lambda (m) (loop (cdar m) (cons (mk-string (subbytes s (caar m) (cdar m))) result)))] [else (error 'c-tokenize "strange: ~e ~e" p (subbytes s p (min len (+ p 100))))])] [(not (car simple)) (cons (reverse result) (cdr simple))] [(eq? (car simple) 'start) (let ([sf source-file] [sl source-line] [sub (loop (cdr simple) null)]) (loop (cdr sub) (cons (make-a-seq (integer->char (bytes-ref s p)) sf sl (car sub)) result)))] [simple (loop (cdr simple) (cons (car simple) result))]))])))))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Pre-process and S-expr-ize ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (verbose f) (if quiet? f (lambda args (printf "xform-cpp: ~a\n" args) (apply f args)))) (define (maybe-add-exe p) (cond [(and (eq? 'windows (system-type)) (not (regexp-match? #rx"[.]exe$" p))) (format "~a.exe" p)] [else p])) ;; To run cpp: (define process2 (if (eq? (system-type) 'windows) (lambda (s) (let ([split (let loop ([s s]) (let ([m (regexp-match #rx"([^ ]*) +(.*)" s)]) (if m (cons (cadr m) (loop (caddr m))) (list s))))]) (apply (verbose process*) (find-executable-path (maybe-add-exe (car split)) #f) (cdr split)))) (verbose process))) (define cpp-process (if (string? cpp) (process2 (format "~a~a~a ~a" cpp (if pgc? (if pgc-really? " -DMZ_XFORM -DMZ_PRECISE_GC" " -DMZ_XFORM") "") (if callee-restore? " -DGC_STACK_CALLEE_RESTORE" "") file-in)) (apply (verbose process*) (append cpp (if pgc-really? '("-DMZ_XFORM" "-DMZ_PRECISE_GC") '("-DMZ_XFORM")) (if callee-restore? '("-DGC_STACK_CALLEE_RESTORE") null) (list file-in))))) (close-output-port (cadr cpp-process)) (define (mk-error-thread proc) (thread (lambda () (let loop () (let ([l (read-bytes-line (list-ref proc 3) 'any)]) (unless (eof-object? l) (eprintf "~a\n" l) (loop)))) (close-input-port (list-ref proc 3))))) (define cpp-error-thread (mk-error-thread cpp-process)) ;; Pipe cpp results through here; we insert a filter ;; between the pipe ends. (define-values (local-ctok-read local-ctok-write) (make-pipe 100000)) (define recorded-cpp-out (and precompiling-header? (open-output-file (change-suffix file-out #".e") 'truncate))) (define recorded-cpp-in (and precompiled-header (open-input-file (change-suffix precompiled-header #".e")))) (define re:boring #rx#"^(?:(?:[ \t]*)|(?:# .*)|(?:#line .*)|(?:#pragma implementation.*)|(?:#pragma interface.*)|(?:#pragma once)|(?:#pragma warning.*)|(?:#ident.*))$") (define re:uninteresting #rx#"^(?:(?:[ \t]*)|(?:# .*)|(?:#line .*)|(?:#pragma implementation.*)|(?:#pragma interface.*)|(?:#pragma once)|(?:#pragma GCC diagnostic.*)|(?:#pragma warning.*)|(?:#ident.*))$") (define (skip-to-interesting-line p) (let ([l (read-bytes-line p 'any)]) (cond [(eof-object? l) l] [(regexp-match-positions re:uninteresting l) (skip-to-interesting-line p)] [else l]))) (when recorded-cpp-in ;; Skip over common part: (let loop ([lpos 1]) (let ([pl (read-bytes-line recorded-cpp-in 'any)]) (unless (eof-object? pl) (let ([l (skip-to-interesting-line (car cpp-process))]) (unless (equal? pl l) (error 'precompiled-header "line mismatch with precompiled: ~s (line ~a) versus ~s" pl lpos l)) (loop (add1 lpos)))))) (close-input-port recorded-cpp-in)) ;; cpp output to ctok input, also writes filtered lines to ;; cpp-out when reading a recompiled header (thread (lambda () (if recorded-cpp-out ;; line-by-line, so we can filter: (begin (let loop () (let ([l (read-bytes-line (car cpp-process) 'any)]) (unless (eof-object? l) (unless (regexp-match-positions re:boring l) (display l recorded-cpp-out) (newline recorded-cpp-out)) (display l local-ctok-write) (newline local-ctok-write) (loop)))) (close-output-port recorded-cpp-out) (close-input-port (car cpp-process)) (close-output-port local-ctok-write)) ;; block copy: (let ([s (make-bytes 4096)]) (let loop () (let ([l (read-bytes-avail! s (car cpp-process))]) (unless (eof-object? l) (write-bytes s local-ctok-write 0 l) (loop)))) (close-input-port (car cpp-process)) (close-output-port local-ctok-write))))) (define e-raw #f) (define read-thread (thread (lambda () (parameterize ([current-input-port local-ctok-read]) (set! e-raw (car (tokenize))))))) ((list-ref cpp-process 4) 'wait) (thread-wait cpp-error-thread) (when (eq? ((list-ref cpp-process 4) 'status) 'done-error) (error 'xform "cpp failed")) (thread-wait read-thread) (set! read-thread #f) (when (exn? e-raw) (raise e-raw)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Output and error-handling ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (current-output-port (if file-out (open-output-file file-out 'truncate) (make-output-port 'dev/null always-evt (lambda (s st ed f?) (- ed st)) void))) (let ([eh (error-escape-handler)]) (error-escape-handler (lambda () (close-output-port (current-output-port)) (current-output-port (current-error-port)) (when file-out (delete-file file-out)) (eh)))) (define exit-with-error? #f) (define (log-error format . args) (eprintf "Error ") (apply eprintf format args) (newline (current-error-port)) (set! exit-with-error? #t)) (define log-warning log-error) (define map-port (if palm-out (open-output-file palm-out 'truncate) #f)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Output common defns ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define per-block-push? #t) (define gc-var-stack-mode (let loop ([e-raw e-raw]) (ormap (lambda (e) (cond [(and (pragma? e) (regexp-match #rx"GC_VARIABLE_STACK_THOUGH_TABLE" (pragma-s e))) 'table] [(and (tok? e) (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_THREAD_LOCAL)) 'thread-local] [(and (tok? e) (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_GETSPECIFIC)) 'getspecific] [(and (tok? e) (eq? (tok-n e) 'XFORM_GC_VARIABLE_STACK_THROUGH_FUNCTION)) 'function] [(braces? e) (loop (seq->list (seq-in e)))] [else #f])) e-raw))) ;; The code produced by xform uses a number of macros. These macros ;; make the transformation about a little easier to debug, and they ;; enable experimentation with different variable-registration ;; strategies without modifying the xform process. (when (and pgc? (not precompiled-header)) ;; Setup GC_variable_stack macro (printf (case gc-var-stack-mode [(table) "#define GC_VARIABLE_STACK (scheme_extension_table->GC_variable_stack)\n"] [(getspecific) "#define GC_VARIABLE_STACK (((Thread_Local_Variables *)pthread_getspecific(scheme_thread_local_key))->GC_variable_stack_)\n"] [(function) "#define GC_VARIABLE_STACK ((scheme_get_thread_local_variables())->GC_variable_stack_)\n"] [(thread-local) "#define GC_VARIABLE_STACK ((&scheme_thread_locals)->GC_variable_stack_)\n"] [else "#define GC_VARIABLE_STACK GC_variable_stack\n"])) (if gc-variable-stack-through-funcs? (begin (printf "#define GET_GC_VARIABLE_STACK() GC_get_variable_stack()\n") (printf "#define SET_GC_VARIABLE_STACK(v) GC_set_variable_stack(v)\n")) (begin (printf "#define GET_GC_VARIABLE_STACK() GC_VARIABLE_STACK\n") (printf "#define SET_GC_VARIABLE_STACK(v) (GC_VARIABLE_STACK = (v))\n"))) ;; Declare stack-registration record of a particular size: (printf (string-append "#define PREPARE_VAR_STACK(size) void *__gc_var_stack__[size+2]; __gc_var_stack__[0] = GET_GC_VARIABLE_STACK();" (if callee-restore? " SET_GC_VARIABLE_STACK(__gc_var_stack__);" "") "\n")) ;; Same, but in a function where the number of registered variables ;; never changes within the procedure (i.e., in nested blocks): (printf "#define PREPARE_VAR_STACK_ONCE(size) PREPARE_VAR_STACK(size); __gc_var_stack__[1] = (void *)size;\n") ;; Full setup to use before a function call, normally used with FUNCCALL: (printf (string-append "#define SETUP(x) (" (if callee-restore? "" "SET_GC_VARIABLE_STACK(__gc_var_stack__), ") "__gc_var_stack__[1] = (void *)x)\n")) ;; Debugging support: (printf "#ifdef MZ_3M_CHECK_VAR_STACK\n") (printf "static int _bad_var_stack_() { *(long *)0x0 = 1; return 0; }\n") (printf "# define CHECK_GC_V_S ((GC_VARIABLE_STACK == __gc_var_stack__) ? 0 : _bad_var_stack_()),\n") (printf "#else\n") (printf "# define CHECK_GC_V_S /*empty*/\n") (printf "#endif\n") ;; Call a function where the number of registered variables can change in ;; nested blocks: (printf "#define FUNCCALL_each(setup, x) (CHECK_GC_V_S setup, x)\n") ;; The same, but a "tail" call: (printf "#define FUNCCALL_EMPTY_each(x) (SET_GC_VARIABLE_STACK((void **)__gc_var_stack__[0]), x)\n") ;; The same, but the number of registered variables for this call is definitely ;; the same as for the previous call: (printf (if callee-restore? "#define FUNCCALL_AGAIN_each(x) (CHECK_GC_V_S x)\n" "#define FUNCCALL_AGAIN_each(x) FUNCCALL_each(SET_GC_VARIABLE_STACK(__gc_var_stack__), x)\n")) ;; As above, but when the number of registered variables never changes ;; within a procedure: (printf "#define FUNCCALL_once(setup, x) FUNCCALL_AGAIN_each(x)\n") (printf "#define FUNCCALL_EMPTY_once(x) FUNCCALL_EMPTY_each(x)\n") (printf "#define FUNCCALL_AGAIN_once(x) FUNCCALL_AGAIN_each(x)\n") ;; Register a particular variable locally: (printf "#define PUSH(v, x) (__gc_var_stack__[x+2] = (void *)&(v))\n") ;; Register a particular array variable locally: (printf (string-append "#define PUSHARRAY(v, l, x) (__gc_var_stack__[x+2] = (void *)0, __gc_var_stack__[x+3] = (void *)&(v), " "__gc_var_stack__[x+4] = (void *)l)\n")) ;; Wraps code to setup a block's variables: (printf "#define BLOCK_SETUP_TOP(x) ~a\n" (if per-block-push? "x" "/* skipped */")) ;; Same, but specifically in a function where nested blocks register ;; extra variables: (printf "#define BLOCK_SETUP_each(x) BLOCK_SETUP_TOP(x)\n") ;; Same, but specifically in a function where nested blocks DO NOT ;; register extra variables: (printf "#define BLOCK_SETUP_once(x) /* no effect */\n") ;; Wrap a normal return: (printf (if callee-restore? "#define RET_VALUE_START return (__ret__val__ = \n" "#define RET_VALUE_START return\n")) (printf (if callee-restore? "#define RET_VALUE_END , SET_GC_VARIABLE_STACK((void **)__gc_var_stack__[0]), __ret__val__)\n" "#define RET_VALUE_END \n")) ;; Wrap a return where the value is produced by a FUNCCALL_EMPTY expression: (printf "#define RET_VALUE_EMPTY_START return\n") (printf "#define RET_VALUE_EMPTY_END \n") ;; Replacement for non-value return: (printf "#define RET_NOTHING { SET_GC_VARIABLE_STACK((void **)__gc_var_stack__[0]); return; }\n") ;; A non-value return inserted at the end of a void-returning function: (printf "#define RET_NOTHING_AT_END RET_NOTHING\n") ;; Declare a temp variable to hold the return value of the indicated type: (printf (if callee-restore? "#define DECL_RET_SAVE(type) type __ret__val__;\n" "#define DECL_RET_SAVE(type) /**/\n")) ;; Value used to initialize pointer variables: (printf "#define NULLED_OUT 0\n") ;; Macro to initialize a pointer array: (printf "#define NULL_OUT_ARRAY(a) memset(a, 0, sizeof(a))\n") ;; Annotation that normally disappears: (printf "#define GC_CAN_IGNORE /**/\n") (printf "#define XFORM_CAN_IGNORE /**/\n") (printf "#define __xform_nongcing__ /**/\n") ;; Another annotation to protect against GC conversion: (printf "#define HIDE_FROM_XFORM(x) x\n") (printf "#define XFORM_HIDE_EXPR(x) x\n") (printf "#define HIDE_NOTHING_FROM_XFORM() /**/\n") ;; In case a conversion is unnecessary where we have this annotation: (printf "#define START_XFORM_SKIP /**/\n") (printf "#define END_XFORM_SKIP /**/\n") (printf "#define START_XFORM_SUSPEND /**/\n") (printf "#define END_XFORM_SUSPEND /**/\n") (printf "#define XFORM_START_SKIP /**/\n") (printf "#define XFORM_END_SKIP /**/\n") (printf "#define XFORM_START_SUSPEND /**/\n") (printf "#define XFORM_END_SUSPEND /**/\n") (printf "#define XFORM_SKIP_PROC /**/\n") ;; For avoiding warnings: (printf "#define XFORM_OK_PLUS +\n") (printf "#define XFORM_OK_MINUS -\n") (printf "#define XFORM_TRUST_PLUS +\n") (printf "#define XFORM_TRUST_MINUS -\n") (printf "#define XFORM_OK_ASSIGN /**/\n") (printf "\n") ;; C++ cupport: (printf "#define NEW_OBJ(t) new (UseGC) t\n") (printf "#define NEW_ARRAY(t, array) (new (UseGC) t array)\n") (printf "#define NEW_ATOM(t) (new (AtomicGC) t)\n") (printf "#define NEW_PTR(t) (new (UseGC) t)\n") (printf "#define NEW_ATOM_ARRAY(t, array) (new (AtomicGC) t array)\n") (printf "#define NEW_PTR_ARRAY(t, array) (new (UseGC) t* array)\n") (printf "#define DELETE(x) (delete x)\n") (printf "#define DELETE_ARRAY(x) (delete[] x)\n") (printf (if callee-restore? "#define XFORM_RESET_VAR_STACK /* empty */\n" "#define XFORM_RESET_VAR_STACK SET_GC_VARIABLE_STACK((void **)__gc_var_stack__[0]);\n")) (unless pgc-really? (printf "#include \"cgc2.h\"\n")) (printf "\n")) (when (and pgc? precompiled-header) (printf "#include \"~a\"\n" (let-values ([(base name dir?) (split-path precompiled-header)]) (path->string name)))) (when palm? (printf "#include \"segmap.h\"\n")) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Structures and constants ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; vtype and its substructs describe the shape of a local variable: (define-struct vtype ()) (define-struct (pointer-type vtype) (base stars)) (define-struct (array-type vtype) (count)) (define-struct (struc-type vtype) (struct)) (define-struct (struct-array-type struc-type) (count)) (define-struct (union-type vtype) ()) (define-struct (non-pointer-type vtype) (base)) ;; A live-var-info struct is threaded through the conversion process ;; on a function body. It keeps information about which variables ;; are live, which variables were invented along the way, how long ;; the __gc_var_stack__ array needs to be, and so on. (define-struct live-var-info (tag maxlive maxpush vars new-vars pushed-vars num-calls num-noreturn-calls num-empty-calls nonempty-calls?)) ;; A function prototype record: (define-struct prototype (type args static? pointer? pointer?-determined?)) ;; A C++ class record: (define-struct c++-class (parent parent-name prototyped top-vars)) ;; Symbol constants: (define semi (string->symbol ";")) (define START_XFORM_SKIP (string->symbol "START_XFORM_SKIP")) (define END_XFORM_SKIP (string->symbol "END_XFORM_SKIP")) (define START_XFORM_SUSPEND (string->symbol "START_XFORM_SUSPEND")) (define END_XFORM_SUSPEND (string->symbol "END_XFORM_SUSPEND")) (define Scheme_Object (string->symbol "Scheme_Object")) (define sElF (string->symbol "sElF")) (define NULLED_OUT (string->symbol "NULLED_OUT")) (define NULL_OUT_ARRAY (string->symbol "NULL_OUT_ARRAY")) (define gcMark (string->symbol "gcMark")) (define gcFixup (string->symbol "gcFixup")) (define gcMARK_TYPED (string->symbol "gcMARK_TYPED")) (define gcFIXUP_TYPED (string->symbol "gcFIXUP_TYPED")) (define Mark_Proc (string->symbol "Mark_Proc")) (define gcBYTES_TO_WORDS (string->symbol "gcBYTES_TO_WORDS")) (define GC_cpp_delete (string->symbol "GC_cpp_delete")) (define PRE_ALLOCATE (string->symbol "PRE_ALLOCATE")) (define NEW_OBJ (string->symbol "NEW_OBJ")) (define NEW_ARRAY (string->symbol "NEW_ARRAY")) (define NEW_ATOM (string->symbol "NEW_ATOM")) (define NEW_PTR (string->symbol "NEW_PTR")) (define NEW_ATOM_ARRAY (string->symbol "NEW_ATOM_ARRAY")) (define NEW_PTR_ARRAY (string->symbol "NEW_PTR_ARRAY")) (define DELETE (string->symbol "DELETE")) (define DELETE_ARRAY (string->symbol "DELETE_ARRAY")) (define CURRENT_NEW_THIS (string->symbol "CURRENT_NEW_THIS")) (define RESTORE_CURRENT_NEW_VAR_STACK (string->symbol "RESTORE_CURRENT_NEW_VAR_STACK")) (define XFORM_RESET_VAR_STACK (string->symbol "XFORM_RESET_VAR_STACK")) (define END_XFORM_ARITH (string->symbol "END_XFORM_ARITH")) (define START_XFORM_ARITH (string->symbol "START_XFORM_ARITH")) (define GC_CAN_IGNORE (string->symbol "GC_CAN_IGNORE")) (define RET_VALUE_START (string->symbol "RET_VALUE_START")) (define RET_VALUE_END (string->symbol "RET_VALUE_END")) (define RET_VALUE_EMPTY_START (string->symbol "RET_VALUE_EMPTY_START")) (define RET_VALUE_EMPTY_END (string->symbol "RET_VALUE_EMPTY_END")) (define RET_NOTHING (string->symbol "RET_NOTHING")) (define RET_NOTHING_AT_END (string->symbol "RET_NOTHING_AT_END")) (define DECL_RET_SAVE (string->symbol "DECL_RET_SAVE")) (define __attribute__ (string->symbol "__attribute__")) (define re:_stk_ (regexp "^_stk_")) ;; These don't act like functions, but we need to treat them ;; specially: (define setjmp-functions '(setjmp _setjmp scheme_setjmp scheme_mz_setjmp)) ;; The non-functions table identifies symbols to ignore when ;; finding function calls (define non-functions '(<= < > >= == != ! \| \|\| & && |:| ? % + - * / ^ >> << ~ #csXFORM_OK_PLUS #csXFORM_OK_MINUS #csXFORM_TRUST_PLUS #csXFORM_TRUST_MINUS = >>= <<= ^= += *= /= -= %= \|= &= ++ -- return if for while else switch case XFORM_OK_ASSIGN asm __asm __asm__ __volatile __volatile__ volatile __extension__ __typeof sizeof __builtin_object_size ;; These don't act like functions: setjmp longjmp _longjmp scheme_longjmp_setjmp scheme_mz_longjmp scheme_jit_longjmp scheme_jit_setjmp_prepare scheme_get_thread_local_variables pthread_getspecific ;; The following are functions, but they don't trigger GC, and ;; they either take one argument or no pointer arguments. ;; So we can ignore them: __get_errno_ptr ; QNX preprocesses errno to __get_errno_ptr strlen cos sin exp pow log sqrt atan2 isnan isinf fpclass _fpclass __fpclassify __fpclassifyf __fpclassifyl _isnan __isfinited __isnanl __isnan __isinff __isinfl isnanf isinff __isinfd __isnanf __isnand __isinf floor ceil round fmod modf fabs __maskrune _errno __errno isalpha isdigit isspace tolower toupper fread fwrite socket fcntl setsockopt connect send recv close __builtin_next_arg __builtin_saveregs __builtin_constant_p __builtin___CFStringMakeConstantString __error __errno_location __toupper __tolower __attribute__ __mode__ ; not really functions in gcc __iob_func ; VC 8 |GetStdHandle| |__CFStringMakeConstantString| _vswprintf_c scheme_make_small_bignum scheme_make_small_rational scheme_make_small_complex)) (define non-functions-table (let ([ht (make-hash-table)]) (for-each (lambda (s) (hash-table-put! ht s #f)) non-functions) ht)) (define args-unevaled '(sizeof __typeof __builtin_object_size)) (define args-unevaled-table (let ([ht (make-hash-table)]) (for-each (lambda (s) (hash-table-put! ht s #t)) args-unevaled) ht)) (define non-gcing-builtin-functions ;; The following don't need wrappers, but we need to check for ;; nested function calls because it takes more than one argument: (append '(memcpy memmove memcmp memset __builtin___memmove_chk __inline_memmove_chk __builtin___memcpy_chk __inline_memcpy_chk __builtin___memset_chk __inline_memset_chk __builtin___memcmp_chk __inline_memcmp_chk strcmp strcoll strcpy _mzstrcpy strcat __builtin_memset printf sprintf vsprintf vprintf strncmp read write) (map string->symbol '("XTextExtents" "XTextExtents16" "XDrawImageString16" "XDrawImageString" "XDrawString16" "XDrawString")))) (define non-gcing-functions (make-hash-table)) (for-each (lambda (name) (hash-table-put! non-gcing-functions name #t)) non-gcing-builtin-functions) (define non-returning-functions ;; The following functions never return, so the wrappers ;; don't need to push any variables: '(exit scheme_wrong_type scheme_wrong_number scheme_wrong_syntax scheme_wrong_count scheme_wrong_count_m scheme_wrong_rator scheme_read_err scheme_raise_exn scheme_signal_error scheme_raise_out_of_memory )) (define non-pointer-typedef-names ;; Under Windows, things like HANDLE and HWND, are not ;; malloced and could overlap with GCed areas. ;; Mac OS X has similar things. #cs '(HANDLE HWND HDC HMENU HBITMAP HBRUSH HPEN HFONT HPALETTE HRGN HICON HINSTANCE GLOBALHANDLE LOCALHANDLE HGLOBAL HLOCAL GrafPtr RgnHandle PixMapHandle Handle MenuHandle GDHandle WindowPtr DialogPtr ControlRef EventRef EventHandlerCallRef CGContextRef)) (define asm-commands ;; When outputting, add newline before these syms ;; (for __asm blocks in Windows) '(mov shl shld shr shrd sar lock setc add)) (define (get-constructor v) (cond [(creation-parens? v) make-creation-parens] [(parens? v) make-parens] [(brackets? v) make-brackets] [(braces? v) make-braces])) ;; gets the size of a variable in terms of the number of ;; __gc_var_stack__ slots it needs (define (get-variable-size vtype) (cond [(array-type? vtype) ;; 1 for "is an array", 1 for array size, 1 for array pointer 3] [(struc-type? vtype) (let ([size (let ([m (lookup-struct-def (struc-type-struct vtype))]) (apply + (map get-variable-size (map cdr (cdr m)))))]) (if (struct-array-type? vtype) (* size (struct-array-type-count vtype)) size))] [(vtype? vtype) 1] [else (error 'get-variable-size "not a vtype: ~e" vtype)])) (define (replace-live-vars live-vars new-live-vars) (make-live-var-info (live-var-info-tag live-vars) (live-var-info-maxlive live-vars) (live-var-info-maxpush live-vars) new-live-vars (live-var-info-new-vars live-vars) (live-var-info-pushed-vars live-vars) (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars))) (define gentag-count 0) (define gentag (lambda () (set! gentag-count (add1 gentag-count)) (format "XfOrM~a" gentag-count))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; State ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; See `used-symbols' above (define c++-classes null) ;; list of (cons symbol prototype) (define prototyped (make-parameter null)) ;; list of (cons symbol vtype) (define top-vars (make-parameter null)) ;; Accum top-level typedefs for pointers and non-pointers as a list-of-sym: (define pointer-types '()) (define non-pointer-types '(int char long unsigned intptr_t ulong uint uintptr_t void float double uchar wchar_t)) ;; Accum top-level struct decls as list of (cons sym (list (cons symbol vtype) ...)) (define struct-defs '()) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Marhsaling and unmarshaling ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define makers (make-hash-table)) (hash-table-put! makers 'struct:tok (cons 'make-tok make-tok)) (hash-table-put! makers 'struct:sysheader-tok (cons 'make-sysheader-tok make-sysheader-tok)) (hash-table-put! makers 'struct:seq (cons 'make-a-seq make-a-seq)) (hash-table-put! makers 'struct:parens (cons 'make-parens make-parens)) (hash-table-put! makers 'struct:brackets (cons 'make-brackets make-brackets)) (hash-table-put! makers 'struct:braces (cons 'make-braces make-braces)) (hash-table-put! makers 'struct:callstage-parens (cons 'make-callstage-parens make-callstage-parens)) (hash-table-put! makers 'struct:creation-parens (cons 'make-creation-parens make-creation-parens)) (hash-table-put! makers 'struct:nosrc-parens (cons 'make-nosrc-parens make-nosrc-parens)) (hash-table-put! makers 'struct:call (cons 'make-call make-call)) (hash-table-put! makers 'struct:block-push (cons 'make-block-push make-block-push)) (hash-table-put! makers 'struct:note (cons 'make-note make-note)) (hash-table-put! makers 'struct:vtype (cons 'make-vtype make-vtype)) (hash-table-put! makers 'struct:pointer-type (cons 'make-pointer-type make-pointer-type)) (hash-table-put! makers 'struct:array-type (cons 'make-array-type make-array-type)) (hash-table-put! makers 'struct:struc-type (cons 'make-struc-type make-struc-type)) (hash-table-put! makers 'struct:struct-array-type (cons 'make-struct-array-type make-struct-array-type)) (hash-table-put! makers 'struct:union-type (cons 'make-union-type make-union-type)) (hash-table-put! makers 'struct:non-pointer-type (cons 'make-non-pointer-type make-non-pointer-type)) (hash-table-put! makers 'struct:live-var-info (cons 'make-live-var-info make-live-var-info)) (hash-table-put! makers 'struct:prototype (cons 'make-prototype make-prototype)) (hash-table-put! makers 'struct:c++-class (cons 'make-c++-class make-c++-class)) (define (make-short-tok l) (make-tok l #f #f)) ;; A precompiled header saves the above state variables. (when precompiled-header (let ([orig (current-namespace)]) (parameterize ([current-namespace (make-namespace)]) (namespace-attach-module orig 'mzscheme) (namespace-require 'mzscheme) ;; Put constructors into the namespace: (hash-table-for-each makers (lambda (k v) (namespace-set-variable-value! (car v) (cdr v)))) (namespace-set-variable-value! 'make-short-tok make-short-tok) ;; Load the pre-compiled-header-as-.zo: (let ([l (load (change-suffix precompiled-header #".zo"))]) (for-each (lambda (x) (hash-table-put! used-symbols (car x) (+ (hash-table-get used-symbols (car x) (lambda () 0)) (cdr x)))) (list-ref l 0)) (set! c++-classes (list-ref l 1)) (prototyped (list-ref l 2)) (top-vars (list-ref l 3)) (set! pointer-types (list-ref l 4)) (set! non-pointer-types (list-ref l 5)) (set! struct-defs (list-ref l 6)) (set! non-gcing-functions (hash-table-copy (list-ref l 7))) (set! gc-var-stack-mode (list-ref l 8)))))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Pretty-printing output ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define next-indent #f) (define (newline/indent i) (newline) (set! next-indent i)) (define (display/indent v s) (when next-indent (display (make-string next-indent #\space)) (set! next-indent #f)) (display s)) (define re:quote-or-backslash (regexp "[\\\"]")) (define (push-vars l plus comma) (let loop ([l l][n 0][comma comma]) (unless (null? l) (loop (cdr l) (let push-var ([full-name (caar l)][vtype (cdar l)][n n][comma comma]) (cond [(union-type? vtype) (log-error "[UNION]: Can't push union onto mark stack: ~a." full-name) (printf "~aPUSHUNION(~a, ~a~a)" comma full-name n plus) (add1 n)] [(array-type? vtype) (printf "~aPUSHARRAY(~a, ~a, ~a~a)" comma full-name (array-type-count vtype) n plus) (+ 3 n)] [(struc-type? vtype) (let aloop ([array-index 0][n n][comma comma]) ;; Push each struct in array (or only struct if not an array) (let loop ([n n][l (cdr (lookup-struct-def (struc-type-struct vtype)))][comma comma]) (if (null? l) (if (and (struct-array-type? vtype) (< (add1 array-index) (struct-array-type-count vtype))) ;; Next in array (aloop (add1 array-index) n comma) ;; All done n) (loop (push-var (format "~a~a.~a" full-name (if (struct-array-type? vtype) (format "[~a]" array-index) "") (caar l)) (cdar l) n comma) (cdr l) ", "))))] [else (printf "~aPUSH(~a, ~a~a)" comma full-name n plus) (+ n 1)])) ", ")))) (define (total-push-size vars) (apply + (map (lambda (x) (get-variable-size (cdr x))) vars))) (define (extract-src-tok v) (cond [(tok? v) v] [(call? v) (extract-src-tok (call-func v))] [else #f])) (define (print-it e indent semi-newlines? ordered? line file sysheader? keep-lines?) (let loop ([e e][prev #f][prevs null][old-line line][old-file file][old-sysheader? sysheader?]) (if (null? e) (values old-line old-file old-sysheader?) (let* ([v (car e)] [sv (extract-src-tok v)] [line (if keep-lines? (or (and sv (tok-line sv)) old-line) old-line)] [file (if keep-lines? (or (and sv (tok-file sv)) old-file) old-file)] [sysheader? (if keep-lines? (if (and sv (tok-file sv)) (sysheader-tok? sv) old-sysheader?) old-sysheader?)] [inc-line! (lambda () (set! line (add1 line)))]) (when keep-lines? (unless (and (equal? line old-line) (equal? file old-file)) (if (and (equal? file old-file) (line . > . old-line) ((- line old-line) . < . 10)) (display (make-string (- line old-line) #\newline)) (printf "\n# ~a \"~a\"~a\n" line file (if sysheader? " 3" ""))) (set! next-indent indent))) (cond [(pragma? v) (let ([s (format "#pragma ~a" (pragma-s v))]) (unless (regexp-match re:boring s) (printf "\n~a\n\n" s) (set! line (+ line 3))))] [(threadlocal-decl? v) (void)] [(seq? v) (define skip-parens? ;; avoid `if ((...))' when "..." is not an assignment, ;; because that annoys compilers like clang (and prev (tok? prev) (memq (tok-n prev) '(if)) (let ([l (seq->list (seq-in v))]) (and (pair? l) (null? (cdr l)) (parens? (car l)) (let ([l (seq->list (seq-in (car l)))]) (not (ormap (lambda (i) (eq? '= (tok-n i))) l))))))) (display/indent v (if skip-parens? "" (tok-n v))) (let ([subindent (if (braces? v) (begin (newline/indent (+ indent 2)) (inc-line!) (+ indent 2)) indent)]) (let-values ([(l f s?) (print-it (seq->list (seq-in v)) subindent (not (and (parens? v) prev (tok? prev) (memq (tok-n prev) '(for)))) (or (braces? v) (callstage-parens? v)) line file sysheader? (and keep-lines? (not (nosrc-parens? v))))]) (set! line l) (set! file f) (set! sysheader? s?)) (when (and next-indent (= next-indent subindent)) (set! next-indent indent))) (unless skip-parens? (display/indent #f (seq-close v))) (cond [(braces? v) (newline/indent indent) (inc-line!)] [(brackets? v) (display/indent v " ")] [(parens? v) (if (and prev (tok? prev) (memq (tok-n prev) '(if)) (or (null? (cdr e)) (not (braces? (cadr e))))) (begin (newline/indent (+ indent 2)) (inc-line!)) (display/indent v " "))] [else (error 'xform "unknown brace: ~a" (caar v))])] [(note? v) (display/indent v (note-s v)) (newline/indent indent) (inc-line!)] [(call? v) (if (not (call-nonempty? v)) (display/indent v "FUNCCALL_EMPTY(") (if (and ordered? (prev-was-funcall? prevs)) ;; Do fast version (begin (display/indent v "FUNCCALL_AGAIN(")) ;; Do general version (begin (display/indent v (format "FUNCCALL(SETUP_~a(" (call-tag v))) (if show-info? (begin (display/indent v (format "(SETUP(~a)" (total-push-size (call-live v)))) (push-vars (call-live v) "" ", ") (display/indent v ")")) (display/indent v "_")) (display/indent v "), ")))) (let-values ([(l f s?) (print-it (append (call-func v) (list (call-args v))) indent #f #f line file sysheader? ;; Can't put srcloc within macro call: #f)]) (set! line l) (set! file f) (set! sysheader? s?)) (display/indent v ")")] [(block-push? v) (let ([size (total-push-size (block-push-vars v))] [prev-add (if (block-push-super-tag v) (format "+~a_COUNT" (block-push-super-tag v)) "")] [tag (block-push-tag v)] [tabbing (if (zero? indent) "" (make-string (sub1 indent) #\space))]) (unless (zero? size) (display/indent v (format "BLOCK_SETUP~a((" (if (block-push-top? v) "_TOP" ""))) (push-vars (block-push-vars v) prev-add "") (display/indent v "));") (newline) (inc-line!)) (printf "#~adefine ~a_COUNT (~a~a)\n" tabbing tag size prev-add) (inc-line!) (printf "#~adefine SETUP_~a(x) " tabbing tag) (cond [(and (zero? size) (block-push-super-tag v)) (printf "SETUP_~a(x)" (block-push-super-tag v))] [per-block-push? (printf "SETUP(~a_COUNT)" tag)] [else (printf "x")]) (newline/indent indent) (inc-line!))] [(nested-setup? v) (let ([tabbing (if (zero? indent) "" (make-string (sub1 indent) #\space))]) (case (tok-n v) [(nested) (printf "#~adefine BLOCK_SETUP(x) BLOCK_SETUP_each(x)\n" tabbing) (printf "#~adefine FUNCCALL(s, x) FUNCCALL_each(s, x)\n" tabbing) (printf "#~adefine FUNCCALL_EMPTY(x) FUNCCALL_EMPTY_each(x)\n" tabbing) (printf "#~adefine FUNCCALL_AGAIN(x) FUNCCALL_AGAIN_each(x)\n" tabbing)] [(no-nested) (printf "#~adefine BLOCK_SETUP(x) BLOCK_SETUP_once(x)\n" tabbing) (printf "#~adefine FUNCCALL(s, x) FUNCCALL_once(s, x)\n" tabbing) (printf "#~adefine FUNCCALL_EMPTY(x) FUNCCALL_EMPTY_once(x)\n" tabbing) (printf "#~adefine FUNCCALL_AGAIN(x) FUNCCALL_AGAIN_once(x)\n" tabbing)] [(undefine) (printf "#~aundef BLOCK_SETUP\n" tabbing) (printf "#~aundef FUNCCALL\n" tabbing) (printf "#~aundef FUNCCALL_EMPTY\n" tabbing) (printf "#~aundef FUNCCALL_AGAIN\n" tabbing)]) (set! line (+ 4 line)))] [(memq (tok-n v) asm-commands) (newline/indent indent) (inc-line!) (display/indent v (tok-n v)) (display/indent v " ")] [(and (or (eq? '|HIDE_FROM_XFORM| (tok-n v)) (eq? '|XFORM_HIDE_EXPR| (tok-n v))) (pair? (cdr e)) (seq? (cadr e)) (null? (seq->list (seq-in (cadr e))))) ;; This handles the case where we were trying to hide ;; something from xform, but the something macro-expanded ;; to nothing. It happens, for example, in FreeBSD gcc ;; 2.95.x when hiding a va_end() use (display/indent v '|HIDE_NOTHING_FROM_XFORM|)] [else (if (string? (tok-n v)) (begin (display/indent v "\"") (display (tok-n v)) (display/indent v "\"")) (display/indent v (tok-n v))) ;; Don't put a space between L and a string, because without ;; the space it means a long string. (unless (and (eq? '|L| (tok-n v)) (pair? (cdr e)) (or (string? (tok-n (cadr e))) (character? (tok-n (cadr e)))) (not (seq? (tok-n (cadr e))))) (display/indent v " ")) (when (and (eq? semi (tok-n v)) semi-newlines?) (newline/indent indent) (inc-line!))]) (loop (cdr e) v (cons v prevs) line file sysheader?))))) ;; prev-was-funcall? implements a last-ditch optimization: if ;; we just did a FUNCALL setup, we can do a FUNCALL_AGAIN setup ;; this time (which is possibly quicker) (define (prev-was-funcall? prevs) (letrec ([acall? (lambda (v) (or (call? v) ;; Maybe nested seq (and (parens? v) (let ([p (reverse (seq->list (seq-in v)))]) (and (pair? p) (call? (car p)) (callseq-prev? (cdr p)))))))] [callseq-prev? (lambda (prevs) (and (pair? prevs) (pair? (cdr prevs)) (tok? (car prevs)) (eq? '|,| (tok-n (car prevs))) (acall? (cadr prevs))))]) (or ;; Stmt (call or assign=call) sequence (let loop ([prevs prevs][semis 0]) (cond [(and (pair? prevs) (tok? (car prevs)) (eq? semi (tok-n (car prevs)))) (or (positive? semis) ;; means that we already found a proc-ending semi (if (and (pair? (cdr prevs)) (eq? semi (tok-n (cadr prevs)))) ;; Odd extra semi-colon. Skip it and try again. (loop (cdr prevs) semis) ;; Look further... (and (pair? (cdr prevs)) (acall? (cadr prevs)) (loop (cddr prevs) (add1 semis)))))] [(and (pair? prevs) (pair? (cdr prevs)) (pair? (cddr prevs)) (tok? (car prevs)) (tok? (cadr prevs)) (eq? '= (tok-n (car prevs))) (symbol? (tok-n (cadr prevs))) (eq? semi (tok-n (caddr prevs)))) (loop (cddr prevs) semis)] [else #f])) ;; Eval sequence (callseq-prev? prevs)))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; "Parsing" ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define skipping? #f) (define suspend-xform 0) (define re:h (regexp "[.]h$")) ;; top-level converts the top-level tok list e into ;; a new top-level tok list, often collecting info ;; (such as function prototypes and typedefs). ;; It expects that the tok list e reprsents one "thing", ;; which often means that it's terminated with a semicolon. (define (top-level e where can-drop-vars?) (cond [(pragma? (car e)) (list (car e))] ;; START_XFORM_SKIP and END_XFORM_SKIP: [(end-skip? e) (set! skipping? #f) null] [(start-skip? e) (set! skipping? #t) null] [skipping? e] ;; START_XFORM_SUSPEND and END_XFORM_SUSPEND: [(end-suspend? e) (set! suspend-xform (sub1 suspend-xform)) null] [(start-suspend? e) (set! suspend-xform (add1 suspend-xform)) null] ;; END_XFORM_ARITH and START_XFORM_ARITH enable and ;; re-enable warnings about arithmetic operations ;; on pointers [(end-arith? e) (set! check-arith? #f) null] [(start-arith? e) (set! check-arith? #t) null] [(threadlocal-decl? e) null] [(access-modifier? e) ;; public, private, etc. (list* (car e) (cadr e) (top-level (cddr e) where can-drop-vars?))] [(friend? e) ;; C++ friend annotation e] ;; process 'extern "C"' blocks [(and (>= (length e) 3) (eq? (tok-n (car e)) 'extern) (member (tok-n (cadr e)) '("C" "C++")) (braces? (caddr e))) (list* (car e) (cadr e) (let ([body-v (caddr e)]) (make-braces (tok-n body-v) (tok-line body-v) (tok-file body-v) (seq-close body-v) (list->seq (process-top-level (seq->list (seq-in body-v)) where can-drop-vars?)))) (cdddr e))] ;; process 'namespace X' blocks; currently, we assume that namespace ;; content is distinct [(and (>= (length e) 3) (eq? (tok-n (car e)) 'namespace) (symbol? (tok-n (cadr e))) (braces? (caddr e))) (list* (car e) (cadr e) (let ([body-v (caddr e)]) (make-braces (tok-n body-v) (tok-line body-v) (tok-file body-v) (seq-close body-v) (list->seq (process-top-level (seq->list (seq-in body-v)) where can-drop-vars?)))) (cdddr e))] [(typedef? e) (when show-info? (printf "/* TYPEDEF */\n")) (if (or (simple-unused-def? e) (unused-struc-typedef? e)) null (begin (when pgc? (check-pointer-type e)) e))] [(proc-prototype? e) (let ([name (register-proto-information e)]) (when (eq? (tok-n (car e)) '__xform_nongcing__) (hash-table-put! non-gcing-functions name #t)) (when show-info? (printf "/* PROTO ~a */\n" name)) (if (or precompiling-header? (> (hash-table-get used-symbols name) 1) (ormap (lambda (v) (eq? (tok-n v) 'virtual)) e)) ; can't drop virtual methods! (if palm? (add-segment-label name e) (clean-proto e)) null))] [(struct-decl? e) (if (braces? (caddr e)) (begin (when pgc? (register-struct e)) (when show-info? (printf "/* STRUCT ~a */\n" (tok-n (cadr e))))) (when show-info? (printf "/* STRUCT DECL */\n"))) e] [(class-decl? e) (if (or (braces? (caddr e)) (eq? '|:| (tok-n (caddr e)))) (begin (when show-info? (printf "/* CLASS ~a */\n" (tok-n (cadr e)))) (register-class e)) (begin (when show-info? (printf "/* CLASS DECL */\n")) (let ([name (tok-n (cadr e))]) (if (assoc name c++-classes) ;; we already know this class null e))))] [(function? e) (let ([name (register-proto-information e)]) (when (eq? (tok-n (car e)) '__xform_nongcing__) (hash-table-put! non-gcing-functions name #t)) (if (skip-function? e) e (begin (when show-info? (printf "/* FUNCTION ~a */\n" name)) (if (or (positive? suspend-xform) (not pgc?) (and where (regexp-match re:h where) (let loop ([e e][prev #f]) (cond [(null? e) #t] [(and (eq? '|::| (tok-n (car e))) prev (eq? (tok-n prev) (tok-n (cadr e)))) ;; inline constructor: need to convert #f] [else (loop (cdr e) (car e))])))) ;; Not pgc, xform suspended, ;; or still in headers and probably a simple inlined function (let ([palm-static? (and palm? (eq? 'static (tok-n (car e))))]) (when palm? (fprintf map-port "(~aimpl ~s)\n" (if palm-static? "s" "") name) (call-graph name e)) (append (if palm-static? ;; Need to make sure prototype is there for section (add-segment-label name (let loop ([e e]) (if (braces? (car e)) (list (make-tok semi #f #f)) (cons (car e) (loop (cdr e)))))) null) e)) (convert-function e name)))))] [(var-decl? e) (when show-info? (printf "/* VAR */\n")) (if (and can-drop-vars? (simple-unused-def? e)) null (begin (when pgc? (unless (eq? (tok-n (car e)) 'static) (let-values ([(pointers non-pointers) (get-vars e "TOPVAR" #f #t)]) (top-vars (append pointers non-pointers (top-vars)))))) e))] [(empty-decl? e) e] [else (print-struct #t) (error 'xform "unknown form: ~s" e)])) (define (empty-decl? e) (and (= 1 (length e)) (eq? '|;| (tok-n (car e))))) (define (start-skip? e) (and (pair? e) (or (eq? START_XFORM_SKIP (tok-n (car e))) (eq? 'XFORM_START_SKIP (tok-n (car e)))))) (define (end-skip? e) (and (pair? e) (or (eq? END_XFORM_SKIP (tok-n (car e))) (eq? 'XFORM_END_SKIP (tok-n (car e)))))) (define (start-suspend? e) (and (pair? e) (or (eq? START_XFORM_SUSPEND (tok-n (car e))) (eq? 'XFORM_START_SUSPEND (tok-n (car e)))))) (define (end-suspend? e) (and (pair? e) (or (eq? END_XFORM_SUSPEND (tok-n (car e))) (eq? 'XFORM_END_SUSPEND (tok-n (car e)))))) (define (start-arith? e) (and (pair? e) (or (eq? START_XFORM_ARITH (tok-n (car e))) (eq? 'XFORM_END_TRUST_ARITH (tok-n (car e)))))) (define (end-arith? e) (and (pair? e) (or (eq? END_XFORM_ARITH (tok-n (car e))) (eq? 'XFORM_START_TRUST_ARITH (tok-n (car e)))))) (define (threadlocal-decl? e) (and (pair? e) (or (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_GETSPECIFIC (tok-n (car e))) (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_FUNCTION (tok-n (car e))) (eq? 'XFORM_GC_VARIABLE_STACK_THROUGH_THREAD_LOCAL (tok-n (car e)))))) (define (access-modifier? e) (and (memq (tok-n (car e)) '(public private protected)) (eq? (tok-n (cadr e)) '|:|))) (define (friend? e) (memq (tok-n (car e)) '(friend))) ;; recognize a function prototype: (define (proc-prototype? e) (let loop ([l (length e)]) (and (> l 2) ;; Ends in semicolon (eq? semi (tok-n (list-ref e (sub1 l)))) (let loop ([l l]) (or (and (> l 2) ;; next-to-last is parens (parens? (list-ref e (- l 2))) ;; Symbol before parens, not '= or '__attribute__ (let ([s (tok-n (list-ref e (- l 3)))]) (and (symbol? s) (not (eq? '= s)) (not (eq? '__attribute__ s))))) (and ;; next-to-last is 0, then =, then parens (eq? 0 (tok-n (list-ref e (- l 2)))) (eq? '= (tok-n (list-ref e (- l 3)))) (loop (- l 2))) (and ;; next-to-last is 0, then =, then parens (eq? '__attribute__ (tok-n (list-ref e (- l 3)))) (loop (- l 2)))))))) ;; recognize a typedef: (define (typedef? e) (or (eq? 'typedef (tok-n (car e))) (and (eq? '__extension__ (tok-n (car e))) (pair? (cdr e)) (eq? 'typedef (tok-n (cadr e)))))) ;; Sometimes, we know that a declaration is unused because ;; the tokenizer saw the defined symbol only once. (This ;; doesn't work if we're pre-compiling a header for later.) (define (simple-unused-def? e) (and (not precompiling-header?) (andmap (lambda (x) (and (symbol? (tok-n x)) (not (eq? '|,| (tok-n x))))) e) (= 1 (hash-table-get used-symbols (let loop ([e e]) (if (null? (cddr e)) (tok-n (car e)) (loop (cdr e)))))))) ;; See `simple-unused-def?'. The `struct' case is more ;; complex, because multiple names might be assigned ;; in the same declaration. (define (unused-struc-typedef? e) (let ([once (lambda (s) (and (not precompiling-header?) (= 1 (hash-table-get used-symbols (tok-n s)))))] [seps (list '|,| '* semi)]) (let ([e (if (eq? '__extension__ (car e)) (cdr e) e)]) (and (eq? (tok-n (cadr e)) 'struct) (brackets? (cadddr e)) (once (caddr e)) (let loop ([e (cddddr e)]) (cond [(null? e) #t] [(or (memq (tok-n (car e)) seps) (braces? (car e)) (once (car e))) (loop (cdr e))] [else #f])))))) (define (struct-decl? e) (and (memq (tok-n (car e)) '(struct enum)) (ormap braces? (cdr e)))) (define (class-decl? e) (memq (tok-n (car e)) '(class))) ; ;Recognize a function (as opposed to a prototype): (define (function? e) (let ([l (length e)]) (and (> l 2) (let* ([_n (tok-n (list-ref e (sub1 l)))] [ll (if (eq? _n semi) (- l 2) (sub1 l))]) (let ([v (list-ref e ll)]) (and (braces? v) (let ([v (list-ref e (sub1 ll))]) (or (parens? v) (eq? (tok-n v) 'XFORM_SKIP_PROC) ;; `const' can appear between the arg parens ;; and the function body; this happens in the ;; OS X headers (and (eq? 'const (tok-n v)) (positive? (sub1 ll)) (parens? (list-ref e (- ll 2)))))))))))) (define (skip-function? e) (ormap (lambda (v) (eq? (tok-n v) 'XFORM_SKIP_PROC)) e)) ;; Recognize a top-level variable declaration: (define (var-decl? e) (let ([l (length e)]) (and (> l 2) (eq? semi (tok-n (list-ref e (sub1 l))))))) (define (skip-static-line? e) ;; We want to skip the really-big static declaration for ;; the inlined bytecodes in GRacket (let loop ([e e][l '(static unsigned char expr)]) (cond [(null? l) #t] [(null? e) #f] [(eq? (tok-n (car e)) (car l)) (loop (cdr e) (cdr l))] [else #f]))) (define (clean-proto e) ;; Strip __declspec(deprecated(...)) (if (and (eq? '__declspec (tok-n (car e))) (parens? (cadr e)) (let ([l (seq->list (seq-in (cadr e)))]) (and (= 2 (length l)) (eq? 'deprecated (tok-n (car l))) (parens? (cadr l))))) ;; Drop __declspec (cddr e) ;; Nothing to drop e)) ;; e has been determined to be a function prototype. ;; Remember the information needed to convert calls ;; to e (especially the return type). (define (register-proto-information e) (parse-proto-information e (lambda (name class-name type args static?) (unless class-name (prototyped (cons (cons name (make-prototype type (seq->list (seq-in args)) static? #f #f)) (prototyped)))) name))) (define (parse-proto-information e k) (let loop ([e e][type null]) (cond [(eq? '__declspec (tok-n (car e))) (loop (cddr e) type)] [(eq? '__attribute__ (tok-n (car e))) (loop (cddr e) type)] [(parens? (cadr e)) (let ([name (tok-n (let ([p (car e)]) (if (parens? p) (car (seq->list (seq-in p))) p)))] [type (let loop ([t (reverse type)]) (if (pair? t) (if (or (memq (tok-n (car t)) '(extern static virtual __stdcall __cdecl inline _inline __inline __inline__ __xform_nongcing__)) (equal? "C" (tok-n (car t)))) (loop (cdr t)) (cons (car t) (loop (cdr t)))) t))] [static? (ormap (lambda (t) (eq? (tok-n t) 'static)) type)]) ;; Clean type if we find a method/constructor/destructor (let-values ([(type class-name) (if (and (list? type) ((length type) . >= . 2)) (let ([rev-type (reverse type)]) (cond [(eq? '|::| (tok-n (car rev-type))) (values (reverse (cddr rev-type)) (cadr rev-type))] [(and ((length type) . >= . 3) (eq? '~ (tok-n (car rev-type))) (eq? '|::| (tok-n (cadr rev-type)))) (values (reverse (cdddr rev-type)) (caddr rev-type))] [else (values type #f)])) (values type #f))]) (k name class-name type (cadr e) static?)))] [else (loop (cdr e) (cons (car e) type))]))) ;; prototype-for-pointer? : (cons sym prototype) -> bool ;; Returns #t if the prototype declares a function that returns ;; a pointer. This information is computed (based on the declaration) ;; the first time it is needed, and then cached. (define (prototype-for-pointer? m) (let ([name (car m)] [proto (cdr m)]) (unless (prototype-pointer?-determined? proto) ;; We want to use `get-pointer-vars' to figure out the ;; answer, so invent a fake declaration and check it: (let ([e (append (prototype-type proto) (list (make-tok name #f #f) (make-tok semi #f #f)))]) (let ([vars (get-pointer-vars e "PROTODEF" #f #t)]) (set-prototype-pointer?! proto (not (null? vars))) (set-prototype-pointer?-determined?! proto #t)))) (prototype-pointer? proto))) (define (lookup-non-pointer-type t) (memq t non-pointer-types)) (define (lookup-pointer-type t) (assq t pointer-types)) (define (lookup-struct-def t) (assq t struct-defs)) ;; e is a typedef; drop the "typedef" keyword and ;; parse it as a variable declaration using `get-vars', then extend ;; `pointer-types' and `non-pointer-types' based on the result. (define (check-pointer-type e) (let*-values ([(pointers non-pointers) (get-vars ((if (eq? '__extension__ (car e)) cddr cdr) e) "PTRDEF" #t #t)] ;; Remove things like HANDLE and HWND, which are not ;; malloced and could overlap with GCed areas: [(pointers non-pointers) (let ([l (filter (lambda (p) (memq (car p) non-pointer-typedef-names)) pointers)]) (if (null? l) (values pointers non-pointers) (values (filter (lambda (p) (not (memq (car p) non-pointer-typedef-names))) pointers) (append l non-pointers))))]) (set! pointer-types (append pointers pointer-types)) (set! non-pointer-types (append (map car non-pointers) non-pointer-types)))) ;; get-vars : tok-list str bool bool -> (values list-of-(cons sym vtype) list-of-(cons sym vtype)) ;; Parses a declaration of one line (which may have multiple, comma-separated variables). ;; Returns a list of pointer declarations and a list of non-pointer declarations. (define (get-vars e comment union-ok? empty-array-is-ptr?) (let* ([e (if (or (eq? GC_CAN_IGNORE (tok-n (car e))) (eq? 'XFORM_CAN_IGNORE (tok-n (car e)))) (list (make-tok semi #f #f)) ; drop everything (filter (lambda (x) (not (memq (tok-n x) '(volatile __volatile__ __volatile const)))) e))] [base (tok-n (car e))] [base-is-ptr? (lookup-pointer-type base)] [base-struct (and (eq? base 'struct) (if (or (braces? (cadr e)) (braces? (caddr e))) (register-struct e) (let ([m (lookup-struct-def (tok-n (cadr e)))]) (and m (car m)))))] [minpos (if (or (eq? base 'struct) (eq? base 'union)) 1 0)] [non-ptr-base (cond [(eq? 'unsigned (tok-n (car e))) (if (memq (tok-n (cadr e)) '(int long char intptr_t)) (list 'unsigned (tok-n (cadr e))))] [(lookup-non-pointer-type (tok-n (car e))) (list (tok-n (car e)))] [else #f])]) (let loop ([l (- (length e) 2)][array-size #f][pointers null][non-pointers null]) (if (<= l minpos) (values pointers non-pointers) ;; Look back for "=" before comma: (let ([skip (let loop ([l (sub1 l)]) (cond [(or (<= l minpos) (eq? '|,| (tok-n (list-ref e l)))) #f] [(eq? '= (tok-n (list-ref e l))) (sub1 l)] [else (loop (sub1 l))]))]) (if skip ;; Skip assignment RHS: (loop skip #f pointers non-pointers) ;; Not assignment RHS: (let ([v (list-ref e l)]) (cond [(seq? v) ;; Array? Struct? (cond [(brackets? v) ;; Array decl: (loop (sub1 l) (let ([inner (seq->list (seq-in (list-ref e l)))]) (if (null? inner) (if empty-array-is-ptr? 'pointer 0) (tok-n (car inner)))) pointers non-pointers)] [(braces? v) ;; No more variable declarations (values pointers non-pointers)] [else ;; End of function ptr ;; (and we don't care about func ptrs) (values pointers non-pointers)])] [(memq (tok-n v) '(int long char unsigned intptr_t void ulong uint uintptr_t)) ;; No more variable declarations (values pointers non-pointers)] [(memq (tok-n v) '(|,| * |:| 1)) (loop (sub1 l) #f pointers non-pointers)] [else (let* ([name (tok-n v)] [pointer? (or (eq? 'pointer array-size) (eq? '* (tok-n (list-ref e (sub1 l)))))] [star-count (+ (if (eq? 'pointer array-size) 1 0) (let loop ([l (sub1 l)]) (if (eq? '* (tok-n (list-ref e l))) (add1 (loop (sub1 l))) 0)))] [base-struct (or base-struct (and base-is-ptr? (struc-type? (cdr base-is-ptr?)) (struc-type-struct (cdr base-is-ptr?))))] [union? (or (eq? base 'union) (and base-is-ptr? (union-type? (cdr base-is-ptr?))))] [struct-array? (or (and base-struct (not pointer?) (number? array-size)) (and base-is-ptr? (struct-array-type? (cdr base-is-ptr?))))] [array-size (if (number? array-size) array-size (and struct-array? (struct-array-type-count (cdr base-is-ptr?))))]) (when (and struct-array? (not union-ok?) (> array-size 16)) (log-error "[SIZE] ~a in ~a: Large array of structures at ~a." (tok-line v) (tok-file v) name)) (when (and (not union-ok?) (not pointer?) (or union? (and base-struct (let has-union? ([base base-struct]) (let ([v (cdr (lookup-struct-def base))]) (ormap (lambda (v) (or (union-type? v) (and (struc-type? v) (has-union? (struc-type-struct v))))) v)))))) (log-warning "[UNION] ~a in ~a: Can't handle union or record with union, ~a." (tok-line v) (tok-file v) name)) (if (and (or pointer? base-is-ptr? base-struct union?) ; Ignore these variables, for one reason or another: (not (memq name '(tcp_connect_dest_addr tcp_listen_addr tcp_here_addr tcp_there_addr tcp_accept_addr)))) (begin (when show-info? (printf "/* ~a: ~a ~a*/\n" comment name (cond [struct-array? (format "struct ~a[~a] " base-struct array-size)] [(number? array-size) (format "[~a] " array-size)] [(and base-struct (not pointer?)) (format "struct ~a " base-struct)] [(and union? (not pointer?)) "union "] [else (format "~a ~a* " (or (and base (list base)) non-ptr-base) star-count)]))) (loop (sub1 l) #f (cons (cons name (cond [struct-array? (make-struct-array-type base-struct array-size)] [(number? array-size) (make-array-type array-size)] [pointer? (make-pointer-type (or (and base (list base)) non-ptr-base) star-count)] [base-struct (make-struc-type base-struct)] [union? (make-union-type)] [else (make-pointer-type (or (and base (list base)) non-ptr-base) star-count)])) pointers) non-pointers)) (begin (when (and base (find-c++-class base #f)) (log-error "[INST] ~a in ~a: Static instance of class ~a." (tok-line (car e)) (tok-file (car e)) base)) (when show-info? (printf "/* NP ~a: ~a */\n" comment name)) (loop (sub1 l) #f pointers (cons (cons name (make-non-pointer-type non-ptr-base)) non-pointers)))))])))))))) (define (get-pointer-vars e comment union-ok? empty-array-is-ptr?) (let-values ([(pointers non-pointers) (get-vars e comment union-ok? empty-array-is-ptr?)]) pointers)) (define (get-pointer-vars-from-seq body comment comma-sep?) (let-values ([(pragmas el) (body->lines body comma-sep?)]) (apply append (map (lambda (e) (get-pointer-vars e comment #t #f)) el)))) ;; e is a struct decl; parse it an remember the results (define (register-struct e) (let ([body (seq->list (seq-in (if (braces? (cadr e)) (cadr e) (caddr e))))] [name (if (braces? (cadr e)) (gensym 'Anonymous) (tok-n (cadr e)))]) (let ([l (get-pointer-vars-from-seq body "PTRFIELD" #f)]) (and (not (null? l)) (begin (set! struct-defs (cons (cons name l) struct-defs)) name))))) ;; This is for PalmOS conversion with SEGOF decls. (define (add-segment-label name e) (let loop ([e e]) (cond [(null? (cdr e)) (fprintf map-port "(decl ~s)\n" name) (list (make-tok (string->symbol (format "SEGOF_~a" name)) #f #f) (car e))] [(memq (tok-n (car e)) (list __attribute__)) ;; No segment wanted e] [else (cons (car e) (loop (cdr e)))]))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Transformations ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; type->decl : vtype tok[for errs] -> seq list ;; Creates a type declaration based on a type struct (without the name of ;; a declared variable). (define (type->decl x where-v) (cond [(and (non-pointer-type? x) (non-pointer-type-base x)) (map (lambda (x) (make-tok x #f #f)) (non-pointer-type-base x))] [(and (pointer-type? x) (pointer-type-base x)) (append (map (lambda (x) (make-tok x #f #f)) (pointer-type-base x)) (let loop ([n (pointer-type-stars x)]) (if (zero? n) null (cons (make-tok '* #f #f) (loop (sub1 n))))))] [else (log-error "[TYPE] ~a in ~a: Can't render type declaration for ~a" (tok-line where-v) (tok-file where-v) x) (list (make-tok '??? #f #f))])) ;; Takes a class-decl, parses it, and records the information. ;; The basic strategy is to parse the class body as a top-level ;; sequence, and then move the collected info into the class ;; record. As the same time, we re-arrange the constructor ;; and put the work into a gcInit_ method to be called explicitly. ;; We also manufactor the gcMark and gcFixup methods. (define (register-class e) (let ([name (tok-n (cadr e))] [body-pos (if (eq? '|:| (tok-n (caddr e))) (if (memq (tok-n (cadddr e)) '(public private)) 5 4) 2)]) (unless (braces? (list-ref e body-pos)) (error 'xform "Confused by form of class declaration at line ~a in ~a" (tok-line (car e)) (tok-file (car e)))) (let* ([super (if (> body-pos 2) (tok-n (list-ref e (sub1 body-pos))) #f)] [cl (make-c++-class super (if (or super (eq? name 'gc)) super 'gc) null null)] [pt (prototyped)] [vs (top-vars)]) (set! c++-classes (cons (cons name cl) c++-classes)) (prototyped null) (top-vars null) (let* ([body-v (list-ref e body-pos)] [body-e (process-top-level (seq->list (seq-in body-v)) ".h" #f)] [methods (prototyped)]) ;; Save prototype list, but remove constructor and statics: (set-c++-class-prototyped! cl (filter (lambda (x) (not (or (eq? (car x) name) (prototype-static? (cdr x))))) methods)) (set-c++-class-top-vars! cl (top-vars)) (prototyped pt) (top-vars vs) (if (not (or (eq? 'gc (tok-n (caddr e))) (assoc 'gc c++-classes))) ;; primitive class, before `gc' defn e ;; normal class: (let loop ([e e][p body-pos]) (if (zero? p) (append (if (or super (eq? name 'gc)) null (list (make-tok '|:| #f #f) (make-tok 'public #f #f) (make-tok 'gc #f #f))) (cons (make-braces (tok-n body-v) (tok-line body-v) (tok-file body-v) (seq-close body-v) (list->seq (append ;; Replace constructors names with gcInit_ names (let loop ([e body-e][did-one? #f]) (cond [(null? e) (if did-one? null ;; Need an explicit gcInit_ method: (list (make-tok 'inline #f #f) (make-tok 'void #f #f) (make-gc-init-tok name) (make-parens "(" #f #f ")" (seqce)) (make-braces "{" #f #f "}" (if super (seqce (make-tok 'this #f #f) (make-tok '-> #f #f) (make-gc-init-tok super) (make-parens "(" #f #f ")" (seqce)) (make-tok semi #f #f)) (seqce)))))] [(eq? (tok-n (car e)) '~) ;; destructor (cons (car e) (cons (cadr e) (loop (cddr e) did-one?)))] [(and (eq? (tok-n (car e)) name) (parens? (cadr e))) ;; constructor (cons (make-tok 'void #f #f) (cons (make-gc-init-tok (tok-n (car e))) (loop (cdr e) #t)))] [else (cons (car e) (loop (cdr e) did-one?))])) (if (or (eq? name 'gc) (assq gcMark (c++-class-prototyped cl))) ;; Don't add to gc or to a class that has it null ;; Add gcMark and gcFixup methods: (let ([mk-proc (lambda (name marker) (list (make-tok 'inline #f #f) (make-tok 'void #f #f) (make-tok name #f #f) (make-parens "(" #f #f ")" (seqce)) (make-braces "{" #f #f "}" (list->seq (make-mark-body name marker (or super 'gc) (c++-class-top-vars cl) (car e))))))]) (append (list (make-tok 'public #f #f) (make-tok '|:| #f #f)) ;; gcMark method: (mk-proc gcMark gcMARK_TYPED) ;; gcFixup method: (mk-proc gcFixup gcFIXUP_TYPED))))))) (cdr e))) (cons (car e) (loop (cdr e) (sub1 p)))))))))) ;; Builds the body of a gcMark or gcFixup method (define (make-mark-body name marker super vars where-v) (let ([pointers (append (filter (lambda (x) (not (non-pointer-type? (cdr x)))) vars))]) (append (list (make-tok super #f #f) (make-tok '|::| #f #f) (make-tok name #f #f) (make-parens "(" #f #f ")" (seqce)) (make-tok semi #f #f)) (if (null? pointers) null (apply append (map (lambda (x) (list (make-tok marker #f #f) (make-parens "(" #f #f ")" (list->seq (append (type->decl (cdr x) where-v) (list (make-tok '|,| #f #f) (make-tok (car x) #f #f))))) (make-tok semi #f #f))) pointers)))))) (define (find-c++-class class-name report-err?) (and class-name (let ([m (assoc class-name c++-classes)]) (if m (cdr m) (begin (when report-err? (log-error "[CLASS]: Unknown class ~a." class-name)) #f))))) (define (get-c++-class-member var c++-class c++-class-members) (and c++-class (let ([m (assoc var (c++-class-members c++-class))]) (or m (let ([parent (c++-class-parent c++-class)]) (and parent (if (c++-class? parent) (get-c++-class-member var parent c++-class-members) (let ([parent (find-c++-class parent #t)]) (set-c++-class-parent! c++-class parent) (get-c++-class-member var parent c++-class-members))))))))) (define (get-c++-class-var var c++-class) (get-c++-class-member var c++-class c++-class-top-vars)) (define (get-c++-class-method var c++-class) (get-c++-class-member var c++-class c++-class-prototyped)) ;; Temporary state used during a conversion: (define used-self? #f) (define important-conversion? #f) (define saw-gcing-call #f) (define (new-vars->decls vars) (apply append (map (lambda (tv) (list (make-tok (car tv) #f #f) (make-tok '* #f #f) (make-tok (cdr tv) #f #f) (make-tok semi #f #f))) vars))) (define (make-gc-init-tok s) (make-tok (string->symbol (format "gcInit_~a" s)) #f #f)) ;; e is a function definition. Convert its body (if necessary) ;; to register locals with the GC. Do a little special work ;; for constructors, detected by a '|:| outside the body. (define (convert-function e name) (let*-values ([(body-v len) (let* ([len (sub1 (length e))] [v (list-ref e len)]) ;; Function may have trailing semicolon: (if (eq? semi (tok-n v)) (values (list-ref e (sub1 len)) (sub1 len)) (values v len)))] [(body-e) (seq->list (seq-in body-v))] [(class-name function-name func-pos) (let loop ([e e][p 0]) (cond [(null? e) (values #f #f #f)] [(null? (cdr e)) (values #f #f #f)] [(eq? '|::| (tok-n (cadr e))) (values (tok-n (car e)) (tok-n (caddr e)) (+ p 2))] [else (loop (cdr e) (add1 p))]))] [(args-e) (seq->list (seq-in (list-ref e (if (and func-pos (eq? class-name function-name)) (add1 func-pos) (sub1 len)))))] [(arg-vars all-arg-vars) (let-values ([(arg-pragmas arg-decls) (body->lines (append args-e (list (make-tok '|,| #f #f))) #t)]) (unless (null? arg-pragmas) (error 'arg-decls "unexpected pragmas")) (let loop ([l arg-decls][arg-vars null][all-arg-vars null]) (if (null? l) (values arg-vars all-arg-vars) (let-values ([(ptrs non-ptrs) (get-vars (car l) "PTRARG" #f #t)]) (loop (cdr l) (append arg-vars ptrs) (append all-arg-vars ptrs non-ptrs))))))] [(c++-class) (let ([c++-class (find-c++-class class-name #t)]) (and c++-class (or (get-c++-class-method function-name c++-class) (eq? function-name class-name) (eq? function-name '~)) c++-class))] [(initializers) (let loop ([e e][len len]) (cond [(zero? len) #f] [(eq? (tok-n (car e)) '|:|) (cons (cadr e) (caddr e))] [else (loop (cdr e) (sub1 len))]))]) (append ;; Build all of the function declaration up to the body: (let loop ([e e][len len][need-void? #t]) (cond [(zero? len) null] [(eq? (tok-n (car e)) '|:|) ;; skip initializers null] [(and function-name (eq? function-name class-name) (eq? (tok-n (car e)) class-name) (parens? (cadr e))) ;; Replace constructor name with gcInit_ name: (cons (make-gc-init-tok (tok-n (car e))) (loop (cdr e) (sub1 len) #f))] [(eq? (tok-n (car e)) 'inline) ;; Don't want 'void before 'inline (cons (car e) (loop (cdr e) (sub1 len) need-void?))] [else (if (and need-void? function-name (eq? function-name class-name)) (cons (make-tok 'void #f #f) (loop e len #f)) (cons (car e) (loop (cdr e) (sub1 len) #f)))])) (list (make-braces (tok-n body-v) (tok-line body-v) (tok-file body-v) (seq-close body-v) (let-values ([(orig-body-e) (begin (set! important-conversion? #f) (set! saw-gcing-call #f) body-e)] [(body-e live-vars) ;; convert-body does most of the conversion work, and also ;; introduces the PREPARE_VAR_STACK decl, since the last arg ;; provided here is not #f. (convert-body (if c++-class (let* ([new-vars-box (box null)] [e (begin (set! used-self? #f) (convert-class-vars body-e all-arg-vars c++-class new-vars-box))]) (append ;; If sElF is used, add its declaration. (if (or used-self? (and function-name (eq? class-name function-name))) (list (make-tok class-name #f #f) (make-tok '* #f #f) (make-tok sElF #f #f) (make-tok '= #f #f) (make-tok 'this #f #f) (make-tok semi #f #f)) null) ;; New vars for obj creation: (new-vars->decls (unbox new-vars-box)) ;; The main body: e)) ;; Do any conversion? (if source-is-c++? (let* ([new-vars-box (box null)] [e (convert-class-vars body-e all-arg-vars #f new-vars-box)]) (append (new-vars->decls (unbox new-vars-box)) e)) body-e)) arg-vars arg-vars #f c++-class ;; Moved initializers, if constructor (if (and function-name (eq? class-name function-name)) (let ([super-type (if initializers (tok-n (car initializers)) (c++-class-parent-name c++-class))] [super-args (if initializers (cdr initializers) (make-parens "(" #f #f ")" (seqce)))]) (list (list (make-tok sElF #f #f) (make-tok '-> #f #f) (make-gc-init-tok super-type) super-args (make-tok semi #f #f)))) null) (lambda () null) ;; Initially, no live vars, no introduiced vars, etc.: (make-live-var-info #f -1 0 null null null 0 0 0 #f) ;; Add PREPARE_VAR_STACK and ensure result return: (parse-proto-information e (lambda (name class-name type args static?) type)))]) (if (hash-table-get non-gcing-functions name (lambda () #f)) (when saw-gcing-call (log-error "[GCING] ~a in ~a: Function ~a declared __xform_nongcing__, but includes a function call." (tok-line saw-gcing-call) (tok-file saw-gcing-call) name)) (unless saw-gcing-call ' (eprintf "[SUGGEST] Consider declaring ~a as __xform_nongcing__.\n" name))) (if (and (not important-conversion?) (not (and function-name (eq? class-name function-name))) (or (not saw-gcing-call) (and (null? (live-var-info-new-vars live-vars)) (zero? (live-var-info-maxpush live-vars)) (or (<= (live-var-info-num-calls live-vars) 1) (= (live-var-info-num-calls live-vars) (+ (live-var-info-num-empty-calls live-vars) (live-var-info-num-noreturn-calls live-vars))))))) ;; No conversion necessary. (Lack of `call' records means no GC-setup ;; work when printing out the function.) (list->seq (cons (make-note 'note #f #f "/* No conversion */") orig-body-e)) (list->seq body-e)))))))) (define (convert-class-vars body-e arg-vars c++-class new-vars-box) (when c++-class (let-values ([(pragmas el) (body->lines body-e #f)]) (let-values ([(decls body) (split-decls el)]) (for-each (lambda (e) (let-values ([(pointers non-pointers) (get-vars e "CVTLOCAL" #f #t)]) (for-each (lambda (var) (when (get-c++-class-var (car var) c++-class) (log-error "[SHADOW++] ~a in ~a: Class variable ~a shadowed in decls." (tok-line (caar decls)) (tok-file (caar decls)) (car var)))) (append pointers non-pointers)))) decls)))) (let loop ([e body-e][can-convert? #t][paren-arrows? #t]) (cond [(null? e) null] [(skip-static-line? e) ;; Jump to semicolon: (let jloop ([e e]) (if (eq? semi (tok-n (car e))) (loop e can-convert? paren-arrows?) (cons (car e) (jloop (cdr e)))))] [(and can-convert? c++-class (pair? (cdr e)) (eq? (tok-n (cadr e)) '|::|) (find-c++-class (tok-n (car e)) #f)) ;; Maybe class-qualified method invocation. See ;; what happens if we remove the qualification (let ([rest (loop (cddr e) #t paren-arrows?)]) (if (eq? sElF (tok-n (car rest))) (list* (car rest) (cadr rest) (car e) (cadr e) (cddr rest)) (list* (car e) (cadr e) rest)))] [else (let ([v (car e)]) (cond [(pragma? v) (cons v (loop (cdr e) can-convert? paren-arrows?))] [(memq (tok-n v) '(|.| -> |::|)) ;; Don't check next as class member (cons v (loop (cdr e) #f paren-arrows?))] [(eq? (tok-n v) 'delete) ;; Make `delete' expression look like a function call (let ([arr? (brackets? (cadr e))]) (loop (list* (make-tok (if arr? DELETE_ARRAY DELETE) (tok-line v) (tok-file v)) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce ((if arr? caddr cadr) e))) ((if arr? cdddr cddr) e)) #t paren-arrows?))] [(eq? (tok-n v) 'delete_wxobject) ;; replace with call to GC_cpp_delete() (set! important-conversion? #t) (when (brackets? (cadr e)) (log-error "[DELOBJ] ~a in ~a: bad use of delete_wxobject" (tok-line v) (tok-file v))) (loop (list* (make-tok GC_cpp_delete (tok-line v) (tok-file v)) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (cadr e))) (cddr e)) #t paren-arrows?)] [(eq? (tok-n v) 'new) ;; Make `new' expression look like a function call (set! important-conversion? #t) (let* ([t (cadr e)] [obj? (find-c++-class (tok-n t) #f)] [atom? (lookup-non-pointer-type (tok-n t))]) (unless (or obj? atom?) (log-error "[NEW] ~a in ~a: New used on non-class" (tok-line (car e)) (tok-file (car e)))) (cond [(and (pair? (cddr e)) (eq? '* (tok-n (caddr e))) (pair? (cdddr e)) (brackets? (cadddr e))) ;; Array of pointers (loop (list* (make-tok NEW_PTR_ARRAY (tok-line v) (tok-file v)) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (cadr e) (make-tok '|,| #f #f) (cadddr e))) (cddddr e)) #t paren-arrows?)] [(and (pair? (cddr e)) (eq? '* (tok-n (caddr e)))) ;; A pointer (loop (list* (make-tok NEW_PTR (tok-line v) (tok-file v)) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (cadr e) (caddr e))) (cdddr e)) #t paren-arrows?)] [(and (pair? (cddr e)) (brackets? (caddr e))) ;; An array of objects (unless (or atom? (eq? #cs'wxPoint (tok-n t))) (log-warning "[ARRAY] ~a in ~a: array of ~a objects, allocating as array of atomic." (tok-line t) (tok-file t) (tok-n t))) (loop (list* (make-tok (if atom? NEW_ATOM_ARRAY NEW_ARRAY) #f #f) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (cadr e) (make-tok '|,| #f #f) (caddr e))) (cdddr e)) #t paren-arrows?)] [(or (and (pair? (cddr e)) (parens? (caddr e))) (not atom?)) ;; An object with init argument (when atom? (log-error "[CONFUSED] ~a in ~a: atomic type with initializers?" (tok-line v) (tok-file v))) (let ([args? (and (pair? (cddr e)) (parens? (caddr e)))] [line (tok-line v)] [file (tok-file v)] [new-var (string->symbol (format "~a_created" (tok-n (cadr e))))]) (unless (assq (tok-n (cadr e)) (unbox new-vars-box)) (set-box! new-vars-box (cons (cons (tok-n (cadr e)) new-var) (unbox new-vars-box)))) (loop (list* (make-creation-parens "(" line file ")" (seqce (make-tok new-var line file) (make-tok '= line file) (make-tok NEW_OBJ line file) (make-parens "(" line file ")" (seqce (cadr e))) (make-tok '|,| line file) (make-tok new-var line file) (make-tok '-> line file) (make-gc-init-tok (tok-n (cadr e))) (if args? (caddr e) (make-parens "(" line file ")" (seqce))) (make-tok '|,| line file) (make-tok new-var line file))) ((if args? cdddr cddr) e)) #t paren-arrows?))] [else ;; An atom (loop (list* (make-tok NEW_ATOM (tok-line v) (tok-file v)) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (cadr e))) (cddr e)) #t paren-arrows?)]))] [(and can-convert? c++-class (pair? (cdr e)) (parens? (cadr e)) (get-c++-class-method (tok-n v) c++-class)) ;; method call: (set! used-self? #t) (list* (make-tok sElF (tok-line v) (tok-file v)) (make-tok '-> (tok-line v) (tok-file v)) v (loop (cdr e) #t paren-arrows?))] [(and paren-arrows? (>= (length e) 3) (eq? '-> (tok-n (cadr e))) (or (null? (cdddr e)) (not (or (parens? (cadddr e)) (eq? '|::| (tok-n (cadddr e))))))) (loop (cons (make-parens "(" #f #f ")" (seqce (car e) (cadr e) (caddr e))) (cdddr e)) can-convert? #t)] [else ;; look for conversion (cons (cond [(braces? v) (make-braces "{" (tok-line v) (tok-file v) "}" (list->seq (convert-class-vars (seq->list (seq-in v)) arg-vars c++-class new-vars-box)))] [(seq? v) ((get-constructor v) (tok-n v) (tok-line v) (tok-file v) (seq-close v) (list->seq (loop (seq->list (seq-in v)) #t #f)))] [(and can-convert? (eq? (tok-n v) 'this)) (set! used-self? #t) (make-tok sElF (tok-line v) (tok-file v))] [(and can-convert? c++-class (not (assq (tok-n v) arg-vars)) (get-c++-class-var (tok-n v) c++-class)) (set! used-self? #t) (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (make-tok sElF (tok-line v) (tok-file v)) (make-tok '-> (tok-line v) (tok-file v)) v))] [else v]) (loop (cdr e) #t paren-arrows?))]))]))) (define re:funcarg (regexp "^__funcarg")) (define (is-generated? x) (regexp-match re:funcarg (symbol->string (car x)))) ;; body-e is something in {} or (). Convert the body with ;; `convert-function-calls' (which does the actual statement-level ;; inspection), and add appropriate body headers. Some of the ;; work here is distinguishing decls from body code. ;; The result is two values: converted body, and a new live-vars ;; record. (define (convert-body body-e extra-vars pushable-vars &-vars c++-class initializers after-vars-thunk live-vars setup-stack-return-type) (let-values ([(&-vars) (or &-vars (find-&-vars body-e))] [(pragmas el) (body->lines body-e #f)]) (let-values ([(decls body) (split-decls el)]) (let* ([local-vars (apply append (map (lambda (e) (if (eq? (tok-n (car e)) 'static) null (get-pointer-vars e "PTRLOCAL" #f #t))) decls))] [vars (begin (ormap (lambda (var) (when (assq (car var) extra-vars) (log-error "[SHADOW] ~a in ~a: Pointerful variable ~a shadowed in decls." (tok-line (caar decls)) (tok-file (caar decls)) (car var)))) local-vars) (append extra-vars local-vars))]) ;; Convert calls and body (recusively) (let-values ([(orig-maxlive) (live-var-info-maxlive live-vars)] [(orig-maxpush) (live-var-info-maxpush live-vars)] [(orig-tag) (live-var-info-tag live-vars)] [(body-x live-vars) (let loop ([body (append initializers body)]) (cond [(null? body) ;; Starting live-vars record for this block: ;; Create new tag ;; Locally-defined arrays, records, and & variables, are always live. ;; Start with -1 maxlive in case we want to check whether anything ;; was pushed in the block. (values null (make-live-var-info (gentag) -1 0 (append (let loop ([vars extra-vars]) (cond [(null? vars) null] [(memq (caar vars) &-vars) (cons (car vars) (loop (cdr vars)))] [else (loop (cdr vars))])) (let loop ([vars local-vars]) (cond [(null? vars) null] [(or (array-type? (cdar vars)) (struc-type? (cdar vars)) (memq (caar vars) &-vars)) (cons (car vars) (loop (cdr vars)))] [else (loop (cdr vars))])) (live-var-info-vars live-vars)) (live-var-info-new-vars live-vars) (live-var-info-pushed-vars live-vars) (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars)))] [(memq (tok-n (caar body)) '(START_XFORM_SKIP XFORM_START_SKIP)) (let skip-loop ([body (cdr body)]) (let*-values ([(end?) (memq (tok-n (caar body)) '(END_XFORM_SKIP XFORM_START_SKIP))] [(rest live-vars) ((if end? loop skip-loop) (cdr body))]) (values (if end? rest (cons (car body) rest)) live-vars)))] [(eq? (tok-n (caar body)) XFORM_RESET_VAR_STACK) (let-values ([(rest live-vars) (loop (cdr body))]) (values (cons (car body) rest) live-vars))] [else (when (body-var-decl? (car body)) (let ([type (tok-n (caar body))] [var (let loop ([e (car body)]) (if (or (null? (cdr e)) (eq? semi (tok-n (cadr e)))) (tok-n (car e)) (loop (cdr e))))]) (unless (or (eq? '|::| type) (eq? '|::| (tok-n (cadar body)))) ;; $patch vs2008 - goetter (log-error "[DECL] ~a in ~a: Variable declaration (~a ~a) not at the beginning of a block." (tok-line (caar body)) (tok-file (caar body)) type var)))) (let*-values ([(rest live-vars) (loop (cdr body))] [(e live-vars) (if (skip-static-line? (car body)) (values (car body) live-vars) ;; Here's the main body work: (convert-function-calls (car body) vars &-vars c++-class live-vars #f #f #f))]) (values (cons e rest) live-vars))]))]) ;; Collect live vars and look for function calls in decl section. (let ([live-vars (let loop ([decls decls][live-vars live-vars]) (if (null? decls) live-vars (let dloop ([el (let-values ([(pragmas el) (body->lines (car decls) #t)]) el)] [live-vars live-vars]) (if (null? el) (loop (cdr decls) live-vars) (let-values ([(_ live-vars) ;; We're not really interested in the conversion. ;; We just want to get live vars and ;; complain about function calls in the decl area: (convert-function-calls (car el) extra-vars &-vars c++-class live-vars "decls" #f #t)]) (dloop (cdr el) live-vars))))))]) ;; Calculate vars to push in this block. Make sure there are no duplicates. (let ([newly-pushed (let ([ht (make-hash-table)]) (for-each (lambda (x) (when (or (assq (car x) local-vars) (assq (car x) pushable-vars) (and setup-stack-return-type (is-generated? x))) (hash-table-put! ht (car x) x))) (live-var-info-pushed-vars live-vars)) (hash-table-map ht (lambda (k v) v)))]) (values (apply append pragmas (append decls (list (after-vars-thunk)) (list (let* ([vs-size (if per-block-push? (+ (total-push-size newly-pushed) (live-var-info-maxpush live-vars)) (live-var-info-maxlive live-vars))] [once? (and setup-stack-return-type (= (total-push-size newly-pushed) vs-size))]) (append (if show-info? (list (make-note 'note #f #f (format "/* PTRVARS: ~a */" (map car vars)))) null) (if setup-stack-return-type (apply append (live-var-info-new-vars live-vars)) null) (if (and setup-stack-return-type ;; Look for RET_VALUE_START anywhere: (let loop ([e body-x]) (cond [(list? e) (ormap loop e)] [(pair? e) (or (loop (cdr e)) (loop (car e)))] [(seq? e) (ormap loop (seq->list (seq-in e)))] [(and (tok? e) (eq? RET_VALUE_START (tok-n e))) #t] [else #f]))) (list (make-tok DECL_RET_SAVE #f #f) (make-nosrc-parens "(" #f #f ")" (list->seq setup-stack-return-type))) null) (if (and setup-stack-return-type (not (negative? (live-var-info-maxlive live-vars)))) (list (make-note 'note #f #f (format "PREPARE_VAR_STACK~a(~a);" (if once? "_ONCE" "") vs-size))) null) (if (negative? (live-var-info-maxlive live-vars)) null (list (make-block-push "block push" #f #f newly-pushed (live-var-info-tag live-vars) orig-tag setup-stack-return-type))) (if setup-stack-return-type (if once? (list no-nested-pushable) (list nested-pushable)) null)))) ;; Null out local vars: (map (lambda (var) ;; Check that the variable isn't specifically initialized: (if (let loop ([decls decls]) (and (pair? decls) (or (let loop ([e (car decls)]) (and (pair? e) (pair? (cdr e)) (or (and (eq? (car var) (tok-n (car e))) (eq? '= (tok-n (cadr e)))) (loop (cdr e))))) (loop (cdr decls))))) null (let null-var ([full-name (car var)][vtype (cdr var)]) (cond [(or (union-type? vtype) (non-pointer-type? vtype)) null] [(array-type? vtype) (let ([c (array-type-count vtype)]) (if (<= c 3) (let loop ([n 0]) (if (= n c) null (append (null-var (string->symbol (format "~a[~a]" full-name n)) #f) (loop (add1 n))))) (list (make-tok NULL_OUT_ARRAY #f #f) (make-parens "(" #f #f ")" (seqce (make-tok full-name #f #f))) (make-tok semi #f #f))))] [(struc-type? vtype) (let aloop ([array-index 0]) ;; Push each struct in array (or only struct if not an array) (let loop ([l (cdr (lookup-struct-def (struc-type-struct vtype)))]) (if (null? l) (if (and (struct-array-type? vtype) (< (add1 array-index) (struct-array-type-count vtype))) ;; Next in array (aloop (add1 array-index)) ;; All done null) (append (null-var (string->symbol (format "~a~a.~a" full-name (if (struct-array-type? vtype) (format "[~a]" array-index) "") (caar l))) (cdar l)) (loop (cdr l))))))] [else (list (make-tok full-name #f #f) (make-tok '= #f #f) (make-tok NULLED_OUT #f #f) (make-tok semi #f #f))])))) local-vars) body-x (if setup-stack-return-type (list (append (if (or (null? setup-stack-return-type) (and (= 1 (length setup-stack-return-type)) (eq? 'void (tok-n (car setup-stack-return-type))))) (list (make-tok RET_NOTHING_AT_END #f #f) (make-tok semi #f #f)) null) (list undefine-nested-pushable))) null))) ;; Restore original tag and union max live vars: (let ([total-pushed (total-push-size newly-pushed)]) (make-live-var-info orig-tag (max orig-maxlive (live-var-info-maxlive live-vars)) (max orig-maxpush (+ total-pushed (live-var-info-maxpush live-vars))) (live-var-info-vars live-vars) (live-var-info-new-vars live-vars) (live-var-info-pushed-vars live-vars) (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars))))))))))) (define (body-var-decl? e) (and (pair? e) (or (lookup-non-pointer-type (tok-n (car e))) (lookup-pointer-type (tok-n (car e))) (assq (tok-n (car e)) c++-classes)))) (define (looks-like-call? e- nf?) ;; e- is a reversed expression (and (pair? e-) (parens? (car e-)) ;; Something precedes (not (null? (cdr e-))) ;; Not an assignment, sizeof, if, string (or nf? (hash-table-get non-functions-table (tok-n (cadr e-)) #t)) (not (string? (tok-n (cadr e-)))) ;; Look back one more for if, etc. if preceding is paren (not (and (parens? (cadr e-)) (not (null? (cddr e-))) (memq (tok-n (caddr e-)) '(if while for)))))) (define (ignored-stuff? e-) ;; e- is a reversed expression (and (pair? e-) (parens? (car e-)) ;; Something precedes (not (null? (cdr e-))) (memq (tok-n (cadr e-)) '(|HIDE_FROM_XFORM| |XFORM_HIDE_EXPR|)))) (define (cast-or-call e- cast-k call-k) ;; Looks like a function call, although we don't know the ;; function yet. (The parens may be preceded by an ;; unparenthesized expression.) And it could be a cast (which ;; requires parens). (let ([pre (cadr e-)]) ;; Look for cast: (if (and (parens? pre) (let ([prel (seq->list (seq-in pre))]) (or ;; Assume we never have (func)(args, ...) (= 1 (length prel)) ;; trailing * is a give-away (eq? '* (tok-n (list-ref prel (sub1 (length prel))))) ;; leading `struct' is a giveaway: (eq? 'struct (tok-n (car prel)))))) ;; Cast (cast-k) ;; Call (call-k)))) (define (resolve-indirection v get-c++-class-member c++-class locals) (and (parens? v) (let ([seql (seq->list (seq-in v))]) (and (= 3 (length seql)) (eq? '-> (tok-n (cadr seql))) (let ([lhs (car seql)]) (cond [(eq? sElF (tok-n lhs)) (get-c++-class-member (tok-n (caddr seql)) c++-class)] [(or (resolve-indirection lhs get-c++-class-var c++-class locals) (assq (tok-n lhs) locals) (assq (tok-n lhs) (top-vars))) => (lambda (m) (let ([type (cdr m)]) (and (pointer-type? type) (= 1 (pointer-type-stars type)) (= 1 (length (pointer-type-base type)))) (let ([c++-class (find-c++-class (car (pointer-type-base type)) #f)]) (and c++-class (get-c++-class-member (tok-n (caddr seql)) c++-class)))))] [else #f])))))) (define (extract-resolvable-record-var v) (and (parens? v) (let ([seql (seq->list (seq-in v))]) (= 3 (length seql)) (eq? '-> (tok-n (cadr seql))) (if (parens? (car seql)) (extract-resolvable-record-var (car seql)) (car seql))))) ;; Found a sequance of argument expressions where function calls ;; are not allowed. Lift out the calls, inventing temporary variables ;; as necessary. (define (lift-out-calls args live-vars c++-class locals) (let ([e (seq->list (seq-in args))]) (if (null? e) (values null args null null live-vars) (let-values ([(pragmas el) (body->lines e #t)]) (unless (null? pragmas) (error 'lift-out-calls "unexpected pragma")) (let loop ([el el] [new-args null][setups null][new-vars null] [ok-calls null][must-convert? #t][live-vars live-vars]) (letrec ([lift-one? (lambda (e) (let ([e- (let ([e- (reverse e)]) (if (null? (cdr el)) e- (cdr e-)))]) ; skip comma (and (looks-like-call? e- #f) (cast-or-call e- (lambda () #f) (lambda () (lambda (wrap) (lift-one (cons e (cons (or (and (null? (cddr e-)) (cadr e-)) (and (= 3 (length (cdr e-))) (eq? '-> (tok-n (caddr e-))) (make-parens "(" #f #f ")" (list->seq (reverse (cdr e-)))))) (car e-))) wrap)))))))] [lift-one (lambda (call-form wrap) (let* ([call (car call-form)] [call-func (cadr call-form)] [call-args (cddr call-form)] [p-m (and must-convert? call-func (if (parens? call-func) (resolve-indirection call-func get-c++-class-method c++-class locals) (assq (tok-n call-func) (prototyped))))]) (if p-m (let ([new-var (gensym '__funcarg)]) (loop (cdr el) (cons (append (wrap (list (make-tok new-var #f #f))) (if (null? (cdr el)) null (list (make-tok '|,| #f #f)))) new-args) (cons (if (null? (cdr el)) ;; Add comma (append call (list (make-tok '|,| #f #f))) call) setups) (cons (cons new-var (prototype-for-pointer? p-m)) new-vars) ok-calls #t (make-live-var-info (live-var-info-tag live-vars) (live-var-info-maxlive live-vars) (live-var-info-maxpush live-vars) (live-var-info-vars live-vars) ;; Add newly-created vars for lifting to declaration set (cons (append (prototype-type (cdr p-m)) (list (make-tok new-var #f #f)) (if (prototype-for-pointer? p-m) (list (make-tok '= #f #f) (make-tok NULLED_OUT #f #f)) null) (list (make-tok semi #f #f))) (live-var-info-new-vars live-vars)) (live-var-info-pushed-vars live-vars) (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars)))) (loop (cdr el) (cons (wrap e) new-args) setups new-vars (if must-convert? ok-calls (cons call-args ok-calls)) #t live-vars))))] [lift-in-arithmetic? (lambda (e) (and (pair? e) (cond ;; look for: ! [(eq? '! (tok-n (car e))) (let ([k (lift-in-arithmetic? (cdr e))]) (and k (lambda (wrap) (k (lambda (x) (wrap (cons (car e) x)))))))] ;; look for: () [(and (parens? (car e)) (null? (cdr e))) (let ([k (lift-in-arithmetic? (seq->list (seq-in (car e))))]) (and k (lambda (wrap) (k (lambda (x) (wrap (list (make-parens "(" #f #f ")" (list->seq x)))))))))] ;; look for: n op [(and (>= (length e) 3) (let ([n (tok-n (car e))]) (or (number? n) (symbol? n))) (memq (tok-n (cadr e)) '(+ - * / #csXFORM_OK_PLUS #csXFORM_OK_MINUS #csXFORM_TRUST_PLUS #csXFORM_TRUST_MINUS))) (let ([k (lift-in-arithmetic? (cddr e))]) (and k (lambda (wrap) (k (lambda (x) (wrap (list* (car e) (cadr e) x)))))))] ;; look for: op n [(let ([len (if (null? el) (length e) (sub1 (length e)))]) ; skip comma (and (>= len 3) (let ([n (tok-n (list-ref e (sub1 len)))]) (or (number? n) (symbol? n))) (memq (tok-n (list-ref e (- len 2))) '(+ - * / #csXFORM_OK_PLUS #csXFORM_OK_MINUS #csXFORM_TRUST_PLUS #csXFORM_TRUST_MINUS)))) (let* ([last? (null? el)] [len (if last? (length e) (sub1 (length e)))]) (let ([k (lift-in-arithmetic? (let loop ([e e]) (if (null? ((if last? cddr cdddr) e)) (if last? null (cddr e)) (cons (car e) (loop (cdr e))))))]) (and k (lambda (wrap) (k (lambda (x) (wrap (append x (list (list-ref e (- len 2)) (list-ref e (- len 1))) (if last? (list (list-ref e len)) null)))))))))] [(lift-one? e) => values] [else #f])))]) (cond [(null? el) (if (null? new-vars) (values null args null ok-calls live-vars) (values setups (make-parens "(" (tok-line args) (tok-file args) ")" (list->seq (apply append (reverse new-args)))) new-vars ok-calls live-vars))] [(lift-in-arithmetic? (car el)) => (lambda (k) (k values))] [(and (= (length (car el)) 2) (or (string? (tok-n (caar el))) (number? (tok-n (caar el))))) ;; Constant => still no need to lift other args.. (loop (cdr el) (cons (car el) new-args) setups new-vars ok-calls must-convert? live-vars)] [else (loop (cdr el) (cons (car el) new-args) setups new-vars ok-calls #t live-vars)]))))))) (define (check-special-live-vars rest- vars live-vars) (cond [(and (pair? rest-) (eq? '= (tok-n (car rest-))) (pair? (cdr rest-)) (extract-resolvable-record-var (cadr rest-))) => (lambda (v) (if (and (assq (tok-n v) vars) (not (assq (tok-n v) (live-var-info-vars live-vars)))) ;; Add a live variable: (replace-live-vars live-vars (cons (assq (tok-n v) vars) (live-var-info-vars live-vars))) ;; Already there, or not pushable: live-vars))] [else live-vars])) ;; Inspect an expression sequence statement-by-statement to convert ;; function calls with the GC-registration wrappers, expose temps, ;; etc. Some conversions require generating local variables, as ;; accumulated in a `live-vars' record. The result is two values: ;; converted body, and a new live-vars record. (This function is ;; Mutually recursive with convert-body.) (define (convert-function-calls e vars &-vars c++-class live-vars complain-not-in memcpy? braces-are-aggregates?) ;; e is a single statement ;; Reverse to calculate live vars as we go. ;; Also, it's easier to look for parens and then inspect preceding ;; to find function calls. ;; complain-not-in is ither #f [function calls are ok], a string [not ok, string describes way], ;; or (list ok-exprs ...)) [in a rator position, only ok-expr calls are allowed, ;; because they're blessed by the lifter] (let ([e- (reverse e)] [orig-num-calls (live-var-info-num-calls live-vars)]) (let loop ([e- e-][result null][live-vars live-vars][converted-sub? #f]) (cond [(null? e-) (values result live-vars)] [(ignored-stuff? e-) (loop (cdr e-) (cons (car e-) result) live-vars converted-sub?)] [(eq? 'return (tok-n (car e-))) ;; Look forward in result to semicolon, and wrap that: (let rloop ([result result][l null]) (cond [(null? result) (error 'xform "odd return at ~a:~a" (tok-file (car e-)) (tok-line (car e-)))] [(eq? (tok-n (car result)) semi) (loop (cdr e-) (if (null? l) (cons (make-tok RET_NOTHING (tok-line (car e-)) (tok-file (car e-))) result) (let ([has-empty-funccall? ;; All calls must be empty calls, otherwise ;; the result might not depend on the empty call ;; (e.g., f() && empty(f()) ) (let loop ([l l][one? #f]) (cond [(null? l) one?] [(call? (car l)) (if (null? (call-live (car l))) (loop (cdr l) #t) #f)] [(seq? (car l)) (and (loop (seq->list (seq-in (car l))) one?) (loop (cdr l) one?))] [else #f]))]) (list* (make-tok (if has-empty-funccall? RET_VALUE_EMPTY_START RET_VALUE_START) (tok-line (car e-)) (tok-file (car e-))) (make-parens "(" (tok-line (car e-)) (tok-file (car e-)) ")" (list->seq (reverse l))) (make-tok (if has-empty-funccall? RET_VALUE_EMPTY_END RET_VALUE_END) (tok-line (car e-)) (tok-file (car e-))) result))) live-vars converted-sub?)] [else (rloop (cdr result) (cons (car result) l))]))] [(looks-like-call? e- #f) ;; Looks like a function call, maybe a cast: (cast-or-call e- (lambda () ;; It's a cast: (let-values ([(v live-vars) (convert-paren-interior (car e-) vars &-vars c++-class live-vars complain-not-in memcpy?)]) (loop (cddr e-) (list* (cadr e-) v result) live-vars #t))) (lambda () ;; It's a function call; find the start (let-values ([(args) (car e-)] [(func rest-) (let loop ([e- (cdr e-)]) (cond [(null? e-) (values null null)] [(null? (cdr e-)) (values e- null)] [(parens? (car e-)) (values (list (car e-)) (cdr e-))] [(brackets? (car e-)) ;; Array access (let-values ([(func rest-) (loop (cdr e-))]) (values (cons (car e-) func) rest-))] ;; Assignment to result of top-level call [(and (pair? (cddr e-)) (eq? (tok-n (cadr e-)) '|::|) (eq? (tok-n (caddr e-)) '=)) (values (list (car e-) (cadr e-)) (cddr e-))] ;; Struct reference, class-specified: [(memq (tok-n (cadr e-)) '(-> |.| |::|)) ;; In '|::| case, check for 'return or parens that might mean "if" (if (and (eq? '|::| (tok-n (cadr e-))) (pair? (cddr e-)) (or (eq? 'return (tok-n (caddr e-))) (seq? (caddr e-)))) (values (list (car e-) (cadr e-)) (cddr e-)) (let-values ([(func rest-) (loop (cddr e-))]) (values (list* (car e-) (cadr e-) func) rest-)))] [else (values (list (car e-)) (cdr e-))]))]) (when (and complain-not-in (or (not (pair? complain-not-in)) (not (memq args complain-not-in)))) (log-error "[CALL] ~a in ~a: Bad place for function call~a, starting tok is ~s." (tok-line (car func)) (tok-file (car func)) (if (list? complain-not-in) "" (format " (in ~a)" complain-not-in)) (tok-n (car func)))) ;; Lift out function calls as arguments. (Can re-order code. ;; Racket source code must live with this change to C's semantics.) ;; Calls are replaced by varaibles, and setup code generated that ;; assigns to the variables. (let*-values ([(live-vars) ;; Check for special form (XXX -> ivar) = call, which will ;; get re-arranged to (newvar = call, (XXX -> ivar) = newvar) (check-special-live-vars rest- vars live-vars)] [(orig-live-vars) live-vars] [(setups args new-vars ok-calls live-vars) ;; Split args into setup (calls) and args. ;; List newly-created vars (in order) in new-vars. ;; Make sure each setup ends with a comma. (lift-out-calls args live-vars c++-class vars)] [(sub-memcpy?) ;; memcpy, etc. call? (and (pair? (cdr e-)) (hash-table-get non-gcing-functions (tok-n (cadr e-)) #f))] [(args live-vars) (convert-paren-interior args vars &-vars c++-class (replace-live-vars live-vars (append (map (lambda (x) (cons (car x) (make-vtype))) (filter (lambda (x) (cdr x)) new-vars)) (live-var-info-vars live-vars))) ok-calls sub-memcpy?)] [(func live-vars) (convert-function-calls (reverse func) vars &-vars c++-class live-vars "rator" #f #f)] ;; Process lifted-out function calls: [(setups live-vars) (let loop ([setups setups][new-vars new-vars][result null][live-vars live-vars]) (if (null? setups) (values result live-vars) (let-values ([(setup live-vars) (convert-function-calls (car setups) vars &-vars c++-class ;; Remove var for this one: (replace-live-vars live-vars (remove (caar new-vars) (live-var-info-vars live-vars) (lambda (a b) (eq? a (car b))))) #f #f #f)]) (loop (cdr setups) (cdr new-vars) (cons (list* (make-tok (caar new-vars) #f #f) (make-tok '= #f #f) setup) result) live-vars))))]) ;; Put everything back together. Lifted out calls go into a sequence ;; before the main function call. (let* ([non-returning? (and ;; call declared to not return, or after a `return' (or (and (null? (cdr func)) (memq (tok-n (car func)) non-returning-functions)) (and (pair? rest-) (eq? 'return (tok-n (car rest-))) (not converted-sub?))) ;; no arrays of pointers in this scope, or addresses of ;; local vars taken in the function. (not (or (ormap (lambda (var) (and (array-type? (cdr var)) '(eprintf "Optwarn [return] ~a in ~a: tail-push blocked by ~s[].\n" (tok-line (car func)) (tok-file (car func)) (car var)))) (live-var-info-vars live-vars)) (ormap (lambda (&-var) (and (assq &-var vars) '(eprintf "Optwarn [return] ~a in ~a: tail-push blocked by &~s.\n" (tok-line (car func)) (tok-file (car func)) &-var))) &-vars))))] [pushed-vars (cond [non-returning? ;; non-returning -> don't need to push vars null] [else (live-var-info-vars orig-live-vars)])] [this-nonempty? (and (not non-returning?) (or (pair? pushed-vars) (live-var-info-nonempty-calls? live-vars)))]) (let ([non-gcing-call? (and (null? (cdr func)) (hash-table-get non-gcing-functions (tok-n (car func)) (lambda () #f)))] [setjmp-call? (memq (tok-n (car func)) setjmp-functions)]) (loop rest- (let ([call (if (or non-gcing-call? setjmp-call?) ;; Call without pointer pushes (make-parens "(" #f #f ")" (list->seq (append func (list args)))) ;; Call with pointer pushes (begin (set! saw-gcing-call (car e-)) (make-call "func call" #f #f func args pushed-vars (live-var-info-tag orig-live-vars) this-nonempty?)))]) (cons (if (null? setups) call (make-callstage-parens "(" #f #f ")" (list->seq (append (apply append setups) (list call))))) result)) (make-live-var-info (live-var-info-tag live-vars) ;; maxlive is either size for this push or old maxlive: (max (if non-gcing-call? 0 (total-push-size (live-var-info-vars orig-live-vars))) (live-var-info-maxlive live-vars)) ;; note: maxpush calculated at block level (live-var-info-maxpush live-vars) (live-var-info-vars live-vars) (live-var-info-new-vars live-vars) ;; Add newly-pushed variable to pushed set: (let* ([old-pushed (live-var-info-pushed-vars live-vars)] [new-pushed (if non-gcing-call? null (filter (lambda (x) (not (assq (car x) old-pushed))) pushed-vars))]) (append new-pushed old-pushed)) (+ (if (or non-gcing-call? setjmp-call?) 0 1) (live-var-info-num-calls live-vars)) (+ (if (or non-gcing-call? setjmp-call?) 0 (if non-returning? 1 0)) (live-var-info-num-noreturn-calls live-vars)) (+ (if (or non-gcing-call? non-returning? setjmp-call?) 0 (if this-nonempty? 0 1)) (live-var-info-num-empty-calls live-vars)) (or (and this-nonempty? (not (or non-gcing-call? setjmp-call?))) (live-var-info-nonempty-calls? live-vars))) (or converted-sub? (null? rest-) (not (memq (tok-n (car rest-)) '(return else)))))))))))] [(and (looks-like-call? e- #t) (hash-table-get args-unevaled-table (tok-n (cadr e-)) #f)) (loop (cddr e-) (cons (cadr e-) (cons (car e-) result)) live-vars converted-sub?)] [(eq? 'goto (tok-n (car e-))) ;; Goto - assume all vars are live (loop (cdr e-) (cons (car e-) result) (replace-live-vars live-vars vars) #t)] [(eq? '= (tok-n (car e-))) ;; Check for assignments where the LHS can move due to ;; a function call on the RHS. [Note that special support ;; in the function call case is necessary.] (if (> (live-var-info-num-calls live-vars) orig-num-calls) (let ([assignee (cdr e-)]) ;; Special case: (YYY -> ivar) = XXX; (let ([special-case-type (and (not (null? assignee)) (null? (cdr assignee)) (= 2 (length result)) (or (call? (car result)) (creation-parens? (car result))) (eq? semi (tok-n (cadr result))) (let ([m (resolve-indirection (car assignee) get-c++-class-var c++-class vars)]) (and m (cdr m))))]) (if (and special-case-type (or (non-pointer-type? special-case-type) (pointer-type? special-case-type))) ;; Change to (newvar = XXX, (YYY -> ivar) = newvar) (let ([new-var (gensym '__assign)] [v (car e-)]) (loop null (list (make-parens "(" (tok-line v) (tok-file v) ")" (seqce (make-tok new-var #f #f) (make-tok '= #f #f) (car result) (make-tok '|,| #f #f) (car assignee) v (make-tok new-var (tok-line v) (tok-file v)))) (cadr result)) ; semicolon ;; Add new variable to the list: (make-live-var-info (live-var-info-tag live-vars) (live-var-info-maxlive live-vars) (live-var-info-maxpush live-vars) (live-var-info-vars live-vars) ;; Add newly-created vars for lifting to declaration set (cons (append (type->decl special-case-type v) (list (make-tok new-var #f #f) (make-tok semi #f #f))) (live-var-info-new-vars live-vars)) (live-var-info-pushed-vars live-vars) (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars)) #t)) (begin (when (and (not (null? assignee)) (or (if (brackets? (car assignee)) (or (not (or (null? (cddr assignee)) (eq? '|:| (tok-n (caddr assignee))))) (let ([v (cadr assignee)]) (or (not (symbol? (tok-n v))) ;; Assignment to locally-declared array is fine: (let ([m (assq (tok-n v) vars)]) (and m (not (or (array-type? (cdr m)) (struct-array-type? (cdr m))))))))) (and (not (symbol? (tok-n (car assignee)))) ;; as below, ok if preceded by XFORM_OK_ASSIGN (or (not (pair? (cdr assignee))) (not (eq? (tok-n (cadr assignee)) 'XFORM_OK_ASSIGN))))) (and (symbol? (tok-n (car assignee))) (not (null? (cdr assignee))) ;; ok if name starts with "_stk_" (not (regexp-match re:_stk_ (symbol->string (tok-n (car assignee))))) ;; ok if preceding is else or label terminator (not (memq (tok-n (cadr assignee)) '(else |:|))) ;; assignment to field in record is ok (not (and (eq? (tok-n (cadr assignee)) '|.|) (pair? (cddr assignee)) (symbol? (tok-n (caddr assignee))) (null? (cdddr assignee)))) ;; ok if preceded by XFORM_OK_ASSIGN (not (eq? (tok-n (cadr assignee)) 'XFORM_OK_ASSIGN)) ;; ok if preceding is `if', `until', etc. (not (and (parens? (cadr assignee)) (pair? (cddr assignee)) (memq (tok-n (caddr assignee)) '(if while for until)))))) (not (eq? 'exn_table (tok-n (car (last-pair e-)))))) (log-warning "[ASSIGN] ~a in ~a: suspicious assignment with a function call, LHS ends ~s." (tok-line (car e-)) (tok-file (car e-)) (tok-n (cadr e-)))) (loop (cdr e-) (cons (car e-) result) live-vars #t))))) (loop (cdr e-) (cons (car e-) result) live-vars #t))] [(and (braces? (car e-)) (not braces-are-aggregates?)) (let*-values ([(v) (car e-)] ;; do/while/for: we'll need a fixpoint for live-vars ;; (We'll get the fixpoint by poing things twice) [(do?) (and (not (null? (cdr e-))) (memq (tok-n (cadr e-)) '(do)))] [(while?) (and (not (null? (cdr e-))) (parens? (cadr e-)) (not (null? (cddr e-))) (memq (tok-n (caddr e-)) '(for while)))] [(orig-new-vars) (live-var-info-new-vars live-vars)] [(orig-pushed-vars) (live-var-info-pushed-vars live-vars)] ;; Proc to convert body once [(convert-brace-body) (lambda (live-vars) (convert-body (seq->list (seq-in v)) vars null &-vars c++-class null (lambda () null) live-vars #f))] ;; First conversion [(e live-vars) (convert-brace-body live-vars)] ;; Proc to filter live and pushed vars, dropping vars no longer in scope: [(filter-live-vars) (lambda (live-vars) (let* ([not-declared (lambda (x) (assq (car x) vars))] [new-live-vars (filter not-declared (live-var-info-vars live-vars))] [new-pushed-vars (filter (lambda (x) (or (not-declared x) (is-generated? x))) (live-var-info-pushed-vars live-vars))]) (make-live-var-info (live-var-info-tag live-vars) (live-var-info-maxlive live-vars) (live-var-info-maxpush live-vars) new-live-vars (live-var-info-new-vars live-vars) new-pushed-vars (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars))))] [(restore-new-vars) (lambda (live-vars) (make-live-var-info (live-var-info-tag live-vars) (live-var-info-maxlive live-vars) (live-var-info-maxpush live-vars) (live-var-info-vars live-vars) orig-new-vars orig-pushed-vars (live-var-info-num-calls live-vars) (live-var-info-num-noreturn-calls live-vars) (live-var-info-num-empty-calls live-vars) (live-var-info-nonempty-calls? live-vars)))] [(e live-vars rest extra) (cond [(and do? (not exit-with-error?)) (let-values ([(e live-vars) (convert-brace-body (restore-new-vars live-vars))]) (values e live-vars (cdr e-) #f))] [(and while? (not exit-with-error?)) ;; Run test part. We don't filter live-vars, but maybe we should: (let-values ([(v live-vars) (convert-seq-interior (cadr e-) #t vars &-vars c++-class (restore-new-vars live-vars) #f #f)]) ;; Now run body again: (let-values ([(e live-vars) (convert-brace-body (restore-new-vars live-vars))]) ;; Finally, run test again: (let-values ([(v live-vars) (convert-seq-interior (cadr e-) #t vars &-vars c++-class live-vars #f #f)]) (values e live-vars (cddr e-) v))))] [else (values e live-vars (cdr e-) #f)])]) (loop rest (append (if extra (list extra) null) (list (make-braces (tok-n v) (tok-line v) (tok-file v) (seq-close v) (list->seq e))) result) (filter-live-vars live-vars) #t))] [(seq? (car e-)) ;; Do nested body. ;; For (v = new x, ...) parens, check for special conversion ;; on (XXX -> ivar) = (v = new x, ...) (let ([live-vars (if (creation-parens? (car e-)) (check-special-live-vars (cdr e-) vars live-vars) live-vars)]) (let-values ([(v live-vars) (convert-seq-interior (car e-) (parens? (car e-)) vars &-vars c++-class live-vars (or complain-not-in (and (brackets? (car e-)) "array access")) memcpy?)]) (loop (cdr e-) (cons v result) live-vars #t)))] [(and (assq (tok-n (car e-)) vars) (not (assq (tok-n (car e-)) (live-var-info-vars live-vars)))) ;; Add a live variable: (loop (cdr e-) (cons (car e-) result) (replace-live-vars live-vars (cons (assq (tok-n (car e-)) vars) (live-var-info-vars live-vars))) #t)] [(and (memq (tok-n (car e-)) '(while do for)) (case (tok-n (car e-)) [(do) (not (braces? (car result)))] [(for) (not (braces? (cadr result)))] [(while) (not (or (eq? semi (tok-n (cadr result))) (braces? (cadr result))))])) (log-error "[LOOP] ~a in ~a: while/do/for with body not in braces." (tok-line (car e-)) (tok-file (car e-))) (loop (cdr e-) (cons (car e-) result) live-vars #t)] [else (when (and check-arith? (not memcpy?) (positive? (live-var-info-num-calls live-vars))) (when (and (memq (tok-n (car e-)) '(+ - ++ -- += -=)) (let ([assignee (cdr e-)]) (or (and (not (null? assignee)) (assq (tok-n (car assignee)) vars)) ;; Special case: (YYY -> ivar) + ...; (let ([special-case-type (and (not (null? assignee)) (let ([m (resolve-indirection (car assignee) get-c++-class-var c++-class vars)]) (and m (cdr m))))]) (and special-case-type (pointer-type? special-case-type)))))) ;; __u comes from memset in some variants of gcc (unless (eq? '__u (tok-n (cadr e-))) (log-warning "[ARITH] ~a in ~a: suspicious arithmetic, LHS ends ~s." (tok-line (car e-)) (tok-file (car e-)) (tok-n (cadr e-)))))) (loop (cdr e-) (cons (car e-) result) live-vars converted-sub?)])))) (define (convert-seq-interior v comma-sep? vars &-vars c++-class live-vars complain-not-in memcpy?) (let ([e (seq->list (seq-in v))]) (let-values ([(pragmas el) (body->lines e comma-sep?)]) (unless (null? pragmas) (error 'convert-seq-interior "unexpected pragmas")) (let-values ([(el live-vars) (let loop ([el el]) (if (null? el) (values null live-vars) (let-values ([(rest live-vars) (loop (cdr el))]) (let-values ([(e live-vars) (convert-function-calls (car el) vars &-vars c++-class live-vars complain-not-in memcpy? #f)]) (values (cons e rest) live-vars)))))]) (values ((get-constructor v) (tok-n v) (tok-line v) (tok-file v) (seq-close v) (list->seq (apply append el))) live-vars))))) (define (convert-paren-interior v vars &-vars c++-class live-vars complain-not-in memcpy?) (convert-seq-interior v #t vars &-vars c++-class live-vars complain-not-in memcpy?)) (define (find-&-vars e) (let loop ([e e]) (cond [(null? e) null] [(pragma? (car e)) (loop (cdr e))] [(eq? '& (tok-n (car e))) (if (null? (cdr e)) null (let ([next (let loop ([next (cadr e)]) (cond [(symbol? (tok-n next)) next] [(seq? (tok-n next)) (let ([l (seq->list (seq-in next))]) (if (null? l) #f (loop (car l))))] [else #f]))]) (if next (cons (tok-n next) (loop (cdr e))) (loop (cdr e)))))] [(seq? (car e)) (append (find-&-vars (seq->list (seq-in (car e)))) (loop (cdr e)))] [else (loop (cdr e))]))) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Palm call-graph ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (call-graph name e) (let ([body-v (let* ([len (sub1 (length e))] [v (list-ref e len)]) ;; Function may have trailing semicolon: (if (eq? semi (tok-n v)) (list-ref e (sub1 len)) v))]) (call-graph/body name (seq->list (seq-in body-v))))) (define (call-graph/body name body-e) (let-values ([(pragmas el) (body->lines body-e #f)]) (for-each (lambda (v) (call-graph/stmt name v)) el))) (define (call-graph/stmt name e) ;; e is a single statement (for-each (lambda (v) (cond [(seq? v) (call-graph/body name (seq->list (seq-in v)))] [(assq (tok-n v) (prototyped)) (fprintf map-port "(call ~s ~s)\n" name (tok-n v))] [else (void)])) e)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; More "parsing", main loop ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (body->lines e comma-sep?) (let loop ([e e][pragmas null]) (if (or (null? e) (not (pragma? (car e)))) (values (reverse pragmas) (reverse (foldl-statement e comma-sep? (lambda (sube l) (cons sube l)) null))) (loop (cdr e) (cons (car e) pragmas))))) (define (split-decls el) (let loop ([el el][decls null]) (if (null? el) (values (reverse decls) null) (let ([e (car el)]) (if (or ;; These keywords appear only in decls: (memq (tok-n (car e)) '(union struct static)) ;; Otherwise try harder: (and ;; Decl needs at least three parts: (< 2 (length e)) ;; Decl ends in seimicolon (eq? semi (tok-n (list-ref e (sub1 (length e))))) ;; Doesn't start with a star, decrement, increment, or global call (not (memq (tok-n (car e)) '(* -- ++ |::|))) ;; Not an assignemnt (not (memq (tok-n (cadr e)) '(= += -=))) ;; Not a return, case, new, or delete (not (memq (tok-n (car e)) '(return case new delete delete_wxobject))) ;; Not a label, field lookup, pointer deref, class-specific (not (memq (tok-n (cadr e)) '(|:| |.| -> |::|))) ;; No parens/braces in first two parts, except __typeof (not (seq? (car e))) (or (not (seq? (cadr e))) (eq? '__typeof (tok-n (car e)))))) ;; Looks like a decl (loop (cdr el) (cons e decls)) ;; Not a decl (values (reverse decls) el)))))) (define braces-then-semi '(typedef struct union enum __extension__)) (define (get-one e comma-sep?) (let loop ([e e][result null][first #f][second #f]) (cond [(null? e) (values (reverse result) null)] [(pragma? (car e)) (unless (null? result) (error 'pragma "unexpected pragma: ~a at: ~a:~a" (pragma-s (car e)) (pragma-file (car e)) (pragma-line (car e)))) (values (list (car e)) (cdr e))] [(eq? semi (tok-n (car e))) (values (reverse (cons (car e) result)) (cdr e))] [(and (eq? '|,| (tok-n (car e))) comma-sep?) (values (reverse (cons (car e) result)) (cdr e))] [(and (braces? (car e)) (not (memq first '(typedef enum __extension__))) (or (not (memq first '(static extern const struct union))) (equal? second "C") ; => extern "C" ... (equal? second "C++") ; => extern "C++" ... (ormap parens? result))) ; => function prototype (let ([rest (cdr e)]) (if (or (null? rest) (pragma? (car rest)) (not (eq? semi (tok-n (car rest))))) (values (reverse (cons (car e) result)) rest) (values (reverse (list* (car rest) (car e) result)) (cdr rest))))] [else (loop (cdr e) (cons (car e) result) (or first (tok-n (car e))) (or second (and first (tok-n (car e)))))]))) (define (foldl-statement e comma-sep? f a-init) (let loop ([e e][a a-init]) (cond [(null? e) a] [else (let-values ([(sube e) (get-one e comma-sep?)]) (loop e (f sube a)))]))) ; (print-it e 0 #t) (exit) (define (process-top-level e init-file can-drop-vars?) (foldl-statement e #f (lambda (sube l) (let* ([sube (top-level sube init-file can-drop-vars?)]) (append l sube))) null)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let* ([e e-raw] [line -inf.0] [file #f] [sysheader? #f]) (set! e-raw #f) ;; to allow GC (foldl-statement e #f (lambda (sube where) (let* ([where (if (pragma? (car sube)) where (or (tok-file (car sube)) where))] [sube (top-level sube where #t)]) (let-values ([(l f s?) (print-it sube 0 #t #f line file sysheader? keep-lines?)]) (set! line l) (set! file f) (set! sysheader? s?)) where)) #f)) (define (marshall v) (let loop ([v v]) (cond [(struct? v) (let ([vec (struct->vector v)]) (if (eq? 'struct:tok (vector-ref vec 0)) (list 'make-short-tok (loop (vector-ref vec 1))) (cons (car (hash-table-get makers (vector-ref vec 0))) (map loop (cdr (vector->list vec))))))] [(list? v) (cons 'list (map loop v))] [(pair? v) (list 'cons (loop (car v)) (loop (cdr v)))] [(vector? v) (cons 'vector (map loop (vector->list v)))] [(symbol? v) (list 'quote v)] [else v]))) (when precompiling-header? (parameterize ([current-inspector power-inspector] [print-struct #t]) (let ([e (list 'list (list 'quote (hash-table-map used-symbols cons)) (marshall c++-classes) (marshall (prototyped)) (marshall (top-vars)) (marshall pointer-types) (marshall non-pointer-types) (marshall struct-defs) non-gcing-functions (list 'quote gc-var-stack-mode))]) (with-output-to-file (change-suffix file-out #".zo") (lambda () (let ([orig (current-namespace)]) (parameterize ([current-namespace (make-namespace)]) (namespace-attach-module orig 'mzscheme) (namespace-require 'mzscheme) (eval #'(define-syntaxes (#%top-interaction) (lambda (stx) (cdr (syntax-e stx))))) (write (compile e))))) 'truncate)))) (when precompiling-header? (let loop ([i 1]) (unless (i . > . gentag-count) (printf "#undef XfOrM~a_COUNT\n" i) (printf "#undef SETUP_XfOrM~a\n" i) (loop (add1 i))))) (close-output-port (current-output-port)) (when exit-with-error? (error 'xform "Errors converting")) (when output-depends-info? (with-output-to-file (change-suffix file-out #".sdep") (lambda () (write (hash-table-map depends-files (lambda (k v) k))) (newline)) 'truncate/replace))))))