🦜

Delphi(10.4)でスマートポインタ

2021/07/05に公開

スマートポインタとカスタムマネージドレコード

Delphi10.4で実装されたカスタムマネージドレコードでスマートポインタ実装をやってみるという記事です。
公式でもカスタムマネージドレコードの利用例としてスマートポインタが例示されていた記憶があるんですが、なぜかスマートポインタ自体は実装されていないので早く実装してほしいところ。

※Delphi XE2のモバイル向けコンパイラにARCが実装されてからずっとWindows向けに来るのを待ってるのでほぼ10年・・・

動機

自アプリにwasmランタイム(wasmtime)を組み込みたくてwasm.h(C向けAPI)をDelphiに移植する作業をしているんだけどこのAPI、戻り値の扱いに二通りあって、

own wasm_functype_t* wasm_functype_new(
  own wasm_valtype_vec_t* params, own wasm_valtype_vec_t* results);

const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t*);

上のようにownがついている戻り値は所有権がユーザーコードにあるので適切にdeleteを呼んで破棄する必要があるけど、下のようにown無しの戻り値は所有権がないので破棄してはいけない。
このように扱いが複数あるものを適切に管理できる能力は我々人類には備わってない(やりたくない)のでown付戻り値はスマートポインタで管理したい。

※C++向けAPIであるwasm.hhだとownstd::unique_ptrで定義されてるので問題ない

色々なDelphi用スマートポインタ(的)実装

カスタムマネージドレコードを使ったスマートポインタといっても新しく追加されたFinalize()機構以外は、基本的には従来からある機能を使って実装することになります。

昔から実現可能だった順に書くと

  • まずは基本的なInterfacedObjectを使った実装
  type 
    IRc = interface
    end;
    
    TRc = class(TInterfacedObject, IRc)
    end;
  
  procedure Test();
  begin
    var  intf : IRc := TRc.Create();
    
    //intf経由での操作あれこれ
  end; // スコープを抜ける時点でTRcのインスタンスが解放される

ざっくり言うと自動解放してもらいたいオブジェクト(TInterfacedObject継承)をCreateした後は必ずInterfaceとして扱うことで、Interfaceの参照カウンタを使った自動解放の恩恵を得ることができる。
この方法を使って比較的大規模なアプリを組んでみたことがあるけど当時(20年くらい前)は[Weak]もなく循環参照に気を使ったり、どこかでうっかりInterface型じゃない変数に入れたのか謎のダングリングが発生したりと、我々人類が扱うには無理がある(面倒くさい)ものだった。

結局Interfaceの自動解放を無効にして他言語のInterfaceと同じ目的の使い方をするだけのほうが便利だという認識でこれまではやってきています。(このあたり、TOBYさんのブログがすごく参考になっていてよく見に行ってたんだけどリンクを貼ろうとしたら今は404になってしまっていて残念)

これは無名メソッドがreference to functionというInterfaceであることと、従来のrecord型もInterface等のマネージド型を含んでいる場合は見えないfinally節で解放処理が走ることを利用したもので、今回やりたいことのほとんどがこれで良いのではという感じです。なのでかなりの部分を参考にさせていただいています。
スワンマンさんの記事は大変有用で他にも、これとかここもめちゃくちゃ参考になります。

もズバリそのものの解説になります。

生ポインタを扱うスマートポインタ

上に上げた例は基本的にDelphiのclassのインスタンスを扱うものなので、今回目的の他ライブラリからの戻り値として渡ってくる生ポインタを扱うためには手を加える必要があります。

投稿者は最近Rustにかぶれているので、リファレンスカウンタ型のスマートポインタをTRc<T>、弱参照保持用としてTWeak<T>TRc<T>からTWeak<T>方向の変換をdowngrade、逆をupgrade、スマートポインタからデータを取り出す処理をUnwrapと名付けることにします。
あとはC++のユニークポインタstd::unique_ptr的なTUniq<T>(uniqueは打ちにくいので略語化)の実装も用意しました。wasmのC++APIもownstd::unique_ptrとしているのでこちらが目的に近いのですが、後述の理由で使いにくいのでTRc<T>を主に使うこととしました。

カスタムマネージドレコードを利用しますが、ベースとなるのはやはりInterfaceです。

  • まずは生ポインタと、そのポインタの解放処理用のメソッドをまとめて管理するIRcContainer<T>TRcContainer<T>を用意しました。こんな実装
