New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change with-exception-handler for R7RS #335

Closed
wants to merge 7 commits into
base: master
from

Conversation

Projects
None yet
2 participants
@Hamayama
Contributor

Hamayama commented Jan 14, 2018

with-exception-handler の動作をR7RSに近づくように変更してみました。

これで、r7rs-tests のスキップされていたテストは、(exact-complex以外)すべて通るようになりました。

ただ、Gauche の元の動作と互換性がなくなるため、既存のプログラムが動かなくなる可能性があります。
(テストも 3 件 NG になり、変更しています)

元の動作を残して、r7rs:with-exception-handler を新設しようとすると、
vm->exceptionHandler に種別のような情報が必要になる感じでしょうか。
(ややこしくなりそうですが。。。元の動作は必要でしょうか?)

あと、マニュアルも変更しようとしてみましたが。。。

<変更内容>

  1. handlerの実行時には外側の例外ハンドラを有効にする

  2. 「例外が <serious-condition> でない場合に、
    vm->escapePoint を使用する例外処理 (with-error-handler, guard, unwind-protect)
    よりも優先される」
    という条件を無効化 (Scm_VMThrowException2)

<テスト結果>
OS : Windows 8.1 (64bit)
Gauche : コミット a708d70 + 変更
開発環境 : MSYS2/MinGW-w64 (64bit) (gcc version 7.2.0 (Rev1, Built by MSYS2 project))
Total: 19289 tests, 19289 passed, 0 failed, 0 aborted.

@shirok

This comment has been minimized.

Owner

shirok commented Jan 15, 2018

元の動作はsrfi-18互換で、概念的にはそちらの方がプリミティブです。srfi-18:with-exception-handlerを直接利用しているコードはほとんど無いとは思いますが、call/ccのようにプリミティブな制御構造を実験するために使う機構なので、残しておきたいと考えています。なのでやるとしたらr7rs:with-exception-handlerを別に実装することになりますね。
原理的にはsrfi-18:with-exception-handlerの上にr7rs:with-exception-handlerを実装するのが綺麗だと思いますが、動的環境の保存が必要になるのでオーバヘッドが気になるんですよね。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 15, 2018

r7rs:raise を参考にして、
r7rs:with-exception-handler を追加してみました。

元の with-exception-handler の定義は、元に戻しました。

ただ、上記 2. の Scm_VMThrowException2 の処理はコメントアウトしたままとなっています。

この処理があると、例外が <serious-condition> でない場合に、
内側の with-error-handler, guard, unwind-protect よりも、
外側の with-exception-handler が優先されてしまい、r7rs-tests が NG になります。

あとは、この処理をコメントアウトしてしまって問題がないかどうかですが。。。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 15, 2018

なんとなく分かってきました。

(with-exception-handler
  (lambda (e) (display 2))
  (lambda ()
    (guard (exc)
      (raise 1) (display 3))))

のような場合に、現状の guard (with-error-handler) には呼び出し元に戻る機能がないため、
(display 3) が実行されないですね。

これを避けるために、上記 2. の Scm_VMThrowException2 の処理が必要であると。。。

これは、単純にコメントアウトしたのは、まずそうですね。。。

@shirok

This comment has been minimized.

Owner

shirok commented Jan 16, 2018

歴史的に、with-error-handlerが最初にあって、それを効率よく処理するためにEscapePointメカニズムが出来たんで、効率の点からなるべくそれを利用したいのでguardも独自実装にしてあります。
しかし今後のことを考えるとwith-exception-handlerをベースにしてguardもその上に実装する方が良さそうではあるんですよね。srfi-18:wtih-exception-handlerベースにちょっと見直してみます。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 16, 2018

本件ですが、まずは既存の路線の延長でいけないか検討してみました。

まず、問題になっているのが guard の reraise のみであるため、
これを %guard-reraise という命令に置き換えて、特別扱いするようにしました。
(common-macros.scm, libexc.scm)

