ATTAPL Seminar 10/25 Types for Low-Level Languages (2)

Slides:



Advertisements
Similar presentations
1 B10 CPU を作る 1 日目 解説 TA 高田正法
Advertisements

2.5 プログラムの構成要素 (1)文字セット ① ASCII ( American Standard Code for Interchange ) JIS コードと同じ ② EBCDIC ( Extended Binary Coded Decimal for Information Code ) 1.
Region-based Memory Management in Cyclone について 発表者 : 前田俊行.
プログラミング言語論 第10回(演習) 情報工学科 木村昌臣   篠埜 功.
プログラミング演習II 2004年11月 30日(第6回) 理学部数学科・木村巌.
情報基礎演習B 後半第5回 担当 岩村 TA 谷本君.
2006/11/30 山下 諒蔵 佐藤 春旗 前田 俊行 大山 恵弘 佐藤 秀明 住井 英二郎
データ構造とアルゴリズム 第10回 mallocとfree
プログラミング言語論 第6回 型 情報工学科 篠埜 功.
2007/1/18 山下 諒蔵 佐藤 春旗 前田 俊行 大山 恵弘 佐藤 秀明 住井 英二郎
第8回 プログラミングⅡ 第8回
2006/10/19 山下 諒蔵 佐藤 春旗 前田 俊行 大山 恵弘 佐藤 秀明 住井英二郎
F11: Analysis III (このセッションは論文2本です)
  【事例演習6】  数式インタプリタ      解 説     “インタプリタの基本的な仕組み”.
プログラミング演習Ⅰ 課題2 10進数と2進数 2回目.
2007/1/11 山下 諒蔵 佐藤 春旗 前田 俊行 大山 恵弘 佐藤 秀明 住井 英二郎
米澤研究室 全体ミーティング 2010/12/22 M2 渡邊裕貴.
ML 演習 第 7 回 新井淳也、中村宇佑、前田俊行 2011/05/31.
Solving Shape-Analysis Problems in Languages with Destructive Updating
TAL : Typed Assembly Language について
型付きアセンブリ言語を用いた安全なカーネル拡張
暗黙的に型付けされる構造体の Java言語への導入
プログラミング 4 記憶の割り付け.
プログラミング言語入門.
2005年度 データ構造とアルゴリズム 第3回 「C言語の復習:再帰的データ構造」
メモリの準備 メモリには、その準備の方法で2種類ある。 静的変数: コンパイル時にすでにメモリのサイズがわかっているもの。 普通の変数宣言
プログラミング入門2 第11回 情報工学科 篠埜 功.
プログラミング入門2 第11回 情報工学科 篠埜 功.
第7回 プログラミングⅡ 第7回
プログラミング言語論 第五回 理工学部 情報システム工学科 新田直也.
アルゴリズムとデータ構造 補足資料5-1 「メモリとポインタ」
A Provably Sound TAL for Back-end Optimization について
国立情報学研究所 ソフトウェア研究系 助教授/
 型推論1(単相型) 2007.
依存型で型付けされた 副作用のある関数のための 型保存コンパイラ
P n ポインタの基礎 5 q m 5 7 int* p; int 型の変数を指すポインタ int* q; int 型の変数を指すポインタ int n=5, m=7; int 型の変数 int array[3]; int* pArray[3]; p = &n; ポインタにアドレスを代入しているのでOK.
コンパイラ資料 実行時環境.
Development of Certified Program Translators to Verify Low-Level Language Programs Toshihiro YOSHINO (Yonezawa Lab, Univ. of Tokyo)
オブジェクト指向プログラミングと開発環境
コンパイラ 2011年10月20日
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
プログラミング言語論 第六回 理工学部 情報システム工学科 新田直也.
 型推論3(MLの多相型).
