プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin:...

55
プログラミング言語処理系論 (9) Design and Implementation of Programming Language Processors 佐藤周行 (情報基盤センター/電気系専攻融合情報学 コース)

Upload: others

Post on 13-Jul-2020

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

プログラミング言語処理系論 (9) Design and Implementation of Programming Language Processors

佐藤周行

(情報基盤センター/電気系専攻融合情報学コース)

Page 2: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

今日の予定

Register machineの実際

Parrot

Stack machine vs Register machine

VMの設計

vmgen

Page 3: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

parrot.org/

Page 4: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Register Machine

Register Machine

各種演算のオペランドにレジスタ(変数)を想定する

ハードウェア命令との親和性が高いと主張する

ハードウェアとの親和性を考えるとき、命令体系のレジスタセットとハードウェアのレジスタセットが異なる場合、「Register Allocation」が必要になる

レジスタの選択の自由度が上がる分、コードは一般に複雑

Page 5: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ISA of Parrot

Parrot

I (Integer)

N (Real)

S (String)

P (Polymorphic)

それぞれの型に応じてレジスタが用意されている(実質無限個)

Ixx, Nxx, Sxx, Pxx

Page 6: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ターゲット言語の重要な要素

スクリプト言語としてのPerlの特徴

動的型付け

実行時に文字列(S) か数(I, N)かが決まる

文字列の操作

パターン照合を含む

Arrayの操作

Page 7: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Parrot

Perl6用のVM

多くの動的型付け言語対応を唱う Rakudo Perl6 Lua Winxed

では、Parrotのうたい文句を見てみましょう

Parrot is a language-neutral virtual machine for dynamic

languages such as Ruby, Python, PHP, and Perl. It hosts a powerful suite of compiler tools tailored to dynamic languages and a next generation regular expression engine.

Page 8: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

少しambitiousなことが書いてある

Its architecture is fundamentally different than existing virtual machines such as the JVM or CLR, with optimizations for dynamic languages included, a register-based system rather than stack-based, and the use of continuations as the core means of flow control.

Page 9: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

構成

Parrot

VM

言語として

PIR 中間言語系

PASM アセンブリ言語

Page 10: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Parrotの構造

Parrot

Perl他ターゲット言語

PIR

PASM

PBC

PIR: Parrot Intermediate Language PASM: Parrot Assembly Language PBC: Parrot ByteCode

Page 11: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

では、Parrotの解析

データ型は

Integer

Number

String

PMC (PolyMorphic Container)

Page 12: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Register Machine

Registerとして

Ixx

Nxx

Sxx

Pxx

変数はスタックに積むことなく、「レジスタ名」でアクセスされる

Page 13: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

簡単な例(ただし分厚いライブラリ付)

.loadlib 'io_ops'

.pcc_sub :main main: getstdin P0 getstdout P1 REDO: readline S0, P0 print S0 if S0, REDO end

Page 14: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Parrotに求められるものは何か?

「オブジェクト」に対する動的な型付けのサポート。そのためにPMCを用意した

PMC

Scalar/array/subroutine/namespace を収容できる

目的に応じて柔軟に型変換が可能になっている

Page 15: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

PMCのタイプ

Env

Iterator

Array

Hash

String

Integer

Float

Exception

Timer

Page 16: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

