; CPU description file generator for the GNU assembler testsuite. ; Copyright (C) 2000, 2001, 2009 Red Hat, Inc. ; This file is part of CGEN. ; See file COPYING.CGEN for details. ; This is invoked to build allinsn.exp and a script to run to ; generate allinsn.s and allinsn.d. ; Specify which application. (set! APPLICATION 'GAS-TEST) ; Called before/after the .cpu file has been read. (define (gas-test-init!) (opcodes-init!)) (define (gas-test-finish!) (opcodes-finish!)) ; Called after .cpu file has been read and global error checks are done. ; We use the `tmp' member to record the syntax split up into its components. (define (gas-test-analyze!) (opcodes-analyze!) (map (lambda (insn) (elm-xset! insn 'tmp (syntax-break-out (insn-syntax insn) (obj-isa-list insn)))) (non-multi-insns (current-insn-list))) *UNSPECIFIED* ) ; Methods to compute test data. ; The result is a list of strings to be inserted in the assembler ; in the operand's position. ; For a general assembler operand, just turn the value into a string. (method-make! 'test-data (lambda (self ops) (map (lambda (op) (cond ((null? op) "") ((number? op) (number->string op)) (else (error "unsupported assembler operand" op)))) ops)) ) ; For a keyword operand, choose the appropriate keyword. ; OPS is a list of values, e.g. from an ifield. (method-make! 'test-data (lambda (self ops) (let* ((test-cases (elm-get self 'values)) (prefix (elm-get self 'name-prefix)) (find-kw (lambda (val) (find-first (lambda (kw) (= (cadr kw) val)) test-cases)))) (map (lambda (n) ;; If an ifield has, e.g., 2 bits (values 0,1,2,3) and the keyword ;; only has two values, e.g. (foo 0) (bar 1), then we can get ;; invalid requests, i.e. for ifield values of 2 and 3. ;; It's not clear what to do here, but it seems like this is an ;; error in the description file. ;; So it seems like we should flag an error for invalid requests. ;; OTOH, we're just generating testcases. So instead we just ;; flag a warning and cope by returning the first keyword in the ;; list. (let ((kw (find-kw n))) (if (not kw) (begin (message "WARNING: Invalid test data request for keyword " (obj:name self) ": " n ".\n" " Compensating by picking a different value.\n") (set! kw (car test-cases)))) (string-append (if (and (not (string=? prefix "")) (eq? (string-ref prefix 0) #\$)) "\\" "") prefix (->string (car kw))))) ops))) ) (method-make! 'test-data (lambda (self ops) (let* ((test-cases '("foodata" "4" "footext" "-4")) (nr-ops (length ops)) (selection (map (lambda (z) (random (length test-cases))) (iota nr-ops)))) (map (lambda (n) (list-ref test-cases n)) selection))) ) (method-make! 'test-data (lambda (self ops) (let* ((test-cases '("footext" "4" "foodata" "-4")) (nr-ops (length ops)) (selection (map (lambda (z) (random (length test-cases))) (iota nr-ops)))) (map (lambda (n) (list-ref test-cases n)) selection))) ) (method-make-forward! 'indices '(test-data)) (method-make-forward! 'values '(test-data)) ; Test data for a field is chosen firstly out of some bit patterns, ; then randomly. It is then interpreted based on whether there ; is a decode method. (method-make! 'test-data (lambda (self n) (let* ((bf-len (ifld-length self)) (field-max (inexact->exact (round (expt 2 bf-len)))) (highbit (quotient field-max 2)) (values (map (lambda (n) (case n ((0) 0) ((1) (- field-max 1)) ((2) highbit) ((3) (- highbit 1)) ((4) 1) (else (random field-max)))) (iota n))) (decode (ifld-decode self))) (if decode ; FIXME: need to run the decoder. values ; no decode method (case (mode:class (ifld-mode self)) ((INT) (map (lambda (n) (if (>= n highbit) (- n field-max) n)) values)) ((UINT) values) (else (error "unsupported mode class" (mode:class (ifld-mode self)))))))) ) ;; Return N values for assembler test data, or nil if there are none ;; (e.g. scalars). ;; ??? This also returns nil for str-expr and rtx. (method-make! 'test-data (lambda (self n) (case (hw-index:type self) ((ifield operand) (send (hw-index:value self) 'test-data n)) ((constant enum) (make-list n (hw-index-constant-value self))) ((scalar) (make-list n nil)) ((str-expr rtx) (make-list n nil)) ;; ??? (else (error "invalid hw-index type" (hw-index:type self))))) ) (method-make! 'test-data (lambda (self n) (send (op:type self) 'test-data (send (op:index self) 'test-data n))) ) ; Given an operand, return a set of N test data. ; e.g. For a keyword operand, return a random subset. ; For a number, return N numbers. (define (operand-test-data op n) (send op 'test-data n) ) ; Given the broken out assembler syntax string, return the list of operand ; objects. (define (extract-operands syntax-list) (let loop ((result nil) (l syntax-list)) (cond ((null? l) (reverse! result)) ((object? (car l)) (loop (cons (car l) result) (cdr l))) (else (loop result (cdr l))))) ) ; Collate a list of operands into a test test. ; Input is a list of operand lists. Returns a collated set of test ; inputs. For example: ; ((r0 r1 r2) (r3 r4 r5) (2 3 8)) => ((r0 r3 2) (r1 r4 3) (r2 r5 8)) ; L is a list of lists. All elements must have the same length. (define (/collate-test-set L) (if (= (length (car L)) 0) '() (cons (map car L) (/collate-test-set (map cdr L)))) ) ; Given a list of operands for an instruction, return the test set ; (all possible combinations). ; N is the number of testcases for each operand. ; The result has N to-the-power (length OP-LIST) elements. (define (build-test-set op-list n) (let ((test-data (map (lambda (op) (operand-test-data op n)) op-list)) (len (length op-list))) (cond ((= len 0) (list (list))) (else (/collate-test-set test-data)))) ) ; Given an assembler expression and a set of operands build a testcase. ; TEST-DATA is a list of strings, one element per operand. (define (build-asm-testcase syntax-list test-data) (let loop ((result nil) (sl syntax-list) (td test-data)) ;(display (list result sl td "\n")) (cond ((null? sl) (string-append "\t" (apply string-append (reverse result)) "\n")) ((string? (car sl)) (loop (cons (car sl) result) (cdr sl) td)) (else (loop (cons (car td) result) (cdr sl) (cdr td))))) ) ; Generate the testsuite for INSN. ; FIXME: make the number of cases an argument to this application. (define (gen-gas-test insn) (logit 2 "Generating gas test data for " (obj:name insn) " ...\n") (string-append "\t.text\n" "\t.global " (gen-sym insn) "\n" (gen-sym insn) ":\n" (let* ((syntax-list (insn-tmp insn)) (op-list (extract-operands syntax-list)) (test-set (build-test-set op-list 8))) (string-map (lambda (test-data) (build-asm-testcase syntax-list test-data)) test-set)) ) ) ; Generate the shell script that builds the .d file. ; .d files contain the objdump result that is used to see whether the ; testcase passed. ; We do this by running gas and objdump. ; Obviously this isn't quite right - bugs in gas or ; objdump - the things we're testing - will cause an incorrect testsuite to ; be built and thus the bugs will be missed. It is *not* intended that this ; be run immediately before running the testsuite! Rather, this is run to ; generate the testsuite which is then inspected for accuracy and checked ; into CVS. As bugs in the testsuite are found they are corrected by hand. ; Or if they're due to bugs in the generator the generator can be rerun and ; the output diff'd to ensure no errors have crept back in. ; The point of doing things this way is TO SAVE A HELL OF A LOT OF TYPING! ; Clearly some hand generated testcases will also be needed, but this ; provides a good test for each instruction. (define (cgen-build.sh) (logit 1 "Generating gas-build.sh ...\n") (string-append "\ #/bin/sh # Generate test result data for " (->string (current-arch-name)) " GAS testing. # This script is machine generated. # It is intended to be run in the testsuite source directory. # # Syntax: build.sh /path/to/build/gas if [ $# = 0 ] ; then if [ ! -x ../gas/as-new ] ; then echo \"Usage: $0 [/path/to/gas/build]\" else BUILD=`pwd`/../gas fi else BUILD=$1 fi if [ ! -x $BUILD/as-new ] ; then echo \"$BUILD is not a gas build directory\" exit 1 fi # Put results here, so we preserve the existing set for comparison. rm -rf tmpdir mkdir tmpdir cd tmpdir function gentest { rm -f a.out $BUILD/as-new ${1}.s -o a.out echo \"#as:\" >${1}.d echo \"#objdump: -dr\" >>${1}.d echo \"#name: $1\" >>${1}.d $BUILD/../binutils/objdump -dr a.out | \ sed -e 's/(/\\\\(/g' \ -e 's/)/\\\\)/g' \ -e 's/\\$/\\\\$/g' \ -e 's/\\[/\\\\\\[/g' \ -e 's/\\]/\\\\\\]/g' \ -e 's/[+]/\\\\+/g' \ -e 's/[.]/\\\\./g' \ -e 's/[*]/\\\\*/g' | \ sed -e 's/^.*file format.*$/.*: +file format .*/' \ >>${1}.d rm -f a.out } # Now come all the testcases. cat > allinsn.s <string (current-arch-name))) " assembler testsuite. -*- Tcl -*- if [istarget " (->string (current-arch-name)) "*-*-*] { run_dump_test \"allinsn\" }\n" ) )