アルゴリズムとプログラミング (Algorithms and Programming)
文字列へのポインタの配列 static char *lines[MAXLINES]; lines[0] NULL
文法と言語 ー文脈自由文法とLR構文解析ー
ポインタとポインタを用いた関数定義.
米澤研究室 全体ミーティング 2010/07/07 M2 渡邊裕貴.
Type Systems and Programming Languages ; chapter 13 “Reference”
コンピュータアーキテクチャ 第 2 回.
コンピュータアーキテクチャ 第 5 回.
アルゴリズムとデータ構造1 2009年6月15日
オブジェクト指向言語論 第五回 知能情報学部 新田直也.
コンピュータアーキテクチャ 第 5 回.
Eijiro Sumii and Naoki Kobayashi University of Tokyo
SMP/マルチコアに対応した 型付きアセンブリ言語
オブジェクト指向言語論 第二回 知能情報学部 新田直也.
コンパイラ 第12回 実行時環境 ― 変数と関数 ― 38号館4階N-411 内線5459
アルゴリズムとデータ構造 2010年6月17日
全体ミーティング(6/3) 修士2年 飯塚 大輔.
2005年度 データ構造とアルゴリズム 第2回 「C言語の復習:配列」
プログラミング演習II 2004年11月 16日(第5回) 理学部数学科・木村巌.
POPL ミーティング 6/7 Inside ADL
全体ミーティング(9/15) 村田雅之.
オブジェクト指向言語論 第三回 知能情報学部 新田直也.
プログラミング演習II 2003年12月10日(第7回) 木村巌.
情報処理Ⅱ 小テスト 2005年2月1日(火).
プログラミング演習II 2003年10月29日(第2,3回) 木村巌.
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
Safety Checking of Machine Code
Presentation transcript:

ATTAPL Seminar 10/25 Types for Low-Level Languages (2) 米澤研究室 D1 吉野 寿宏 <tossy-2@yl.is.s.u-tokyo.ac.jp>

前回(4.1~4.2)のおさらい RISC-like な低級言語 TAL-0 を設計 言語と型システムの定義 mov, add, jump, brz の命令 Control-Flow Safety を満たすことを証明 Progress, Preservation の手法による

今回の概要 TAL-1: Simple Memory Safety (4.3~4.5) その他の言語機構への拡張 (4.6, 4.7) Object、Closure Existential Type Arrays、Arithmetics Dependent Type の応用

TAL-1: Simple Memory Safety

TAL-1 データに関するプリミティブサポートを追加 メモリ安全性を保証する型システムを設計 オブジェクトの参照(ポインタ)によるアクセス ヒープ、スタック エリアスとの闘い メモリ安全性を保証する型システムを設計 確保済みのメモリ以外にはアクセスしないこと

型理論的な観点からすると… データを扱う場合、型の strong update が必要 低級言語では、データの初期化を組み合わせで表現しなければならない 高級言語では、まとめて初期化するプリミティブを用意できる レジスタについては TAL-0 で導入済 たとえば以下のようなコードは排除したい 5 行目で r4 == 0 なので control-flow safe でない {r1: ptr(code(...))} r3 := 0; Mem[r1] := r3; r4 := Mem[r1]; jump r4

型理論的な観点からすると… しかしエリアシングの問題がある メモリは 2 箇所以上から同じ場所を参照可能 たとえば次のコードで r1 == r2 だったら? 先ほどのスライドと同じ現象になる しかし r1 と r2 が同じ場所を指していないなら全く問題はない {r1: ptr(code(...)), r2:...} r3 := 0; Mem[r1] := r3; r4 := Mem[r2]; jump r4

エリアスとどう付き合うか 完全なロジックは存在しないが、いくつか静的 解析の理論はある Alias types [Smith et al. 2000; Walker et al. 2001; DeLine et al. 2001] 型システムによりリファレンスを追跡 TAL-1 では、ポインタを 2 種類に分類する アプローチをとる Shared pointer: エリアスするかもしれないポインタ 型は上書きできない Unique pointer: エリアスのないポインタ strong update できるが、ポインタのコピーはできない

TAL-1 Syntax レジスタ オペランド ヒープに割り当てられるオブジェクト sp (スタックポインタ)レジスタ追加   : コードまたは shared data へのポインタ uptr(h) : unique data ポインタ ヒープに割り当てられるオブジェクト タプル        追加

TAL-1 Syntax 命令セット (追加分) rd := Mem[rs + n] ロード Mem[rd + n] := rs ストア rd := malloc n n ワードを確保 割り当てられたポインタは unique pointer になる commit rd shared pointer 化 Unique pointer を shared pointer に coerce salloc n スタックで n ワード確保 sfree n スタックを n ワード解放

TAL-1 Semantics MOV-1 ルール MALLOC ルール COMMIT ルール RHS が unique pointer でない場合のみ代入を許す MALLOC ルール             をレジスタに返す この段階ではまだヒープ(H)は書き換えない COMMIT ルール uptr の中身を取り出して H に追加、ラベル  を 割り当てる uptr はスタックにも使われているので、sp レジスタはとれない

TAL-1 Semantics LD-S・LD-U / ST-S・ST-U ルール SALLOC/SFREE ルール ほぼ直感的といえるでしょう Shared/unique の違いは、ヒープ(H)から取るか uptr から取り出すかの違い SALLOC/SFREE ルール スタックを前のほうに n ワード伸ばす/前から n ワード切り捨てる スタック内のオブジェクトへの参照は sp レジスタを 介してのみ可能にするため、uptr を使う

例: malloc を使う malloc の例: tuple の deep copy H copy: {r1:ptr(int, int), r2, r3:int} r2 := malloc 2; r3 := Mem[r1]; Mem[r2] := r3; r3 := Mem[r1 + 1]; Mem[r2 + 1] := r3; commit r2; {r1: ptr(int,int), r2: ptr(int,int), r3: int} H r1 1 2 r2 ??? r3 int 1 2 1 ??? 2

Exercise 4.3 4.3.1 4.3.2 push v ≡ salloc 1; Mem[sp] := v pop rd ≡ rd := Mem[sp]; sfree 1 最適化としては、salloc/sfree をまとめて発行 4.3.2 Semantics の書き換えは straightforward Unique pointer が share されていないことは、命令列の長さに関する帰納法で証明可能

TAL-1 Type System ポインタ型を追加 ヒープ内のデータの型 σ ptr(σ) : shared pointer type uptr(σ) : unique pointer type ヒープ内のデータの型 σ ε : 空 τ : value σ1,σ2 : 連結 連結は、結合則を満たし ε を単位元とするモノイドになる ⇒ () のつけ・はずしによって変わらない ρ : allocated type の型変数

TAL-1 Type System Typing Rules ヒープ上のデータのルールについては 直観的なので省略 命令列 S-MOV-1 ルール RHS が uptr 型でないことのチェック追加 S-MALLOC ルール n ≧ 0 チェック n 個の int からなる tuple を作成 Semantics での <m1, …, mn> と対応がついている

TAL-1 Type System Typing Rules 命令列 (cont’d) S-COMMIT ルール COMMIT ルールと同様のチェック(sp でないこと) S-LDS, S-LDU ルール RHS の型が ptr か uptr かの違い S-STS, S-STU ルール S-LDS, S-LDU とほぼ同じ ただし MOV と同様に、RHS が uptr でないことを確認する必要がある

TAL-1 Type System Typing Rules 命令列 (cont’d) S-SALLOC ルール スタックの伸張を連結で記述 この型システムではスタックオーバーフローは検出できない 意味論の SALLOC ルールではオーバーフロー防止があった S-SFREE ルール 先頭 n 要素を削る オーバーフローとは逆に、アンダーフローは検出 できる uptr の中身が空になると S-SFREE のルールは失敗

TAL-1 Type System “Allocated Type Variables”? メモリのチャンクを変数として扱う α, β, … といった型変数はワードサイズの値を表現 Allocated type variables は任意の長さのデータを表現 Allocated type variables は型検査ルールでは消去できない データ長がわからないので

Exercise 4.4.1 Soundness of TAL-1 Type System Proof Sketch Canonical Values, Canonical Operands に ついて追加された部分を証明 タプルについては長さに関する帰納法で証明 code(Γ), ptr(σ) は  に対応 uptr(σ) はヒープにないタプルに対応 その後 TAL-0 と同様の議論を繰り返す Well-typed になるための条件から、意味論の事前条件が導出できればよい

Exercise 4.4.1 Soundness of TAL-1 Type System rd := v; I の場合 型付け規則の逆と Canonical Values より v = n または  (≠ uptr(h))である したがって MOV-1 の前提条件が満たされる commit rd; I の場合 (rd) = <...> かつ rd ≠ sp である したがって COMMIT の事前条件が満たされる rd := Mem[rs + n]; I の場合 …