たとえば、これはどうか(動的関係ない

.sub 'example' :main $I1 = 96 $I2 = 64 print "Algorithm E (Euclid's algorithm)¥n" e1: $I4 = mod $I1, $I2 e2: unless $I4 goto done e3: $I1 = $I2 $I2 = $I4 branch e1 done: print "The greatest common denominator of 96 and 64 is " print $I2 print ".¥n" .end

Page 17: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ではこちらはどうか

$i = 4.3; print fact(); sub fact { $r = 1; while ($i > 0) { $r = $r * $i; $i--; } return $r; }

Page 18: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

解決方法

特定の演算を指定して、polymorphicにする

この場合はdecrement, multiply

PMCを引数に取る時の挙動を定める

→ VTABLEで処理内容を指定する

型指定のタイミングを変数の代入の時点で制御できるようにする

代入のsemanticsを定める

Page 19: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

実際 src/ops/math.opsでは

inline op dec(inout INT) { $1--; } inline op dec(inout NUM) { $1--; } inline op dec(invar PMC) { VTABLE_decrement(interp, $1); }

Page 20: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ISA

まずは命令セットを眺めてみましょうか

include/parrot/oplib/ops.h

1129個の命令

高レベルのものを含む(特に制御関係)

Page 21: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ISA 特に代入(set)を見てみましょう

inline op set(out INT, in INT) { $1 = $2; } inline op set(out INT, in NUM) { $1 = (INTVAL)($2); } inline op set(out INT, in STR) { $1 = Parrot_str_to_int(interp, $2); }

inline op set(out NUM, in NUM) { $1 = $2; } …

Page 22: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Non-trivialなsetは…

inline op set(invar PMC, in INT) { VTABLE_set_integer_native(interp, $1, $2); } inline op set(invar PMC, in NUM) { VTABLE_set_number_native(interp, $1, $2); } inline op set(invar PMC, invar STR) { VTABLE_set_string_native(interp, $1, $2); } inline op set(invar PMC, inconst STR) { VTABLE_set_string_native(interp, $1, $2); } inline op set(out INT, invar PMC) { $1 = VTABLE_get_integer(interp, $2); }

Page 23: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Set

全体として以下のパターンに対してそれぞれ対応する関数が定められている

set (単純なパターン)

set P {I/N/S}

set Px [key] By/ set Ax Py[key]

白鳥は優雅に泳いでいるが…というパターン

Page 24: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

関数呼び出しについて(PIR)

まずはフレーム # factorial.pir .sub 'main' :main .local int count .local int product count = 5 product = 1 $I0 = 'fact'(count,

product) say $I0 .end

.sub 'fact' .param int c .param int p

loop: if c <= 1 goto fin p = c * p dec c branch loop fin: .return (p) .end

Page 25: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

PASMでの表現

.pcc_sub :main main:

new P10, 'ResizableIntegerArray'

set I1,0

## P9 is used as a stack for temporaries.

new P9, 'ResizableIntegerArray'

loop:

print "fact of "

print I1

print " is: "

new P0, 'Integer'

set P0,I1

local_branch P10, fact

print P0

print "¥n"

inc I1

eq I1,31,done

branch loop

done:

end

### P0 is the number to compute,and also the return value.

fact:

lt P0,2,is_one

## save I2, because we're gonna trash it.

push P9,I2

set I2,P0

dec P0

local_branch P10, fact

mul P0,P0,I2

pop I2,P9

local_return P10

is_one:

set P0,1

local_return P10

Page 26: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

まともなやつ

.pcc_sub :main main: # Get @ARGV as a

ResizableStringArray get_params "0", P0 # Discard the program

name shift S0, P0 # Look for additional args if P0, FOUND_EXTRA_ARG print "Hello World¥n" end

FOUND_EXTRA_ARG:

shift S1, P0

print "Hello "

print S1

print "¥n"

end

Page 27: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

関数コール

Signature で型付け

IN -> OUT

関数コールごとにcontextを作成(特別のスタックがあるわけではない)

Continuationを受け渡すことができる

Coroutine等が実現可能…

Page 28: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

OpCodeを見てみる

クラス関係

Callmethod, tailcallmethod, addmethod, can, does, isa (VTABLE), newclass, subclass, get_class, class, addparent, removeparent, addattribute, removeattribute, getattribute, setattribute, inspect

Page 29: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

文字列命令

ord, chr, chopn, concat, repeat, length, bytelength, pin, new, substr, replace, index, rindex, sprintf, upcase, downcase, titlecase, stringinfo, join, split, encoding, find_encoding, trans_encoding, finc_cclass, escape, compose, find_compose

Page 30: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

PMC

new, root_new, typeof, find_method, defined, exists, delete, elements, push, pop, shift, splice, setprop, getprop, freeze, thaw, box, iter,

Page 31: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

プロセス管理