type

  IRcContainer<T> = Interface
    function Unwrap : T;
  End;

  TRcDeleter = reference to procedure(Data : Pointer);
  //TRcDeleter<T> = procedure(data : T)にすると内部エラーが出るので致し方なし

  TRcContainer<T> = class(TInterfacedObject, IRcContainer<T>)
  private
    FData : T;
    FDeleter : TRcDeleter;
  public
    constructor Create(Data : T; Deleter : TRcDeleter = nil);
    destructor  Destroy; override;
    function Unwrap : T;
    function Move : T; // 所有権を移動させる/移動したことを示す状態にする(FDeleterをnilにする。)  ※TObjectに対しては無効	
  end;

implementation
{ TRcContainer<T> }

constructor TRcContainer<T>.Create(Data: T; Deleter: TRcDeleter);
begin
  FData := data;
  FDeleter := deleter;
end;

destructor TRcContainer<T>.Destroy;
var
  p : TObject;
  o : T absolute p;
begin
  o := FData;
  if GetTypeKind(T) = tkClass then
  begin
     p.Free;
  end else begin
    if Assigned(FDeleter) then FDeleter(Pointer(p));
  end;

  inherited;
end;

function TRcContainer<T>.Move : T;
begin
  result := FData;
  FDeleter := nil;
end;

function TRcContainer<T>.Unwrap: T;
begin
  result := FData;
end;

implementsを見てもらうとわかりますが、スコープが外れて解放されるときに、classインスタンスの場合はFreeを、それ以外はdeleterで指定された解放処理用のメソッドを呼び出します。
参照カウンタで管理されている実体はここなので、このInterfaceclassだけでも注意力があればなんとかなります。後述のdowngradeの際も型変換ではなく、このInterfaceをやりとりして実現しています。
ですが、以下のカスタムマネージドレコードを使い、このInterfaceはできるだけユーザー側からは見えないようにします。

リファレンスカウンタ型のスマートポインタTRc<T>の実装

  TRcUnwrapProc<T> = reference to procedure(const data : T);

  // 参照カウント型スマートポインタ
  TRc<T> = record
  private
    FStrongRef : IRcContainer<T>;
  public
    class function Wrap(Data : T; Deleter : TRcDeleter = nil) : TRc<T>; static;
    class operator Implicit(const Src : IRcContainer<T>) : TRc<T>; overload;
    class operator Finalize(var Dest : TRc<T>);
    class operator Positive(Src : TRc<T>) : T; // (+Src) = Unwrap
    class operator Negative(Src : TRc<T>) : IRcContainer<T>; // (-Src) = downgrade
    function Unwrap : T; overload;
    procedure Unwrap(func : TRcUnwrapProc<T>); overload;

//    procedure Drop; // Drop後にUnwrapされるのを防ぎようがないのでコメントアウトしておく。
//     意図したタイミングで解放したい場合はbegin/endでスコープを作る
  end;
  
  // TRc<>を少ない記述量で生成するための関数群
  TRc = class
  public
    class function Wrap<T>(Data : T; Deleter : TRcDeleter = nil) : TRc<T>; static;
    class function NewWrap<PT,T>(Deleter : TRcDeleter = nil) : TRc<PT>; static;
    class function CreateWrap<T:class, constructor>() : TRc<T>; static;
  end; 

  ERcDroppedException = class(Exception)
  end;

  ENewWrapException = class(Exception)
  public
    class constructor Create();
  end;
 
implementation
{ TRc<T> }

 // Drop後にUnwrapされるのを防ぎようがないのでコメントアウトしておく
 {
procedure TRc<T>.Drop;
begin
  FStrongRef := nil; // ref --
end;
}

class operator TRc<T>.Finalize(var Dest: TRc<T>);
begin
  Dest.FStrongRef := nil; // ref --
end;

class operator TRc<T>.Implicit(const Src: IRcContainer<T>): TRc<T>;
begin
  result.FStrongRef := Src;
end;

class operator TRc<T>.Negative(Src: TRc<T>): IRcContainer<T>;
begin
  result := Src.FStrongRef;
end;

class operator TRc<T>.Positive(Src: TRc<T>): T;
begin
  if Src.FStrongRef = nil then raise ERcDroppedException.Create('Unable to unwrap dropped references!');

  result := Src.FStrongRef.Unwrap;
end;