Compiling to TAL-1 TAL-1 はコンパイラターゲットとして十分な表現力を 持っている 整数、タプル、レコード、関数ポインタ (closure はまだない) prod: ∀a,b,c,s. {...} r2 := Mem[sp]; r3 := Mem[sp + 1]; r1 := 0; jump loop loop: ∀s. {r1,r2,r3: int, sp:uptr(...), ...} if r2 jump done; r1 := r1 + r3; r2 := r2 + (-1); done: ∀s. {r1,r2,r3: int, sp:uptr(...), ...} sfree 2; jump r4 int prod(int x, int y) { int a = 0; while(x != 0) { a = a + y; x = x – 1; } return a;

Compiling to TAL-1 コーリングシーケンスは以下の通りとする prod の例でわかるとおり、コードは直観的 引数はスタックに積んで渡され、callee が 解放する 戻り先アドレスは r4 に格納されている 関数の返値は r1 に格納される prod の例でわかるとおり、コードは直観的

Compiling to TAL-1 Type Translation 重要なのは、型がどのように変換されるか 例: prod の型は? ⇒∀a, b, c, s. code{ r1:a, r2:b, r3:c, sp:uptr(int, int, s), r4: ∀d, e, f. code{ r1:int, r2:d, r3:e, r4:f, sp:uptr(s) } } なんでもいい 引数 返値

Compiling to TAL-1 Type Translation prod: ∀a, b, c, s. code{ r1:a, r2:b, r3:c, sp:uptr(int, int, s), r4: ∀d, e, f. code{ r1:int, r2:d, r3:e, r4:f, sp:uptr(s) } 引数なので prod が解放 r1 sp 結果: int ? 7: int 4: int s として抽象化 r2 ? ? r3 ? ? : r4 コード

Compiling to TAL-1 再帰呼び出しの型付け 関数呼び出しの手順 r4、引数をスタックに積み、r4 に戻り先を代入して関数本体に jump 関数から戻ってきたら r4 を書き戻してスタックを 1 要素解放 しかしこの呼び出し規約は本質的ではない 例えば戻り先もスタックに積むという方法もとれる (Exercise 4.5.1)

Exercise 4.5.1 型はひとまず省略 cont: salloc 1; Mem[sp] := r1; fact: r1 := Mem[sp]; if r1 jump retn; r2 := r1 + (-1); salloc 2 Mem[sp + 1] := cont; Mem[sp] := r2; jump fact cont: salloc 1; Mem[sp] := r1; jump prod // tail call retn: r1 := 1; r4 := Mem[sp + 1]; sfree 2; jump r4

Exercise 4.5.1 fact の型 cont, retn の型 ∀a,b,c,d,s. code{ r1:a, r2:b, r3:c, r4:d, sp:uptr(int,∀e,f,g. code{ r1:int, r2:e, r3:f, r4:g, sp:uptr(s) }, s) } cont, retn の型 ∀b,c,d,s’. code{ r1:int, r2:b, r3:c, r4:d, sp:uptr(int, ∀e,f,g. code{ r1:int, r2:e, r3:f, r4:g, sp:uptr(s’) }, s’) } 同じ形になります

Scaling to Other Language Features

Objects and Closures Existential Type を導入 ∃α.τ … オペランドの抽象化 S-PACK, S-UNPACK ルール ∃ρ.τ … allocated type の抽象化 Operand の existential と同様のルール α はワードなのに対し、ρ は任意の長さの タプルを抽象化する、という違い

Objects and Closures Existential の導入 S-PACK ルール: 型の中の共通している部分を抽象化する ptr(code{r1:int, r2:int, ...}, int) ⇒ ∃α.ptr(code{r1:α, r2:int, ...}, α) S-UNPACK ルール: αが context に存在する とき ∃ を eliminate する しかし α は abstract として残される Context 中の他の型と distinct として扱う

Objects and Closures オブジェクトの実装 interface Point { int getX(); int getY(); } Class C1 implements Point { int x = 0, y = 0; int getX() { return x; } int getY() { return y; } } Class C2 implements Point { int x = 0, y = 0, n = 0; int getX() { n++; return x; } int getY() { n++; return y; } } ptr(int,int) → int ptr(int,int,int) → int

Objects and Closures オブジェクトの実装 getX の型は…? C1 から ptr(int,int) → int C2 から ptr(int,int,int) → int ⇒ 左辺を抽象化して∃α. α→int getY についても同様 したがって Point インターフェイスの型は ∃α.ptr(ptr(α→int, α→int), α) いわゆる vtable データフィールド

Objects and Closures Discussion ML 等の高級言語からコンパイルするときに 必要 もっと実用的に表現するには、再帰型を 導入すればよい[Bruce 1995; 2002] ∃ρ.μα.ptr(ptr(α→int, α→int), ρ) そのほかにもいくつもの表現手段がある

Objects and Closures Discussion 再利用可能な型構築子の集合を見つけることが重要 F-bounded existential など それによって、多くのオブジェクトモデルを表現できるようになる CLI, JVML はオブジェクトモデルを固定しており、対象とする以外の言語へのサポートは貧困 しかしダウンキャストは問題になる 動的に型検査を行う必要がある 表現型を応用できる[Crary et al. 1998]が、重い上に 実用的なオブジェクトの表現をサポートしない

配列 TAL-1 では固定長のデータしかとれない 配列をサポートするには? CISC 風のアプローチ Dependent Type を用いるアプローチ [Xi, Harper 2001] 実際に TALx86 に実装されている

配列 CISC 風アプローチ CISC 風のアプローチ ≡ プリミティブを増やす rd := newarray(ri, rs) rd := ldarr(rs, ri) / starr(rs, rd, ri) 範囲外アクセスをしないようにすることが重要 サイズを最初の要素に持たせたタプルを配列として扱う等 範囲外をアクセスしようとしたら例外ハンドラに飛ばす

配列 CISC 風アプローチ 利点 欠点 型システムの設計が単純 配列のアクセス範囲内チェックを動的にやる必要がある サイズが静的に定まる場合でも必ず実行時にサイズを管理しなければならない コード最適化との相性が悪い

配列 Dependent Type によるアプローチ Compile time expression の導入 整数値、算術式(加減算・乗算)、変数から成る 型がこれらの式を含むことを許す arr(τ, 30+12) ≡ arr(τ, 42) 42 要素からなるτ型の配列 int(36) 「36」という値を表す型 (singleton integer) ∃i. int(i * 2) 偶数 ∀i1,i2. code{r1:int(i1), r2:arr(T,i1), r3:int(i2)} r2 は T 型の配列、r1 はその要素数を格納 r3 はまた別の整数

配列 Dependent Type によるアプローチ 算術式から変数の間の大小関係を静的に求めれば配列の境界チェックを消せる 教科書のコードにはミスあり (3行目 RHS) コンテクストに変数の制約を追加して計算 if 文で分岐しない場合の条件を求める sub: … if r3 < 0 jump L; rt := r3 - r1; if rt >= 0 jump L; rd := ldarr(r2, r3) 範囲チェック済み なので ldarr での 検査は必要ない

配列 Dependent Type によるアプローチ どういった制約がかけるか? DTAL では型付けを decidable にするために制約は linear なものに限られている 配列くらいならばこれでなんとかなる 実際にはコード生成側が proof をつければ 任意の制約を扱える Proof-Carrying Code Framework を作れる 型システムの設計者側で、sound な inference rule のみ受け付けるようにする必要は残る

Exercise 4.6.1 意味論 例外ルール

Exercise 4.6.1 型システム τに arr(τ) を追加 型付け規則 境界チェックは例外生成

Some Real World Issues メモリ領域に対して読み込み・書き込みのアクセス制限をかける Subtyping の追加 領域の型に対してフラグをつけることで可能 Subtyping の追加 σの連結に関する subtyping rw-access <: ro-access, wo-access ...

Some Real World Issues スタックの型を拡張する free 命令の追加 Capability types 中を指すポインタを作れるようにする Nested procedure にも対応できる STAL [Morrisett et al. 2002] Intersection type の応用 TALT [Crary, 2003] free 命令の追加 ヒープオブジェクトの解放 Capability types Region のサポートを追加することができる [Walker et al. 2000]

Some Real World Issues これらの拡張は型システムの肥大化を招く 健全性の証明を手でやるのは非常に大変 定理証明系の応用 TALT [Crary 2003] Coq を利用した例 [Hamid et al. 2002] もうひとつのアプローチとして「Foundational ×××」の世界がある[Appel, Felty 2000] 高階論理を用いて型をつくる

4 章まとめ

まとめ 低級言語のための型システムは面白い 型情報を生成・処理するのが機械のため、 人的要因をある程度排除できる 高級言語の設計においてはこのあたりが心配 もちろん型システム自体の健全性は(定理証明系などによって)証明しておくべき メモリ操作のための型システムなどは高級 言語では必要ない