schedule, add_handler, wait, pass, invokecc, returncc, disable/enable-preemption, terminate,

関数コール

get/set_params, get/set_result

Page 32: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

名前解決

後は、グローバルな変数(名前でしかアクセスできないデータ)の扱いですが

以下の命令が用意されている

{store/find}_lex 名前付き変数の解決

get_namespace をやる前提で

get_global

set_global

find_name

Page 33: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

(問題8’への追加) CPythonのVMについてJVMと同じことを解析せよ

(問題8’’) ParrotについてJVMと同じことを解析せよ

Page 34: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

stack machine vs. register machine

微妙なpros/consがある

少し古い論文だが

Y. Shi, K. Casey, M. A. Ertl, D. Gregg:

Virtual Machine Showdown: Stack Versus Registers, ACM Transactions on Architecture and Code Optimization, r(4), 2008.

(このグループはvmgenの開発者です)

Page 35: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

stack machineとregister machineはどちらが高速に動くか? JITコンパイラとは別の話

register machineを新たに定義し、JVMとの性能比較実験をしてみた vmgenを利用して気軽にVMが実装できる

生成されたコードは、load/storeに関して最適化を行った load/storeの挙動が両アーキテクチャの最大の違いであることはすぐわかる

Page 36: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Load/Store

stack machine

(基本的に)演算の度にstackへのデータ移動が発生

対frame, 対constant pool, 対object pool

register machine

registerにデータを移動したら、registerを使って演算ができる

copy propagation, redundant load eliminationは性能を出すうえで必須の最適化

Page 37: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

性能比較

生成されたコードの比較 Naïveなstack register変換

Move 31%, constant load 28% (これがstack machineの特徴と考えてよい)

Load/storeの最適化後 命令数全体で44%削減 (ただし1命令あたりのサイズは大きくなっている コード量では26%削減)

Moveは大部分削除、constant loadは35%削減

実行されたコードの比較 Move 42% ほとんど削除 Constant loadは全体の6.4% 2.9% (実行頻度は少ない)

実行命令数全体で46%削減(NOT 実行時間)

Page 38: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Load/Store

(VMではなく)実行プラットフォームCPUのload/store数の比較

VMでレジスタを実装しても、裏でメモリload/storeをする

register machineは、stack machineと比較してloadで35%削減、storeで48%削減

全体性能として、(dispatchに気を配れば)2倍程度性能向上(AMD64)

Page 39: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

オチとして

命令dispatchの方法の改善が重要

vmgenはいろいろ実験ができる。

switch文を使ったdispatch

ノーマルな方法

direct-threaded dispatch

Next PCの指す命令に対応するラベルに直接goto

言語のサポート必要(GCCならできる)

Page 40: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Just in Time Compiler

昔、そういう技術がありまして… Javaの実行速度を上げるための工夫に使われた

実用的には、このアイデアはいろいろなところに残っている LTOなど

VM

CPU

Native code

VM code

実行直前にコンパイル

Page 41: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Note

JVMの命令列をregister machineの命令列に変換したと言っているが…

Stack machineの命令列にする時点で情報がだいぶ落ちている

最適化の支障になる可能性がある(論文中で言及されている)

最適化をするなら、中間表現の設計に気を配るのが筋(これは次回以降のトピック)

Page 42: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

VMの設計

VMを設計する

Engine部分なら、ごく簡単

load/link、データ領域の確保を忘れずに

スレッド等、並列サポートもあればなおよい

Page 43: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

ターゲット言語の重要な要素

オブジェクト指向言語

オブジェクト

Method invocation

フレームは普通にスタックに積むのだが、Java VMではそこまでは強制されていない

スクリプト言語

動的型付け

文字列の操作が…

Page 44: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

決めるべきこと

ISA 重要なfeatureをサポートすることを考える

今まで説明してきたVMの真似から始めるのがよかろう

Minimum setとして何が必要かを考えると、言語の設計とリンクしてきます

データ、コードReference方法の決定 大昔はAddressing Modeなどと言いましてね…

Frame Management Call 関係の命令の実装

Page 45: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