そして、reraise 時に ep->guardReraised というフラグを立てて、
Scm_VMDefaultExceptionHandler から return するようにしました。
このとき、ep の復帰と dynamic-winds の再入処理を行っています。
(vm.c, vm.h gauche.h)

これで guard の body 内に戻れるようになったため、
Scm_VMThrowException2 内の優先処理を削除しました。

<テスト結果>
OS : Windows 8.1 (64bit)
Gauche : コミット a708d70 + 変更
開発環境 : MSYS2/MinGW-w64 (64bit) (gcc version 7.2.0 (Rev1, Built by MSYS2 project))
Total: 19292 tests, 19292 passed, 0 failed, 0 aborted.

<気になる点>

  • ep->guardReraised の追加による複雑さの増加。
    フラグ管理の抜け等がないか。

  • ep の復帰と dynamic-winds の再入処理が、あまり理解できていない。
    他の処理から類推して記述しましたが。。。

  • Scm_VMDefaultExceptionHandler はこれまで「戻ることのない関数」であったが、
    今回の変更で「戻るかもしれない関数」になった。
    戻り値の型も void から ScmObj に変更している。
    これによって互換性の問題等が出ないか。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 16, 2018

1個の guard で2回 reraise するケースで、
dynamic-wind が正常に処理できていませんでした。
テストも修正しました。

@shirok

This comment has been minimized.

Owner

shirok commented Jan 17, 2018

どうしようかな…guardが今の形なのは、根本的にはcall/ccが重いせいなんですよね。なので軽いwith-error-handlerを使ったんだけどセマンティクスを合わせるのに苦労しているという。
なので軽いcall/ccを作った上で、素直にguardをcall/cc, dynamic-wind, with-exception-handlerの組み合わせで実現するのが実装の見通しの点からもベストだと思います。
これをきっかけにちょっと考えてみます。思ったより大変なら経過処置としてこのfixをいただくかもしれません。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 17, 2018

不要な行を削除しました。

どうしようかな…guardが今の形なのは、根本的にはcall/ccが重いせいなんですよね。なので軽いwith-error-handlerを使ったんだけどセマンティクスを合わせるのに苦労しているという。
なので軽いcall/ccを作った上で、素直にguardをcall/cc, dynamic-wind, with-exception-handlerの組み合わせで実現するのが実装の見通しの点からもベストだと思います。
これをきっかけにちょっと考えてみます。思ったより大変なら経過処置としてこのfixをいただくかもしれません。

個人的には、現状ベースでよいように思いますが。。。

(どうも SRFI-34 の guard が、本当によいものなのか、判断がつかない感じです。
(厳密には reraise の前に body の dynamic-wind を脱出、再入して、
例外ハンドラだけをひとつ前のものに差し替えて、raise しないといけないようですが、
これってどういう利点があるのでしょうか。。。))

@shirok

This comment has been minimized.

Owner

shirok commented Jan 17, 2018

guardを、処理しないコンディションに対して透過的にするためだと思います。

自作のライブラリルーチンfooがあったとします。

(define (foo)
   ...)

ある時、fooの中で特殊な状況をハンドリングする必要が生じて、その状況だけを捕まえるguardを追加しました。このguardは'special-conditionが投げられた時以外はfooの動作に変更を与えないことが期待されます。

