1

私はPascalScriptスクリプトエンジンのIssue14に取り組んでいます。この場合、Gotoコマンドを使用してCaseブロックからジャンプすると、完全に有効な(醜い場合)Object Pascalコードであっても、コンパイラエラーが発生します。

コンパイラのProcessCaseルーチンがHasInvalidJumpsを呼び出していることがわかります。これは、Caseブロックの外側につながるすべてのGotoをスキャンし、見つかった場合はコンパイラエラーを返します。そのチェックアウトについてコメントすると、コンパイルは問題なく実行されますが、実行時にクラッシュすることになります。バイトコードを逆アセンブルすると、その理由がわかります。元のスクリプトコードで注釈を付けました。

[TYPES]
<SNIPPED>
[VARS]
Var [0]: 27 Class TFORM
Var [1]: 28 Class TAPPLICATION
Var [2]: 11 S32 //i: integer
[PROCS]
Proc [0] Export: !MAIN -1
{begin}
 [0] ASSIGN GlobalVar[2], [1]
{ i := 1;}
 [15] PUSHTYPE 11(S32) // 1
 [20] ASSIGN Base[1], GlobalVar[2]
{ case i of}
 [31] PUSHTYPE 25(U8) // 2
{   0:}
 [36] COMPARE into Base[2]: [0] = Base[1]
 [57] COND_NOT_GOTO currpos + 5 Base[2] [72]
{   end;}
 [67] GOTO currpos + 41 [113]
{   1:}
 [72] COMPARE into Base[2]: [1] = Base[1]
 [93] COND_NOT_GOTO currpos + 10 Base[2] [113]
{     goto L1;}
 [103] GOTO currpos + 8 [116]
{   end;}
 [108] GOTO currpos + 0 [113]
{ end; //<-- case}
 [113] POP // 1
 [114] POP // 0
{ Exit;}
 [115] RET
{L1:
 Writeln('Label L1');}
 [116] PUSHTYPE 17(WideString) // 1
 [121] ASSIGN Base[1], ['????????']
 [144] CALL 1
{end.}
 [149] POP // 0
 [150] RET
Proc [1]: External Decl: \00\00 WRITELN

「gotoL1;」103のステートメントは、113と114のクリーンアップポップをスキップし、スタックを無効な状態のままにします。

Delphiは計算スタックを使用しないため、これに問題はありません。ただし、PascalScriptはそれほど幸運ではありません。このパターンは、PascalScriptに変換した制御構造がほとんどなく、サポートできる必要がある、はるかに単純なシステムの一部のレガシースクリプトで非常に一般的であるため、これを機能させる方法が必要です。

スタックを適切にクリーンアップするためにcodegenにパッチを適用する方法を知っている人はいますか?

4

3 に答える 3

3

IIRC のクラシック パスカルの goto ルールは次のとおりです。

  • ジャンプはブロックからのみ許可されます (ツリーの「同じ」ブランチで上位のネスト レベルから下位のネスト レベルへ)
  • 地元の手続きから両親まで。

後者は、Borland から派生した Pascals によってサポートされていないことがわかりましたが、最初のものはまだ保持されています。

したがって、Martin が言うように既存のコードを生成する必要がありますが、複数のブロック レベルの可能性があるため、goto ごとにコードを生成することはできませんが、コードを生成する必要があります (必要なブロックの正確な数を終了するため)。

典型的なテスト パターンは、goto を使用して複数のネストされた if を (おそらくループ内で) 終了することです。これは、少なくとも D7 までは高速な古典的なマイクロ最適化であったためです。

if 評価とそのブランチの begin..end ブロックが、クリーンアップが必要な一時ファイルを生成した可能性があることに注意してください。

---------- 後で追加

コードジェネレーターには、goto とそのエンドポイントの間でスコープを移動し、途中でブロックに関連する終了コードを生成する方法が必要だと思います。そうすれば、この例だけでなく、一般的なケースで修正が機能します。スコープから飛び出すことしかできず、スコープに入ることはできないので、それほど難しくないかもしれません。

IOW は (仮想的な double case ブロックの場合) と同等のものを生成します

Lgoto1gluecode: // コードの最初のブロックを終了 pop x pop y // コードの最初のブロックを終了 pop A pop B goto real_goto_destination

追加の分析を行うことができます。たとえば、スコープが 1 つしかなく、クリーンアップ終了ラベルが既にある場合は、直接ジャンプできます。上記の pop が単に破棄された値 (レジスタの保存ではない) であることが確実にわかっている場合は、$16,%esp (4*4 バイト値) などを追加して一度に行うことができます。

于 2009-06-22T16:39:51.990 に答える
1

どこまでジャンプするかの計算が問題のように思えます。さらに支援するためにパーサーの実装を調べるのに少し時間を費やす必要がありますが、goto を使用し、スタックに値があり、goto がそれらの値の後に配置される場合は、追加の処理を実行する必要があると思います。スタックから削除されます。もちろん、これを判断するには、解析中の現在の場所 (goto) を保存し、スタックの変更を監視するターゲットの場所への前方解析を保存する必要があります。そうであれば、goto の場所を後方に調整するか、Martin としてコードを挿入します。提案した。

于 2009-06-22T17:04:27.917 に答える
1

簡単な解決策は次のとおりです。

goto ステートメントの GOTO を生成するときは、RET の前に来る同じクリーンアップ コードを GOTO の前に付けます。

于 2009-06-21T21:55:35.413 に答える