VMの実装

Stack/register machine ISA

Code/data/stack segment

Symbol table

これらが決まったら、後は各命令の解釈を書くだけ

vmgenなど、便利そうに見えるツールがある

Page 46: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

問題10

Stack machineのVMを一つ設計せよ

命令セットはDCC.zip中のopcode.hを使うこと

stack segmentとdata segmentを定義すること

各命令を実行したときのstack/data segmentの状態変化を記述すること

OP_CALL とOP_RETURNについては詳述すること

この記述に従ってvmgenでVMを作成するとなおよい

コンパイラを作る必要はない

Page 47: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

VMGENの強調すること

VMGENは、Gforthの実装言語として世に出た(らしい)

http://savannah.gnu.org/projects/vmgen ここからダウンロードはできない。Project

HomepageにいってGforthをダウンロードするとついてくる

VMのエンジン部分を生成してくれる 命令dispatchに凝っている Switch (naïve, simple, but slow) Direct threaded dispatchのサポート

Page 48: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Switch

vmexec(int pc)

{

for (;;) {

switch (codeseg[pc].op) {

case OP_NOP:

NEXTPC;

case OP_POP:

stacktop -= codeseg[pc].operand; NEXTPC;

case OP_U_NOT:

stackseg[stacktop] = !stackseg[stacktop]; NEXTPC;

case OP_B_ADD:

binop(+); NEXTPC;

case OP_B_SUB:

binop(-); NEXTPC;

Page 49: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

Direct Threaded Dispatch

vmexec(int pc)

{

for (;;) {

switch (codeseg[pc].op) {

case OP_NOP:

goto codeseg[NEXTPC];

case OP_POP:

stacktop -= codeseg[pc].operand;

goto codeseg[NEXTPC];

case OP_U_NOT:

stackseg[stacktop] = !stackseg[stacktop];

goto codeseg[NEXTPC]; case OP_B_ADD:

binop(+);

goto codeseg[NEXTPC];

case OP_B_SUB:

binop(-);

goto codeseg[NEXTPC];

Page 50: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

さてと、

レジスタ変数(またはスタック上の変数)の間の演算列という形でプログラムが書かれるようになれば「最適化」が可能になります

今までのtree traversalではできる最適化にも限度がありましたが…

「最適化」は性能をあげるためには必須です

register machine architectureでは、load/storeの最適化がstack machine architectureより劇的に効く– ことを説明しました

Page 51: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

動的言語では

動的な型変換が性能にどれだけのマイナス要因になっているかを理解する

静的にでも動的にでも型変換のルーチンをスキップできれば、性能が上がる

JVM上でスクリプト言語を実行する場合

引数の動的型チェックを行う

引数の型チェックをできるものは静的にすませてしまう

Page 52: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

動的型変換においては

ルーチン内で、型が伝播する可能性がある

ローカルな解析をもとにして

データフロー解析をもとにして

ルーチンをまたいで、型が分かる場合がある

手続き関解析をもとにして

Page 53: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

最適化の分類

1. ローカル最適化 1. 基本ブロック内外で、一定のパターンに当てはまるものを変換(ピープホール最適化)

2. 基本ブロック内で、変数の挙動を解析して変換

2. データフロー解析をもとにした大域最適化

3. データ依存解析をもとにしたループ最適化

4. 手続き間解析をもとにした手続き間最適化 1. LTO

2. WPRO

Page 54: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

LTO (Link Time Optimization)

関数をまたいだ最適化を効率よく行うためにリンク時に最終的に最適化を行うことが普通になりつつある

コンパイラシステムがサポートすることも普通になってきている

Page 55: プログラミング言語処理系論 (9) - 東京大学...p = c * p dec c branch loop fin: .return (p) .end PASMでの表現 .pcc_sub :main main: compute,and also the return new

代表的な手続き間解析

部分評価

手続きに渡される引数の性質を利用する

int g(x) { if (x==0) return 1; else return h(x-1); } main() { print g(2)+g(0); }

int g(x) { if (x==0) return 1; else return h(x-1); } main() { print h(1)+1; }