function TRc<T>.Unwrap: T;
begin
  if FStrongRef = nil then raise ERcDroppedException.Create('Unable to unwrap dropped references!');

  result := FStrongRef.Unwrap;
end;

procedure TRc<T>.Unwrap(func: TRcUnwrapProc<T>);
begin
  if FStrongRef = nil then raise ERcDroppedException.Create('Unable to unwrap dropped references!');
  func(FStrongRef.Unwrap);
end;

class function TRc<T>.Wrap(Data: T; Deleter: TRcDeleter): TRc<T>;
begin
  result.FStrongRef := TRcContainer<T>.Create(Data, Deleter); // ref ++
end;

{ TRc }

class function TRc.CreateWrap<T>(): TRc<T>;
begin
  var obj := T.Create();
  result := TRc<T>.Wrap(obj, nil);
end;

procedure DefaultRecordDisposer(p : Pointer);
begin
  Dispose(p);
end;

class function TRc.NewWrap<PT,T>(Deleter: TRcDeleter): TRc<PT>;
type
  PR = ^T;
var
  p : PR;
  d : PT absolute p;
begin
  if GetTypeKind(PT) = tkPointer then
  begin
    System.New(p);
    p^ := Default(T);
    if Assigned(Deleter) then result := TRc<PT>.Wrap(d, Deleter)
    else                      result := TRc<PT>.Wrap(d, DefaultRecordDisposer);
  end else begin
    raise ENewWrapException.Create('TRc.NewWrap<PT,T>() PT must be record pointer! ');
  end;
end;

class function TRc.Wrap<T>(Data: T; Deleter: TRcDeleter): TRc<T>;
begin
  result := TRc<T>.Wrap(data, Deleter);
end;


  • TRc<T>.Wrap() スマートポインタの生成/初期化
  • TRc<T>.Implicit() IRcContainer<T>からTRc<T>への暗黙的変換。IRcContainer<T>を持つ他の型からTRc<T>へ変換するために使われる
  • TRc<T>.Finalize() スコープが外れた際に呼ばれ、FStrongRefの参照カウンタを--する
  • TRc<T>.Positive()/Unwrap() 保持しているデータを取り出す。(+rc)で取り出すかrc.Unwrapで取り出すかの違い
  • TRc<T>.Negative() downgradeするためにTWeak<T>へ代入する際に(-rc)と記述することでIRcContainer<T>経由で代入する

class operator Negative(Src : TRc<T>) : TWeak<T>;と書いて直接TWeak<T>にできれば良いんだけどTWeak<T>は後方にあるし、それならTWeak<T>側に定義すればいいかと思いきや引数とTWeak<T>の型を合わせろとエラーが出るので一旦Interfaceをかませている。 docwikiには

For unary operators, either the input parameter or the return value must be the record type.

と記述されているので本来ならできそうなもんなんだが。 ただし、別の理由もあってInterface経由の方が都合が良いのでこうしている。

使い方

Wrap()で生成(初期化)して、Unwrapで取り出し。記述量低減のために単項+演算子でも取り出し可能。後述のTWeak<T>への代入のために単項-演算子を使います。
生成は型推論の恩恵を受けやすくするためのユーティリティ関数のTRc.Wrap<>()も使えます、というか使います。

TRc<T>recordなので代入や関数への引数渡しで値コピーされるときにIRcContainer<T>の参照カウンタが増え、渡し先の変数のスコープが外れる時に参照カウンタが減ります。