(define (foo)
  (guard (e ((eq? e 'special-condition) (handle-special-condition)))
     ...
     (raise 'special-condition)))

ところで、fooは第三者のコードから次のように呼ばれていました。

(with-exception-handler (lambda (e) (invoke-debugger e)) foo)

invoke-debuggerは呼び出された時点での動的環境にもアクセスする必要があります。

このコードがguardの追加前後で動作を変えないためには、guardで捕まえられなかった例外をreraiseする際に、動的環境を一旦大元のraise時点に巻き戻す必要がある、というわけです。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 18, 2018

なるほど。。。
(しかし、透過を考慮するのであれば、guard のハンドラの最初で無条件に body の dynamic-wind を脱出しているのが無駄なような。。。
しかし、guard のメインの目的が例外を捕まえて脱出することであると考えると、これでよいのか。。。
例えば、guard では、例外を捕まえて何か処理を行い、そこからさらに raise して透過的に扱うことはできない。。。
ただ、そのような用途では、with-exception-handler を使えばよいのか。。。)

ぐるぐると考えましたが、大体納得できました。

現状の Gauche では、この透過的な reraise を実装していませんが、
本パッチで Scm_VMGuardReraise に書いている Scm_VMThrowException2 を、
Scm_VMDefaultExceptionHandler の reenter dynamic-winds の後に移動すれば、
対応可能かもしれません。
ただ、ここをあまり複雑にするよりは、TODO にしておいた方がよい気もしますが。。。
(例外ハンドラだけをひとつ前のものに差し替えるというのが、バグりそう)

@shirok

This comment has been minimized.

Owner

shirok commented Jan 18, 2018

私の解釈では、with-exception-handlerはプリミティブなビルディングブロック、guardは日常で使うユーティリティ、という位置づけなんだと思います。
guardのハンドラで動的環境が巻戻らないと、例えば次のようなコードでエラーログをerror portに吐いてるつもりが、body中でcurrent-error-portがparameterizeされてると別のところに流れちゃいます。いちいちハンドラで使う動的環境を保存するというのは日常ユーティリティとしては不便すぎるということでしょう。

(guard (e (else (write "Exception!" (current-error-port))))
  body)

ただ、現実に書かれるコードだと、guardで処理せずに元の動的環境に戻って何とかするっていうケース自体が滅多にないので、call/ccで行ったり来たりする苦労の大部分は無駄になるんですよね… ChickenとかChezみたいに継続のキャプチャがむちゃくちゃ軽ければいいんですが。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 18, 2018

ありがとうございます。すごく納得できました。

vm.c を変更して、guard の reraise が透過的になるようにしてみました。

ただ、guard の段数が増えると、
脱出1 → 再入1 → reraise → 脱出1 → 脱出2 → 再入2 → 再入1 → reraise → ...
という感じで、毎回大元の raise の位置まで戻ってから投げなおすので、
パフォーマンスの問題が出そうではあります。。。

reraise の透過をやめると、
脱出1 → reraise → 脱出2 → reraise → ...
となるので、効率はよさそうです。仕様とは違いますが。。。

あと、Scm_VMDefaultExceptionHandler に追加した reraise の処理なのですが、
この部分は、SCM_UNWIND_PROTECT の中に入れた方がよいでしょうか。
それとも、return するので、現状のように外側にあった方がよいでしょうか。
(正直、いろいろとよく分かっていない部分があります。。。)

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 19, 2018

unwind-protect についても <serious-condition> でない場合に reraise を使うようにしました。

これによって、本プルリクエストの前のように、
with-exception-handler と unwind-protect の組み合わせで、
unwind-protect の expr 内に戻れるようになりました。

また、この変更にともない、%guard-reraise を %reraise に名称変更しました。

また、テストも一部追加/変更しました。

<テスト結果>
OS : Windows 8.1 (64bit)
Gauche : コミット 76c3ea7 + 変更
開発環境 : MSYS2/MinGW-w64 (64bit) (gcc version 7.2.0 (Rev1, Built by MSYS2 project))
Total: 19293 tests, 19293 passed, 0 failed, 0 aborted.

@shirok

This comment has been minimized.

Owner

shirok commented Jan 27, 2018

ありがとうございます。ひとまずこの変更をsquashしていただきます。
長期的な変更はその後で。

shirok added a commit that referenced this pull request Jan 27, 2018

R7RS-compatible with-exception-handler and guard
Based on a patch by @Hamayama
#335

Allow to return from Scm_VMDefaultExceptionHandler by invoking
Scm_VMReraise on a continuable exception.

Rewrite guard to call reraise when exception isn't handled.  This
is closer to R7RS behavior.

r7rs#with-exception-handler is defined separately from built-in one,
to maintain compatible semantics.
@shirok

This comment has been minimized.

Owner

shirok commented Jan 27, 2018

234d0ef で取り込みました。
今回慎重になったのは、%reraiseがguardやunwind-protectの展開結果に含まれることで、これは今のバージョンでプリコンパイルしたコードには%reraiseへの参照が埋め込まれてしまうことを意味します。
0.9.6リリース後に何かこのへんを大きく変えたくなった際にも、コアは0.9.6でコンパイルされるため%reraiseの呼び出しを含み、従ってその動作の互換性は保つ必要があります。まあ多分大丈夫だろうとは思うんですが、念のため%reraiseをgaucheモジュールからは見えないようにしました。

@Hamayama

This comment has been minimized.

Contributor

Hamayama commented Jan 28, 2018

こちらの環境でも動作を確認できました。
クローズします。

@Hamayama Hamayama closed this Jan 28, 2018

netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Jul 24, 2018

Update to 0.9.6
* Use gmake for pattern rules

Changelog:
Release 0.9.6

Major feature upgrade

  * Notable feature enhancement:
  * New modules and procedures
      + R7RS-Large Red Edition support (WiLiKi:Gauche:R7RS-RedEdition)
      + New srfi support:
      + Other new modules and procedures:
  * Incompatible changes
  * Other bug fixes

Notable feature enhancement:

  * Static linking and standalone executable support: Now you can create a
    standalone executable from Gauche program. See blog entry and "Building
    standalone executables" section.
  * Single shell-script installer (blog entry).
  * REPL enhancement: Pretty printing (blog entry), online document display (
    blog entry) and search (blog entry).
  * Method dispatch optimizations (1, 2).
  * Procedure inlining optimizations (1, 2)
  * Windows console Japanese handling: Thanks to @Hamayama, numerous fixes to
    use Japanese on Windows command prompt is incorporated.
  * Bump to Boehm gc 7.6.6, thanks to @qykth-git.
  * Support mbedTLS as an additional TLS support, thanks to @qykth-git. See
    rfc.tls for the details.
  * format finally supports floating number formatting ~f. It also supports a
    subtle rounding mode switch regarding binary to decimal conversion (blog
    post).
  * Support of using multiple versions of Gauche in parallel---from 0.9.6 and
    after, you can invoke a different version of Gauche by gosh -vVERSION, as
    far as VERSION of Gauche is also installed. This isn't much useful now
    (VERSION must be 0.9.6 or later), but will be handy with future releases.
  * Sampling profiler now works on Windows, thanks to Saito Atsushi and
    @Hamayama (although it can only sample the attached thread).

New modules and procedures

R7RS-Large Red Edition support (WiLiKi:Gauche:R7RS-RedEdition)

12 libraries (out of 17) are supported:

  * scheme.list List library (formerly srfi-1)
  * scheme.vector Vector library (formerly srfi-133)
  * scheme.sort Sort libraries (formerly srfi-132)
  * scheme.set Sets and bags (formerly srfi-113)
  * scheme.charset Character-set library (formerly srfi-14)
  * scheme.hash-table Intermediate hash tables (formerly srfi-125)
  * scheme.ideque Immutable deques (formerly srfi-134
  * scheme.generator Generators (formerly srfi-121)
  * scheme.lseq Lazy sequences (formerly srfi-127)
  * scheme.box Boxes (formerly srfi-111)
  * scheme.list-queue Mutable queues (formerly srfi-117)
  * scheme.comparator Comparators (formerly srfi-128)

Those are still accessible as srfi-* names, but new code is recommended to use
the scheme.* names.

New srfi support:

  * srfi-64 A Scheme API for test suites
  * srfi-66 Octet vectors
  * srfi-74 Octet-addressed binary blocks
  * srfi-96 SLIB prerequisites
  * srfi-129 Titlecase procedures
  * srfi-141 Integer division
  * srfi-143 Fixnums
  * srfi-145 Assumptions (built-in)
  * srfi-146 Mappings
      + srfi-146.hash Hashmaps
  * srfi-149 Basic Syntax-rules template extensions (built-in)
  * srfi-151 Bitwise operations
  * srfi-152 String library (reduced)
  * srfi-158 Generators and accumulators

Other new modules and procedures:

  * pprint - pretty printer.
  * assume-type macro and type-error procedure.
  * define-inline is now official.
  * hash-table-compare-as-sets, tree-map-compare-as-sets - compare those
    mappings as sets
  * let-values, let*-values: now built-in.
  * In gauche.process: do-process!, do-pipeline, run-pipeline!.
  * In gauche.unicode: char-east-asian-width
  * In gauche.uvector: uvector-binary-search, u8vector=? ..., u8vector-compare
    ....
  * In gauche.charconv: Conversion routines accepts u8vector as well as
    strings.
  * In gauche.sequence: delete-neighbor-dups, delete-neighbor-dups!,
    delete-neighbor-dups-squeeze!, group-contiguous-sequence
  * In gauche.threads: atomic and atomic-update! allows more than one timeout
    values.
  * text.template: Simple template expander, based on built-in string
    interpolation feature.
  * Char-set can be immutable. char-set-freeze and char-set-freeze! are used to
    make a char set immutable. Literal char-sets are immutable, as other
    literal objects.
  * rfc.http: You can now use stunnel process to do https connection instead of
    Gauche's rfc.tls module. Note that it only works with command mode of
    stunnel---which isn't available on Windows.
  * rfc.tls: Now that we support mbedTLS and server certificate authentication,
    a minimal document is added.
  * binary.io: get-uint, get-sint, put-uint!, put-sint!.
  * gauche.generator: generator->uvector, generator->uvector!, generator->
    bytevector, generator->bytevector!.
  * data.random: regular-string$ - creates a generator that generates random
    strings that match the given regexp.
  * string-incomplete->complete: Add :escape mode to escape illegal bytes in
    lossless way.

Incompatible changes

Some change undocumented behaviors; others change because of bug fix.

  * Literal character sets (#[chars]) are now immutable, as other literal
    objects; it will raise an error if you try to mutate it.
  * getter-with-setter now associates the setter to the getter in immutable way
    ('locked'); it will raise an error if you try to change it. It is the way
    specified in srfi-17. It also allows Gauche to inline setters. (NB: Many
    predefined setters are now locked. If your existing code alters them it
    will cause an error.)
  * list*, cons* - Requires at least one arg, as specified in srfi-1. Zero
    argument doesn't make sense, although previous versions of Gauche allowed
    it.
  * append, append! - Now it is an error if the arguments except the last one
    is a dotted list. We've tolerated it before, but it's rather error prone.
  * util.match: The way to match record instance with positional variables are
    changed for more reasonable way. We hope no code depends on the previous
    way, which was broken anyway. See the blog entry for the details.
  * twos-complement-factor: We fix the behavior when 0 is passed; it used to
    return 0, now it returns -1. The latter is consistent with srfi-60.
    Unfortunately we documented the former behavior, so it breaks
    compatibility.
  * string-split: Splitting an empty string now yields an empty list instead of
    (""), as srfi-152 specifies

Other bug fixes

There are too many; we list up some notable ones.

  * The behavior of guard when no clauses are satisfied and the exception is
    reraised is now R7RS-compatible ( shirok/Gauche#335
    ). When using R7RS, with-exception-handler is R7RS compatible (which is
    slightly different from built-in with-exception-handler, compatible to
    srfi-18).
  * unwind-protect: Fix bug with interaction of call/cc.
  * rfc.tls: axTLS interface had MT-hazard.
  * er-macro-transformer: Fix hygienity issue ( https://github.com/shirok/
    Gauche/issues/250 )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment