計算機科学実験及演習3 (ソフトウェア) 初回座学 担当教員: 大本義正 林冬恵 馬谷誠二 URL: http://www.fos.kuis.kyoto-u.ac.jp/~umatani/le3b/ mailto: [email protected]u.ac.jp 2 初回座学の内容 1. 2. 3. 実験の概要 字句解析器 構文解析器 3 実験内容 • Small Cコンパイラの作成 • Racket言語(Scheme言語の一種)を用いて作成 • ターゲット言語はMIPSアセンブリ言語 • 一人一つ作成 • 上位互換であれば言語を拡張してもよい • 講義「コンパイラ」の内容(Tiny Cコンパイラ) や実験資料と異なる設計をしてもよい • 選択課題(後述)へ積極的に取り組むと評価 があがる Small C 4 gccでコンパイル可能 • C言語のサブセット • 型はint,一次元配列,intへのポインタのみ • 制御構造や演算子は最低限のものだけ int a[2]; void swap(int *p, int *q) { int tmp = *p; *p = *q; *q = tmp; } void main() { a[0] = 1; a[1] = 2; swap(&a[0], &a[1]); /* swap(a, a+1); とも書ける */ print(a[0]); print(a[1]); } 5 コンパイラの構成 プログラムテキスト int main(int argc, char **argv) { printf("Hello!¥n"); return 0; } 0000000 0000020 0000040 0000060 0000100 0000120 0000140 0000160 0000200 0000220 240 ……… facf 0010 0019 4f52 0000 0000 0000 5f5f 0000 0000 feed 0000 0000 0000 0000 0000 0000 4554 0000 0000 0007 05b0 0048 0000 0001 0000 0000 5458 0001 0000 0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 機械語 意味解析・ 最適化 トークン列 INT; ID(“main”); LPAREN; INT; ID(“argc”); CHAR; AST; AST; ID(“argv”); RPAREN; LBRACE; … 字句解析 0003 0085 5f5f 0000 0000 0000 0019 0000 1000 1000 抽象構文木 8000 0020 4150 0000 0000 0000 0000 0000 0000 0000 0002 0000 4547 0000 0000 0000 0228 0000 0000 0000 0000 0000 455a 0000 0000 0000 0000 0000 0000 0000 アセンブル proc “main” “argc” 構文解析 int .data L0: .asciiz “Hello!¥n” .text .globl main main: subu $sp,20,$sp addu $fp,$sp,8 li $t0,L0 sw $t0,0($sp) … arg s stmts “argv” char** call “printf” main : int->char**->int set x “Hello!¥n” call ret printf [x] set y 0 return y アセンブリ 中間命令列 生成 アセンブリ 最適化 最適化 5 … … 6 実験の進め方 • 実験資料のコンパイラは講義「コンパイラ」のTiny C に沿った実装 • 講義で学んだ内容の理解度 • 実験資料の課題を順に解く • 課題10, 11,14, 16がこの実験の主要部分 • 費やす時間もこの部分が大きい • 課題3,4はレポートに書く必要なし • [選択]と書かれた課題はやらなくても良い • 各課題の解答となるソースファイルは残しておくこと • 課題ごとにディレクトリを分ける • あるいはgitのようなバージョン管理ツールを使っても良い 7 課題内容一覧 課題番号 必須 内容 1 ○ 2 Tiny C Small Cプログラミング 拡張機能の設計 3, 4 ○ 電卓インタプリタの作成 5, 6 ○ Small C構文解析器の作成 ○ 7 ○ 抽象構文木の操作 △ 8, 9 シンタックスシュガーの実装 10 ○ 意味解析部の作成 11 ○ 中間表現への変換 12, 13 14 データフロー解析と最適化 ○ 15 16 17 ○ 相対番地の計算 ○ レジスタ割り当て ○ コード生成 覗き穴最適化 ○ 8 成績評価 • レポート3回 • 中間レポート1(〜課題7) • 中間レポート2(〜課題10) • 最終レポート • 提出内容の詳細はwebページ参照 • 提出方法: PandAの「提出箱」へアップロード(後述) • 報告会(7月17日(金)) • サンプルプログラムの実行 etc. • 評価方法 • コンパイラの完成度 • レポートの出来(詳しくて分かりやすい説明) • [選択]課題の加点 9 [注意] コード最適化の方針について • この実験では 1. メモリ参照(ロード,ストア命令)数 2. 全命令数 3. メモリ使用量(スタックサイズ) が少なければ少ないほど,より良いアセンブリ コードとする • 優先順位はこの順 10 ミニ座学 • 補足説明などを(おもにTAが)行う • 演習室で1回あたり0.5〜1時間程度 • スケジュール(全6回の予定) 実験日 内容 6/4(木) git入門,Racketドキュメントについて,etc. 6/5(金) 抽象構文木の処理 6/19(金) 意味解析部の補足説明 6/25(木) データフロー解析(講義内容の実装の仕方) 7/2(木) レジスタ割り当て 7/10(金) MIPSコード生成(講義内容のおさらい) 11 その他の注意(1) • 出席を重視する • 止むを得ない場合はできるだけ事前に連絡する • 積極的にTA・教員に訊く • 予習をしっかりと • 予め資料を読み,理解し,方針を立てておく • 演習時間は「実装とデバックの時間」 • 教員・TA宛メールアドレスも有効活用しましょう • [email protected]u.ac.jp 12 その他の注意(2) • PandAの利用 • レポート提出 • 自分の「提出箱」の中に,必ずレポート毎にフォルダを 別々にして提出すること • 講義「コンパイラ」の配布資料一式が「リソース」に 置いてあります • 参考書について • 「コンパイラ」というタイトルの2つの本が,それぞれ 10冊程度用意されています • [選択]課題に取り組むときの参考にしてください 13 PandA提出箱 14 グループ分け(座席の固定) • TAによる進捗状況の把握のため • この後,演習室に行ったらすぐにします 15 Racket以外で演習するには • 実験資料の読み替えは自己責任 • 過去の配布資料(実装言語はC言語)を参考までに webページに置いておきます • 言語仕様,ターゲットアーキテクチャが異なるので注意 • TAが対応可能な言語 Python, Ruby, Haskell, OCaml, C++, C# • グループ分けで考慮するので,はじめに申し出て ください 16 2.字句解析器の作成 17 parser-tools/lex • 字句解析器(有限オートマトン)を仕様から 自動生成 • Racketの標準ライブラリ(requireするだけ) 1. 正規表現を扱う機能 2. 字句解析器を生成する機能(lexerマクロ) • 引数: 字句構造と対応するアクションの集合 • 返り値: Racket関数 • 引数: portオブジェクト(入力ソース) • 返り値: Racketの任意の値(コンパイラの場合はトークン) 18 字句解析器の例 プレフィックスを つけて使用 #lang racket (require parser-tools/lex (prefix-in : parser-tools/lex-sre)) (define-lex-abbrevs (digit (char-range "0" "9"))) 省略記法の定義 字句構造とアクションの組 (define calc-lexer (lexer ((:+ digit) (string->number lexeme)) ("+" '+) ("-" '-) ("¥n" 'newline) (whitespace (calc-lexer input-port)) ((eof) 'eof))) 19 Racketにおける正規表現 • 連結: ab (: a b) • 選択: a | b (:or a b) • 閉包: a* (:* a) • その他の便利記法 • (:? a), (:+ a) • (char-range c1 c2) • whitespace, (eof), etc. 20 lexer • (lexer (パターン1 アクション式1) (パターン2 アクション式2) ...)) • パターン: 字句構造の正規表現 • アクション式: 字句要素(ソース中の部分文字列)を切り 出したときに評価される式 • 任意のRacket式 • 典型的には,トークン(字句要素を構文解析に適した形に変換した データ)を返す • 入力からパターンに一致する最長の文字列を 探し,見つかれば対応するアクションを実行 • 複数のパターンと一致するなら先に書いてある方 を優先 21 (define-lex-abbrevs (digit (char-range "0" "9"))) マッチした文字列 (define calc-lexer (lexer ((:+ digit) (string->number lexeme)) ("+" '+) ("++" '++) 長い方を優先 ("-" '-) ("¥n" 'newline) (whitespace (calc-lexer input-port)) ((eof) 'eof))) 上のルールを優先 入力portオブジェクト 22 トークンオブジェクト • 構文解析器へ渡すデータの型 • 2つの属性を持つ • 種類: 識別子,整数定数,etc. • 値: 識別子の名前,整数値,etc. (define-empty-tokens t1 (+ - NEWLINE EOF)) (define-tokens t2 (ID NUM)) (token-+) (token-NEWLINE) (token-ID ’foo) (token-ID 123) 字句解析器の例(トークン版) (define-empty-tokens t1 (+ - NEWLINE EOF)) (define-tokens t2 (NUM)) (define-lex-abbrevs (digit (char-range "0" "9"))) (define calc-lexer (lexer ((:+ digit) (token-NUM (string->number lexeme))) ("+" (token-+)) ("-" (token--)) ("¥n" (token-NEWLINE)) (whitespace (calc-lexer input-port)) ((eof) (token-EOF)))) 23 24 実行例 > (define p (open-input-string "1 +23¥n 45 - 6")) > (calc-lexer p) (token 'NUM 1) token構造体 > (calc-lexer p) '+ > (calc-lexer p) シンボル (token 'NUM 23) > (calc-lexer p) 'NEWLINE > (calc-lexer p) (token 'NUM 45) > (calc-lexer p) '> (calc-lexer p) (token 'NUM 6) > (calc-lexer p) 'EOF 25 実際には... • ソースファイルの位置情報の埋め込み, etc. • 詳細は実験資料を参照 26 3.構文解析器の作成 27 parser-tools/yacc • LALR(1)構文解析器を仕様から自動生成 • Racketの標準ライブラリ(requireするだけ) 1. 構文解析器を生成する機能(parserマクロ) • 引数: BNFによる生成規則と対応するアクションの集合 • 返り値: Racket関数 • 引数: Racket関数(字句解析を行う無引数関数) • 返り値: Racketの任意の値(コンパイラの場合は抽象構文木) 構文解析器の例 28 (require parser-tools/yacc) ... ;; 字句解析器とトークンの定義 (define calc-parser (parser (tokens t1 t2) (start program) 諸設定 (end EOF) (error (lambda (o n v) 文法定義 (error "parse error:" n v))) (grammar (program (() (begin (display "calculating...") (newline))) ((program expr NEWLINE) (begin (display $2) (newline)))) (expr ((NUM) $1) ((expr + NUM) (+ $1 $3)) ((expr - NUM) (- $1 $3)))))) 29 諸々の定義 • (tokens グループ名 …) • 文法定義に用いるトークン(終端記号)を宣言 • (start 非終端記号名) • 文法の開始記号を指定 • (end トークン名) • 入力の終端をあらわすトークンを指定 • (error エラー処理関数) • 構文エラーが生じたときに構文解析器から呼び出 されるRacket関数を指定 構文解析器の例 30 (require parser-tools/yacc)) ... ;; 字句解析器とトークンの定義 (define calc-parser (parser (token-EOF) (tokens t1 t2) (start program) トークン情報 (end EOF) (error (lambda (o n v) (error "parse error:" n v))) (grammar (program (() (begin (display "calculating...") (newline))) ((program expr NEWLINE) (begin (display $2) (newline)))) (expr ((NUM) $1) ((expr + NUM) (+ $1 $3)) ((expr - NUM) (- $1 $3)))))) 31 grammar指定 (grammar (非終端記号1 ((ルール1,1) アクション式1,1) ((ルール1,2) アクション式1,2) ...) <非終端記号1> ::= <ルール1,1> ...) | <ルール1,2> | … 非終端記号: BNFの左辺に相当 • ルール: BNFの右辺に相当.(終端または非終端)記号の並び • アクション式: 非終端記号へ還元するときに評価される式 • • 典型的なコンパイラでは抽象構文木を構築して返す 32 構文解析器の例 (define calc-parser (parser 非終端記号 ... ε 終端記号 (grammar (トークン名) (program (() ...) ((program expr NEWLINE) ...) (expr ((NUM) ...) ((expr + NUM) ...) ((expr - NUM) ...))))) 終端記号 (トークン名) 33 アクション式の書き方 • $n: ルール中のn番目の記号の属性値 • 非終端記号: 還元時に評価したアクション式の値 • 終端記号: トークンの値 ... (grammar (program (() (begin (display "calculating...") (newline))) 未定義値 ((program expr NEWLINE) NUMトークンの値 (begin (display $2) (newline)))) (expr ((NUM) $1) ((expr + NUM) (+ $1 $3)) ((expr - NUM) (- $1 $3)))))) exprの値 NUMトークンの値 34 実行例 入力portを生成 > (define (calc-string s) (let ((p (open-input-string s))) (calc-parser (lambda () (calc-lexer p))))) > (calc-string "3+ 45 ¥n calculating... 48 9 > (calc-string "") calculating... > 12 ¥t-3¥n") 無引数の関数 (クロージャ)を生成 35 実際には... • ソースファイル位置情報の扱い • 抽象構文木の構築 • より詳しくは,講義「コンパイラ」の資料 • 別に構造体でなくてもOK(リストなど) 36 抽象構文木の構築に関するヒント • 基本はTiny Cと同じ (struct aop-exp ( ;; 算術二項演算 op ;; 演算子 left ;; 左オペランド right) ;; 右オペランド #:transparent) ...ですが,具象構文そのままではなく • 無駄を省略 • より「自然」な形 で木をつくるよう工夫すると,後のフェーズの作成 が非常に楽になります 37 int *f(int a, int *b); function-prototype ; function-declarator type-specifier int * identifier 返り値型の一部 ( parameter-type-list f parameter-decl (fun-decl (* int) ;; ret-tyフィールド f ;; nameフィ-ルド (int (* int)) ;; parm-tys ) ) int a , parameter-decl int *b プロトタイプ(宣言) には不要 38 ここから先は… • 抽象構文木に対する処理は,ミニ座学2回目で • 実験資料,講義の配布資料・ソースをよく読む [連絡] • 一昨年度以前の講義「コンパイラ」を取っていた 人,昨年度までの実験3を取っていた人は,声を かけてください
© Copyright 2024 ExpyDoc