こんな感じ。

  // ローカルで使い捨て
  begin
    var rc := TRc.Wrap(TStringList.Create);
    rc.Unwrap.Add('Unwrapしてstringlistを取り出してAdd()にアクセス');
    (+rc).Add('+演算子でstringlistを取り出してAdd()にアクセス');
  end;  // ここでTStringListは解放される
  
  // ローカルで生成、なにかしら処理してフィールド等で保持
  begin
    var rc := TRc.Wrap(TStringList.Create); // 参照カウンタ1
    (+rc).Add('ちょっとした初期処理');
    
    self.fieldStr := rc; // 参照カウンタ++
  end;  // 参照カウンタが--されるが1なのでTStringListは解放されない
  
  // なにかしらで受け取った生ポインタを、破棄用のメソッドともに管理
  var rc := TRc.Wrap(raw_pointer, disposer);
  // 生ポインタを要求する関数などにはUnwrapして渡す
  api_use_raw_pointer(rc.Unwrap);
  api_use_raw_pointer((+rc));
  
  
  // オブジェクトを生成してフィールド保持
  instanceA.filedStr := TRc.Wrap(TStringList.Create); // instanceA解放時に参照カウンタが-される
  
  // 循環参照によるリークを避けるために弱参照`TWeak<T>`のフィールドに代入
  instanceB.weakStr := (-instanceA.fieldStr); // 括弧は不要だがわかりやすくするため
  
  // あまり記述量は変わらないけどこういう書き方も
  instanceA.filedStr := TRc.CreateWrap<TStringList>;
  
  // recordをNew()で生成して、自動解放時にDispose()させる使い方
  instanceA.filedRecP := TRc.NewWrap<PRecord, TRecod>;
  //                                 ^^^^^^^   ^^^^^^
  //                                    |         +---- 生成するrecordの型
  //                                    +---- 管理するポインタの型  
 
  // recordをNew()で生成して、自動解放時に指定の破棄メソッドを呼ぶ使い方
  instanceA.filedRecP := TRc.NewWrap<PRecord, TRecod>(recordDisposer);
 

弱参照保持用のTWeak<T>の実装

  // TWeak<T>からTRc<T>に昇格させる場合、既に解放済のケースがあるので解放済(IsNone)を表現可能なTOptionRc<T>に昇格させる
  TOptionRc<T> = record
  private
    FStrongRef : IRcContainer<T>;
  public
    class function Wrap(Data : T; Deleter : TRcDeleter = nil) : TOptionRc<T>; static;
    class operator Finalize(var Dest : TOptionRc<T>);
    class operator Negative(Src : TOptionRc<T>) : IRcContainer<T>;

    function IsNone() : Boolean;
    function TryUnwrap(Func : TRcUnwrapProc<T>) : Boolean; overload;
    function TryUnwrap(out Data : T) : Boolean; overload;
    function TryUnwrap(out Data : TRc<T>) : Boolean; overload;

    procedure Drop;   
  end;

   TRcUpgradeProc<T> = reference to procedure(const rc : TRc<T>);

 // TRc<>で管理しているオブジェクトへの弱い参照を管理する
  // 保持している参照は既に存在しない場合があるため
  // オブジェクトにアクセスするためにはTryUpgadeでTRc<>に昇格もしくは
  // Upgrade()/+演算子にてTOptionRc<>に昇格させる必要がある。
  TWeak<T> = record
  private
    [Weak] FWeakRef : IRcContainer<T>;
  public
    class operator Implicit(const Src : TRc<T>) : TWeak<T>; overload;
    class operator Implicit(const Src : IRcContainer<T>) : TWeak<T>; overload;
    class operator Positive(Src : TWeak<T>) : TOptionRc<T>;

    function TryUpgrade(var Dest : TRc<T>) : Boolean; overload;
    function TryUpgrade(func : TRcUpgradeProc<T>) : Boolean; overload;
    function Upgrade() : TOptionRc<T>;
    procedure Drop;
  end;

implementation
{ TOptionRc<T> }

procedure TOptionRc<T>.Drop;
begin
  FStrongRef := nil; // ref --
end;

class operator TOptionRc<T>.Finalize(var Dest: TOptionRc<T>);
begin
  Dest.FStrongRef := nil; // ref --
end;

function TOptionRc<T>.IsNone: Boolean;
begin
  result := FStrongRef = nil;
end;

class operator TOptionRc<T>.Negative(Src: TOptionRc<T>): IRcContainer<T>;
begin
  result := Src.FStrongRef;
end;

function TOptionRc<T>.TryUnwrap(out Data: TRc<T>): Boolean;
begin
  result := FStrongRef <> nil;
  if result then data.FStrongRef := FStrongRef;
end;

function TOptionRc<T>.TryUnwrap(Func: TRcUnwrapProc<T>): Boolean;
begin
  result := FStrongRef <> nil;
  if result then func(FStrongRef.Unwrap);
end;

function TOptionRc<T>.TryUnwrap(out Data: T): Boolean;
begin
  result := FStrongRef <> nil;
  if result then data := FStrongRef.Unwrap;
end;

class function TOptionRc<T>.Wrap(Data: T; Deleter: TRcDeleter): TOptionRc<T>;
begin
  result.FStrongRef := TRcContainer<T>.Create(Data, Deleter); // ref ++
end;

{ TWeak<T> }

procedure TWeak<T>.Drop;
begin
  FWeakRef := nil;
end;

class operator TWeak<T>.Implicit(const Src: TRc<T>): TWeak<T>;
begin
  result.FWeakRef := Src.FStrongRef; // ref +-0
end;

class operator TWeak<T>.Implicit(const Src: IRcContainer<T>): TWeak<T>;
begin
  result.FWeakRef := Src; // ref +-0
end;

class operator TWeak<T>.Positive(Src: TWeak<T>): TOptionRc<T>;
begin
  result.FStrongRef := Src.FWeakRef;
end;

function TWeak<T>.TryUpgrade(var Dest: TRc<T>): Boolean;
begin
  Dest.FStrongRef := FWeakRef;  // ref++
  result := Dest.FStrongRef <> nil;
end;

function TWeak<T>.TryUpgrade(func: TRcUpgradeProc<T>): Boolean;
begin
  var rc : TRc<T>;
  if TryUpgrade(rc) then
  begin
    func(rc);
    result := true;
  end else begin
    result := false;
  end;
end;

function TWeak<T>.Upgrade: TOptionRc<T>;
begin
  result.FStrongRef := FWeakRef;
end;


TWeak<T>の実装といいながらいきなりTOptionRc<T>の実装から始まっているが、これはTweak<T>upgradeしたときにすでに保持しているデータが解放されている=参照先が無い状態になっている可能性があるため。TRc<T>には参照先が無い状態を持たせたくない。他の言語であればNull許容型やRustのOption<T>を使えるがDelphiには相当するものが無いし実装が難しい(他の目的でもOption<T>の実装をしたいのでDelphiジェネリックで簡単に特殊化できるようなってほしい。ところでNull許容型ってロードマップに無かったっけ?)。
よって、参照が無い状態を持つTOptionRc<T>を用意した。TOptionRc<T>にはUnwrapがなく、参照が有効な場合のみデータを取り出すTryUnwrapがあるので面倒だが安全に使える。

  • TWeak<T>.Implicit() TRc<T>からTWeak<T>への暗黙的変換。

  • TWeak<T>.Implicit() IRcContainer<T>からの暗黙的変換

  • TWeak<T>.Positive()/Upgrade() TOptionRc<T>upgradeする

  • TWeak<T>.TryUpgrade() upgradeできる場合、TRc<T>を引数経由で戻す。もしくは指定メソッドを実行する。

  • TOptionRc<T>TRc<T>からUnwrap()/Positive()を取り除いてIsNone()Drop()TryUnwrap()を追加したもの。

  • TOptionRc<T>.IsNone() 保持している参照が有効か?(データを取り出せるか?)を判定する

  • TOptionRc<T>.TryUnwrap() データを取り出せる場合のみ、引数経由で取り出す。もしくは指定メソッドを実行する。

  • TOptionRc<T>.Drop() 参照を解放し、参照カウンタを--する。データが解放されるかどうかはその時の参照カウンタ次第。

所有権のオーナーが一つであるようにふるまうTUniq<>の実装

実装してみたものの使いにくい

  TUniq<T> = record
  private
    FStrongRef : IRcContainer<T>;
  public
    class function Wrap(Data : T; Deleter : TRcDeleter = nil) : TUniq<T>; static;
    class operator Finalize(var Dest : TUniq<T>);
    class operator Assign(var Dest : TUniq<T>; var Src : TUniq<T>);

    function IsNone() : Boolean;
    function TryUnwrap(var Data: T): Boolean; overload;
    function TryUnwrap(Func: TRcUnwrapProc<T>): Boolean; overload;
    function MoveTo : IRcContainer<T>; overload;
    procedure Drop;  // TRc<>とは違い、TryUnwarpでしか取り出せないのでDropも有効にした。
  end;

  TUniq = class
  public
    class function Wrap<T>(Data : T; Deleter : TRcDeleter = nil) : TUniq<T>; static;
  end;
implementation
{ TUniq<T> }

class operator TUniq<T>.Assign(var Dest: TUniq<T>; var Src: TUniq<T>);
begin
  Dest.FStrongRef := Src.FStrongRef;
  Src.FStrongRef := nil;
end;

procedure TUniq<T>.Drop;
begin
  FStrongRef := nil; // ref --
end;

class operator TUniq<T>.Finalize(var Dest: TUniq<T>);
begin
  Dest.FStrongRef := nil; // ref --
end;

function TUniq<T>.IsNone: Boolean;
begin
  result := FStrongRef = nil;
end;

function TUniq<T>.MoveTo: IRcContainer<T>;
begin
  result := FStrongRef; // ref++
  FStrongRef := nil; // ref --
end;


function TUniq<T>.TryUnwrap(var Data: T): Boolean;
begin
  result := FStrongRef <> nil;
  if result then Data := FStrongRef.Unwrap;
end;

function TUniq<T>.TryUnwrap(Func: TRcUnwrapProc<T>): Boolean;
begin
  result := FStrongRef <> nil;
  if result then func(FStrongRef.Unwrap);
end;

class function TUniq<T>.Wrap(Data: T; Deleter: TRcDeleter): TUniq<T>;
begin
  result.FStrongRef := TRcContainer<T>.Create(Data, Deleter); // ref ++
end;

{ TUniq }

class function TUniq.Wrap<T>(Data: T; Deleter: TRcDeleter): TUniq<T>;
begin
  result := TUniq<T>.Wrap(Data, Deleter);
end;
  • C++std::unique_ptrのように代入を禁止してmoveしかできないようにしたかったが、難しかったため代入(Assign)したら必ず移動になるようにした。
  • そのため、参照が無くなっている可能性があるのでTryUnwrapでしか取り出せない仕様としたので使いにくい。Rustのように移動された可能性がある箇所で使用したらコンパイルエラーにできるようになってほしい。(例えば、移動が発生するメソッドに何らかのAttributeを指定しておくなど。今でもフロー解析して未初期化変数の警告を出す処理があるのだからできなくはないと思われる)
  • MoveToTRc<T>に移動するためのメソッド。

力尽きたので使い方は省略。

ユニットソース (Ownership.pas)

gistに上げました

gistを開く

オマケで作りかけのTArcMutex<T>も入ってますが碌に動かしていません。

TArcMutex<T>の使い方

  var mutex := TArcMutex.Wrap(TStringList.Create);
  として
  それぞれのThreadにコピーした上記mutexを使って
  begin
    var lock := mutex.LockW; // 書き込み用ロックを取得
    lock.Unwap.Add('write');
  end; // ここでロック解除
  
  begin
    var lock := mutex.LockR; // 読み出し用ロックを取得
    var cnt := lock.Unwrap.Count;
  end; // ここでロック解除
  
  // NG例1
  var lockobj := mutex.LockW.Unwrap; // ロックとUnwrapを一度にやれると楽!
  lockobj.Add('lock and unwrap'); // NG:ここに至るまでにロックが解除されてしまう
  // NG例2
  var lock := mutex.LockR; // 読み出し用ロックを取得
  ・・・長い処理
  // 意識的にbegin/endで短いスコープを作ってロックしないとロックされっぱなしになる
  

オチ

とここまで作ったものの、もともとの目的であるwasm.hの移植にはTRc<T>をそのまま使うことができませんでした。最初の例を移植するときに以下のようにしたかったんですが

type
  PWasmFuncType = ^TWasmFuncType; // PWasmFuncTypeをAPIでやり取りする
  
  TWasmFuncType = record // TWasmFuncTypeにはAPIのラッパー関数を定義する
//own wasm_functype_t* wasm_functype_new(own wasm_valtype_vec_t* params, own wasm_valtype_vec_t* results);
    class function New(params : PWasmValtypeVec; results : PWasmValtypeVec) : TRc<PWasmFuncType>;
//const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t*);
    function Params() : PWasmFuncType;
  end;

TWasmFuncTypeに定義した関数の戻り値をTRc<PWasmFuncType>にすると、変換したヘッダー(wasm.pas)のコンパイルは通りますが、他のユニットから以下のように型推論しようとすると内部エラーが発生してしまいます

  var rc := TWasmFuncType.New(params, results); // NG:内部エラー
  var rc : TRc<PWasmFuncType> := TWasmFuncType.New(params, results); // OK

仕方ないのでwasm.pasユニットの中でTRcFuncTypeという名前のrecordを作り、実装はTRc<PWasmFuncType>をそのまま書くという残念な方法に落ち着いています。
TWeak<T>TRc<T>の変換やTRc<T>同士の変換にIRcContainer<T>を経由しているのはこのためです。

Discussion