Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

slidenumber: true autoscale: true

Swiftのコンパイル時計算

わいわいswiftc #12

omochimetaru


コンパイル時計算

Swiftのようなコンパイル言語は通常、ソースコードをコンパイルする事によって実行コードを生成してからそれを実行する。つまり、実行はコンパイルの後になる。

一方、コンパイル時計算とは、コンパイルの時点で計算をしてしまう技術のことである。


Swiftとコンパイル時計算

Swift for TensorFlowチームのMarc RasiとChris LattnerがSwiftへのコンパイル時計算の導入を提案している。


static assert

彼らの提案では、static assertという具体的なユーザ機能の提案と、それを実現するための内部的なコンパイル時計算機構の導入がひとまとめになっている。

#assert(1 < 2)

動機


汎用的なコンパイル時計算を言語に搭載することによって、それを用いた様々な言語機能が実現できる。


メモリレイアウト検証

struct MyStruct {
  let x: Int
  let y: Bool
}

#assert(
  MemoryLayout<MyStruct>.size <= 16,
  "MyStructは16バイト以下")

ジェネリックパラメータへの値の導入

let a: FixedArray<Int, 2> = [1, 2]

extension FixedArray where N > 0 {
    var first: T { ... }
}

// firstはnon optional
let x: Int = a.first

let b: FixedArray<Int, 3> = [3, 4, 5]

func +(a: FixedArray<T, N>, b: FixedArray<T, M>)
    -> FixedArray<T, N + M>

// cの型はFixedArray<Int, 5>になる
let c = a + b

#ifの一般化

#ifのためにコンパイラに専用の実装がある。これをコンパイル時計算に統合できる。

#if os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif

StaticStringの廃止

通常のStringがコンパイル時に計算できればStaticStringは不要になる。

//
func fatalError(_ message: @autoclosure () -> String, 
    file: StaticString,
    line: UInt) -> Never

//
func fatalError(_ message: @autoclosure () -> String,
    file: @constexpr String,
    line: UInt) -> Never

グローバル定数の事前計算

現状グローバル変数はスレッドセーフな遅延評価にコンパイルされる。これはオーバーヘッドがある。 最適化によって定数になる場合はある。

これを保証させられる。

@constexpr let x = fibonacci(3)

範囲バグ検出の一般化

下記のオーバーフローはコンパイル時に検出される。このチェックにおける計算処理はコンパイル時計算に統合できる。

// a.swift:1:19: error: arithmetic operation
// '100 + 100' (on type 'Int8') 
// results in an overflow
let a: Int8 = 100 + 100

仕様


@compilerEvaluable

これを付けている関数はコンパイル時に計算できる。外部環境に依存しているなど、コンパイル時に計算できない場合はコンパイルエラーになる。

@compilerEvaluable
func fibonacci(_ n: Int) -> Int {
    if n == 0 { return 0 }
    if n == 1 { return 1 }
    return fibonacci(n - 2) + fibonacci(n - 1)
}

#assert

コンパイル時に評価されるアサーション。通らないとコンパイル時にエラーになる。

// a.swift:7:1: error: assertion failed
#assert(fibonacci(0) == 9)

他の例

func g() -> String {
    let a = "hello"
    let b = "world"
    return a + " " + b
}
func f() {
    #assert(g() == "hello world")
}

実装


SILインタプリタ

SILのインタプリタを実装して、コンパイル中に実行する。


他のアプローチ

ASTインタプリタ

型チェック前にASTをインタプリタで評価する。

pros: 型チェック(Sema)フェーズで値が使えるようになる。値ジェネリクスの実装で便利。

cons: SIL生成がそもそもASTの実行向け変換なので作業の重複になる。


他のアプローチ

実行コード生成

コンパイル時実行したいコードを抽出して実際にコンパイルして実行する。

cons: コンパイル実行が複雑になる。コンパイルが遅くなる。


Swiftサブセット

コンパイル時実行用のSwiftのサブセットを定義する(完全な仕様は大変すぎて無理だから?)。 インタプリタが実行できるSIL命令を限定する。 必要なら徐々に追加する。


Symbolic計算モデル

値を専用の表現形式で保持する。 変数をテーブルで保持する。

メモリモデルを持たない。


static assertの実行までの流れ

Swiftの#assert

→ SILのpoundAssert命令

→ SIL Optimizerのdataflow-diagnosticsパスで、poundAssertを見つけたらそのオペランドをインタプリタで評価。


// Passes.def
PASS(EmitDFDiagnostics, "dataflow-diagnostics",
     "Emit SIL Diagnostics")

// DataflowDiagnostics.cpp
class EmitDFDiagnostics : public SILFunctionTransform {
  ~EmitDFDiagnostics() override {}

  /// The entry point to the transformation.
  void run() override {
    // Don't rerun diagnostics on deserialized functions.
    if (getFunction()->wasDeserializedCanonical())
      return;

    SILModule &M = getFunction()->getModule();
    for (auto &BB : *getFunction())
      for (auto &I : BB) {
        diagnoseUnreachable(&I, M.getASTContext());
        diagnoseStaticReports(&I, M);
      }

    if (M.getASTContext().LangOpts.EnableExperimentalStaticAssert) {
      SymbolicValueBumpAllocator allocator;
      ConstExprEvaluator constantEvaluator(allocator);
      for (auto &BB : *getFunction())
        for (auto &I : BB)
          diagnosePoundAssert(&I, M, constantEvaluator);
    }
  }
};

// DataflowDiagnostics.cpp
static void diagnosePoundAssert(const SILInstruction *I,
                                SILModule &M,
                                ConstExprEvaluator &constantEvaluator) {
  auto *builtinInst = dyn_cast<BuiltinInst>(I);
  if (!builtinInst ||
      builtinInst->getBuiltinKind() != BuiltinValueKind::PoundAssert)
    return;

  SmallVector<SymbolicValue, 1> values;
  constantEvaluator.computeConstantValues({builtinInst->getArguments()[0]},
                                          values);
  SymbolicValue value = values[0];
  if (!value.isConstant()) {
    diagnose(M.getASTContext(), I->getLoc().getSourceLoc(),
             diag::pound_assert_condition_not_constant);

    // If we have more specific information about what went wrong, emit
    // notes.
    if (value.getKind() == SymbolicValue::Unknown)
      value.emitUnknownDiagnosticNotes(builtinInst->getLoc());
    return;
  }
  assert(value.getKind() == SymbolicValue::Integer &&
         "sema prevents non-integer #assert condition");

  APInt intValue = value.getIntegerValue();
  assert(intValue.getBitWidth() == 1 &&
         "sema prevents non-int1 #assert condition");
  if (intValue.isNullValue()) {
    auto *message = cast<StringLiteralInst>(builtinInst->getArguments()[1]);
    StringRef messageValue = message->getValue();
    if (messageValue.empty())
      messageValue = "assertion failed";
    diagnose(M.getASTContext(), I->getLoc().getSourceLoc(),
             diag::pound_assert_failure, messageValue);
    return;
  }
}

冒頭の命令検出と評価の実行がほとんど。残りはassertionの評価。


トップレベルの逆引き実行

発火点をpoundAssertで引っ掛けているので、データフローを再帰的に逆引きするようになっている。


実験

func a() -> Int { return 1 }
func f() {
    #assert(a() + 1 == 2)
}
args = [
    "swift",
    "-Xfrontend", "-enable-experimental-static-assert",
    "-Xllvm", "-debug-only=ConstExpr",
    "a.swift"
]

sil hidden @$s1aAASiyF : $@convention(thin) () -> Int {
bb0:
  %0 = integer_literal $Builtin.Int64, 1          // user: %1
  %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  return %1 : $Int                                // id: %2
} // end sil function '$s1aAASiyF'

sil hidden @$s1a1fyyF : $@convention(thin) () -> () {
bb0:
  // function_ref a()
  %0 = function_ref @$s1aAASiyF : $@convention(thin) () -> Int // user: %1
  %1 = apply %0() : $@convention(thin) () -> Int  // user: %3
  %2 = integer_literal $Builtin.Int64, 1          // user: %5
  %3 = struct_extract %1 : $Int, #Int._value      // user: %5
  %4 = integer_literal $Builtin.Int1, -1          // user: %5
  %5 = builtin "sadd_with_overflow_Int64"
    (%3 : $Builtin.Int64, %2 : $Builtin.Int64, %4 : $Builtin.Int1) :
    $(Builtin.Int64, Builtin.Int1) // users: %7, %6
  %6 = tuple_extract %5 : $(Builtin.Int64, Builtin.Int1), 0 // user: %10
  %7 = tuple_extract %5 : $(Builtin.Int64, Builtin.Int1), 1 // user: %8
  cond_fail %7 : $Builtin.Int1                    // id: %8
  %9 = tuple ()
  %10 = struct $Int (%6 : $Builtin.Int64)         // user: %13
  %11 = tuple ()
  %12 = integer_literal $Builtin.Int64, 2         // user: %14
  %13 = struct_extract %10 : $Int, #Int._value    // user: %14
  %14 = builtin "cmp_eq_Int64"(%13 : $Builtin.Int64, %12 : $Builtin.Int64) : 
    $Builtin.Int1 // user: %15
  %15 = struct $Bool (%14 : $Builtin.Int1)        // user: %16
  %16 = struct_extract %15 : $Bool, #Bool._value  // user: %18
  %17 = string_literal utf8 ""                    // user: %18
  %18 = builtin "poundAssert"(%16 : $Builtin.Int1, %17 : $Builtin.RawPointer) : $()
  %19 = tuple ()                                  // user: %20
  return %19 : $()                                // id: %20
} // end sil function '$s1a1fyyF'


ConstExpr top level:   // function_ref a()
  %0 = function_ref @$s1aAASiyF : $@convention(thin) () -> Int // user: %1
  RESULT: fn: $s1aAASiyF: a.a() -> Swift.Int

ConstExpr call fn: a.a() -> Swift.Int
ConstExpr interpret:   %0 = integer_literal $Builtin.Int64, 1          // user: %1
  RESULT: int: 1
ConstExpr interpret:   %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  RESULT: agg: 1 elt:   int: 1
ConstExpr interpret:   return %1 : $Int                                // id: %2

ConstExpr top level:   %1 = apply %0() : $@convention(thin) () -> Int  // user: %3
  RESULT: agg: 1 elt:   int: 1
ConstExpr top level:   %3 = struct_extract %1 : $Int, #Int._value      // user: %5
  RESULT: int: 1
ConstExpr top level:   %2 = integer_literal $Builtin.Int64, 1          // user: %5
  RESULT: int: 1
ConstExpr top level:   %4 = integer_literal $Builtin.Int1, -1          // user: %5
  RESULT: int: -1
ConstExpr top level:   %5 = builtin "sadd_with_overflow_Int64"
  (%3 : $Builtin.Int64, %2 : $Builtin.Int64, %4 : $Builtin.Int1) :
  $(Builtin.Int64, Builtin.Int1) // users: %7, %6
  RESULT: agg: 2 elements [
  int: 2
  int: 0
]
ConstExpr top level:   %6 = tuple_extract %5 : $(Builtin.Int64, Builtin.Int1), 0 // user: %10
  RESULT: int: 2
ConstExpr top level:   %10 = struct $Int (%6 : $Builtin.Int64)         // user: %13
  RESULT: agg: 1 elt:   int: 2
ConstExpr top level:   %13 = struct_extract %10 : $Int, #Int._value    // user: %14
  RESULT: int: 2
ConstExpr top level:   %12 = integer_literal $Builtin.Int64, 2         // user: %14
  RESULT: int: 2
ConstExpr top level:   %14 = builtin "cmp_eq_Int64"(%13 : $Builtin.Int64, %12 : $Builtin.Int64) : $Builtin.Int1 // user: %15
  RESULT: int: -1
ConstExpr top level:   %15 = struct $Bool (%14 : $Builtin.Int1)        // user: %16
  RESULT: agg: 1 elt:   int: -1
ConstExpr top level:   %16 = struct_extract %15 : $Bool, #Bool._value  // user: %18
  RESULT: int: -1

値の表現

ADTで表現している。 メモリモデルを持たないため、参照の扱いが構造的に表現される。


// SILConstants.h
class SymbolicValue {
private:
  enum RepresentationKind {
    RK_UninitMemory, RK_Unknown,
    RK_Metatype, RK_Function,
    RK_Integer, RK_IntegerInline,
    RK_String, RK_Aggregate,
    RK_Enum, RK_EnumWithPayload,
    RK_DirectAddress, RK_DerivedAddress,
  };

  union {
    UnknownSymbolicValue *unknown;
    TypeBase *metatype;
    SILFunction *function;
    uint64_t *integer;
    uint64_t integerInline;
    const char *string;
    const SymbolicValue *aggregate;
    EnumElementDecl *enumVal;
    EnumWithPayloadSymbolicValue *enumValWithPayload;
    SymbolicValueMemoryObject *directAddress;
    DerivedAddressValue *derivedAddress;
  } value;

  RepresentationKind representationKind : 8;

  union {
    UnknownReason unknownReason : 32;
    unsigned integerBitwidth;
    unsigned stringNumBytes;
    unsigned aggregateNumElements;
  } auxInfo;

public:
  enum Kind {
    Unknown, Metatype, Function,
    Integer, String, Aggregate,
    Enum, EnumWithPayload,
    Address, UninitMemory
  };
};

インターフェースと内部表現があるためややこしいが、enumとunionによる標準的なADT。 プリミティブがIntとStringなのもポイント。


// SILConstants.h
struct SymbolicValueMemoryObject {
private:
  const Type type;
  SymbolicValue value;
};

// SILConstants.cpp
struct DerivedAddressValue final
    : private llvm::TrailingObjects<DerivedAddressValue, unsigned> {
  friend class llvm::TrailingObjects<DerivedAddressValue, unsigned>;

  SymbolicValueMemoryObject *memoryObject;

  const unsigned numElements;

  static DerivedAddressValue *create(SymbolicValueMemoryObject *memoryObject,
                                     ArrayRef<unsigned> elements,
                                     SymbolicValueAllocator &allocator) { }

  ArrayRef<unsigned> getElements() const {
    return {getTrailingObjects<unsigned>(), numElements};
  }

  size_t numTrailingObjects(OverloadToken<unsigned>) const {
    return numElements;
  }
};

ルートオブジェクトとTail allocateな整数の整数の配列。


参照表現の実験

struct S1 {
    var a: Int
    var b: Int
}
struct S2 {
    var c: Int
    var d: S1
}

func f() -> Int {
    var s = S2(c: 0, d: S1(a: 0, b: 0))
    g(&s.d.b)
    return s.d.b
}
func g(_ x: inout Int) {
    x = 1
}
#assert(f() == 1)

sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // function_ref f()
  %2 = function_ref @$s1a1fSiyF : $@convention(thin) () -> Int // user: %3
  %3 = apply %2() : $@convention(thin) () -> Int  // user: %5
  %4 = integer_literal $Builtin.Int64, 1          // user: %6
  %5 = struct_extract %3 : $Int, #Int._value      // user: %6
  %6 = builtin "cmp_eq_Int64"(%5 : $Builtin.Int64, %4 : $Builtin.Int64) : $Builtin.Int1 // user: %7
  %7 = struct $Bool (%6 : $Builtin.Int1)          // user: %8
  %8 = struct_extract %7 : $Bool, #Bool._value    // user: %10
  %9 = string_literal utf8 ""                     // user: %10
  %10 = builtin "poundAssert"(%8 : $Builtin.Int1, %9 : $Builtin.RawPointer) : $()
  %11 = integer_literal $Builtin.Int32, 0         // user: %12
  %12 = struct $Int32 (%11 : $Builtin.Int32)      // user: %13
  return %12 : $Int32                             // id: %13
} // end sil function 'main'

sil hidden @$s1a1fSiyF : $@convention(thin) () -> Int {
bb0:
  %0 = alloc_stack $S2, var, name "s"             // users: %13, %25, %14, %20
  %1 = metatype $@thin S2.Type                    // user: %12
  %2 = integer_literal $Builtin.Int64, 0          // user: %3
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %12
  %4 = metatype $@thin S1.Type                    // user: %10
  %5 = integer_literal $Builtin.Int64, 0          // user: %6
  %6 = struct $Int (%5 : $Builtin.Int64)          // user: %10
  %7 = integer_literal $Builtin.Int64, 0          // user: %8
  %8 = struct $Int (%7 : $Builtin.Int64)          // user: %10
  // function_ref S1.init(a:b:)
  %9 = function_ref @$s1a2S1VAA1bACSi_SitcfC : $@convention(method) (Int, Int, @thin S1.Type) -> S1 // user: %10
  %10 = apply %9(%6, %8, %4) : $@convention(method) (Int, Int, @thin S1.Type) -> S1 // user: %12
  // function_ref S2.init(c:d:)
  %11 = function_ref @$s1a2S2V1c1dACSi_AA2S1VtcfC : $@convention(method) (Int, S1, @thin S2.Type) -> S2 // user: %12
  %12 = apply %11(%3, %10, %1) : $@convention(method) (Int, S1, @thin S2.Type) -> S2 // user: %13
  store %12 to %0 : $*S2                          // id: %13
  %14 = begin_access [modify] [static] %0 : $*S2  // users: %19, %15
  %15 = struct_element_addr %14 : $*S2, #S2.d     // user: %16
  %16 = struct_element_addr %15 : $*S1, #S1.b     // user: %18
  // function_ref g(_:)
  %17 = function_ref @$s1a1gyySizF : $@convention(thin) (@inout Int) -> () // user: %18
  %18 = apply %17(%16) : $@convention(thin) (@inout Int) -> ()
  end_access %14 : $*S2                           // id: %19
  %20 = begin_access [read] [static] %0 : $*S2    // users: %24, %21
  %21 = struct_element_addr %20 : $*S2, #S2.d     // user: %22
  %22 = struct_element_addr %21 : $*S1, #S1.b     // user: %23
  %23 = load %22 : $*Int                          // user: %26
  end_access %20 : $*S2                           // id: %24
  dealloc_stack %0 : $*S2                         // id: %25
  return %23 : $Int                               // id: %26
} // end sil function '$s1a1fSiyF'

struct_element_addrによるgの呼び出しの構築とreturnの構築がポイント。


// g(_:)
sil hidden @$s1a1gyySizF : $@convention(thin) (@inout Int) -> () {
// %0                                             // users: %4, %1
bb0(%0 : $*Int):
  debug_value_addr %0 : $*Int, var, name "x", argno 1 // id: %1
  %2 = integer_literal $Builtin.Int64, 1          // user: %3
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %5
  %4 = begin_access [modify] [static] %0 : $*Int  // users: %5, %6
  store %3 to %4 : $*Int                          // id: %5
  end_access %4 : $*Int                           // id: %6
  %7 = tuple ()                                   // user: %8
  return %7 : $()                                 // id: %8
} // end sil function '$s1a1gyySizF'

storeによる書き込み


ConstExpr top level:   // function_ref f()
  %2 = function_ref @$s1a1fSiyF : $@convention(thin) () -> Int // user: %3
  RESULT: fn: $s1a1fSiyF: a.f() -> Swift.Int

ConstExpr call fn: a.f() -> Swift.Int
ConstExpr interpret:   %0 = alloc_stack $S2, var, name "s"             // users: %13, %25, %14, %20

メモリ確保。ここでSymbolicValueMemoryObjectができる。


ConstExpr interpret:   %1 = metatype $@thin S2.Type                    // user: %12
  RESULT: metatype: S2
ConstExpr interpret:   %2 = integer_literal $Builtin.Int64, 0          // user: %3
  RESULT: int: 0
ConstExpr interpret:   %3 = struct $Int (%2 : $Builtin.Int64)          // user: %12
  RESULT: agg: 1 elt:   int: 0
ConstExpr interpret:   %4 = metatype $@thin S1.Type                    // user: %10
  RESULT: metatype: S1
ConstExpr interpret:   %5 = integer_literal $Builtin.Int64, 0          // user: %6
  RESULT: int: 0
ConstExpr interpret:   %6 = struct $Int (%5 : $Builtin.Int64)          // user: %10
  RESULT: agg: 1 elt:   int: 0
ConstExpr interpret:   %7 = integer_literal $Builtin.Int64, 0          // user: %8
  RESULT: int: 0
ConstExpr interpret:   %8 = struct $Int (%7 : $Builtin.Int64)          // user: %10
  RESULT: agg: 1 elt:   int: 0
ConstExpr interpret:   // function_ref S1.init(a:b:)
  %9 = function_ref @$s1a2S1VAA1bACSi_SitcfC : $@convention(method) (Int, Int, @thin S1.Type) -> S1 // user: %10
  RESULT: fn: $s1a2S1VAA1bACSi_SitcfC: a.S1.init(a: Swift.Int, b: Swift.Int) -> a.S1
ConstExpr interpret:   %10 = apply %9(%6, %8, %4) : $@convention(method) (Int, Int, @thin S1.Type) -> S1 // user: %12

ConstExpr call fn: a.S1.init(a: Swift.Int, b: Swift.Int) -> a.S1
ConstExpr interpret:   %3 = struct $S1 (%0 : $Int, %1 : $Int)          // user: %4
  RESULT: agg: 2 elements [
  agg: 1 elt:     int: 0
  agg: 1 elt:     int: 0
]
ConstExpr interpret:   return %3 : $S1                                 // id: %4

ConstExpr interpret:   // function_ref S2.init(c:d:)
  %11 = function_ref @$s1a2S2V1c1dACSi_AA2S1VtcfC : $@convention(method) (Int, S1, @thin S2.Type) -> S2 // user: %12
  RESULT: fn: $s1a2S2V1c1dACSi_AA2S1VtcfC: a.S2.init(c: Swift.Int, d: a.S1) -> a.S2
ConstExpr interpret:   %12 = apply %11(%3, %10, %1) : $@convention(method) (Int, S1, @thin S2.Type) -> S2 // user: %13

ConstExpr call fn: a.S2.init(c: Swift.Int, d: a.S1) -> a.S2
ConstExpr interpret:   %3 = struct $S2 (%0 : $Int, %1 : $S1)           // user: %4
  RESULT: agg: 2 elements [
  agg: 1 elt:     int: 0
  agg: 2 elements [
    agg: 1 elt:       int: 0
    agg: 1 elt:       int: 0
  ]
]
ConstExpr interpret:   return %3 : $S2                                 // id: %4

値の構築なのでどうでもいい。


ConstExpr interpret:   store %12 to %0 : $*S2                          // id: %13
ConstExpr interpret:   %14 = begin_access [modify] [static] %0 : $*S2  // users: %19, %15
  RESULT: Address[S2] 
ConstExpr interpret:   %15 = struct_element_addr %14 : $*S2, #S2.d     // user: %16
  RESULT: Address[S2] 1
ConstExpr interpret:   %16 = struct_element_addr %15 : $*S1, #S1.b     // user: %18
  RESULT: Address[S2] 1, 1
ConstExpr interpret:   // function_ref g(_:)
  %17 = function_ref @$s1a1gyySizF : $@convention(thin) (@inout Int) -> () // user: %18
  RESULT: fn: $s1a1gyySizF: a.g(inout Swift.Int) -> ()
ConstExpr interpret:   %18 = apply %17(%16) : $@convention(thin) (@inout Int) -> ()

struct_element_addrに基づいてDerivedAddressValueのパスが育つ。


ConstExpr call fn: a.g(inout Swift.Int) -> ()
ConstExpr interpret:   debug_value_addr %0 : $*Int, var, name "x", argno 1 // id: %1
ConstExpr interpret:   %2 = integer_literal $Builtin.Int64, 1          // user: %3
  RESULT: int: 1
ConstExpr interpret:   %3 = struct $Int (%2 : $Builtin.Int64)          // user: %5
  RESULT: agg: 1 elt:   int: 1
ConstExpr interpret:   %4 = begin_access [modify] [static] %0 : $*Int  // users: %5, %6
  RESULT: Address[S2] 1, 1
ConstExpr interpret:   store %3 to %4 : $*Int                          // id: %5
ConstExpr interpret:   end_access %4 : $*Int                           // id: %6
ConstExpr interpret:   %7 = tuple ()                                   // user: %8
  RESULT: agg: 0 elements []
ConstExpr interpret:   return %7 : $()                                 // id: %8

DerivedAddressValueで渡ってきているのがわかる。storeで書き込まれる。


ConstExpr interpret:   end_access %14 : $*S2                           // id: %19
ConstExpr interpret:   %20 = begin_access [read] [static] %0 : $*S2    // users: %24, %21
  RESULT: Address[S2] 
ConstExpr interpret:   %21 = struct_element_addr %20 : $*S2, #S2.d     // user: %22
  RESULT: Address[S2] 1
ConstExpr interpret:   %22 = struct_element_addr %21 : $*S1, #S1.b     // user: %23
  RESULT: Address[S2] 1, 1
ConstExpr interpret:   %23 = load %22 : $*Int                          // user: %26
  RESULT: agg: 1 elt:   int: 1
ConstExpr interpret:   end_access %20 : $*S2                           // id: %24
ConstExpr interpret:   dealloc_stack %0 : $*S2                         // id: %25
ConstExpr interpret:   return %23 : $Int                               // id: %26

DerivedAddressValueを伸ばした後、loadで取り出している。


ConstExpr top level:   %3 = apply %2() : $@convention(thin) () -> Int  // user: %5
  RESULT: agg: 1 elt:   int: 1
ConstExpr top level:   %5 = struct_extract %3 : $Int, #Int._value      // user: %6
  RESULT: int: 1
ConstExpr top level:   %4 = integer_literal $Builtin.Int64, 1          // user: %6
  RESULT: int: 1
ConstExpr top level:   %6 = builtin "cmp_eq_Int64"(%5 : $Builtin.Int64, %4 : $Builtin.Int64) : $Builtin.Int1 // user: %7
  RESULT: int: -1
ConstExpr top level:   %7 = struct $Bool (%6 : $Builtin.Int1)          // user: %8
  RESULT: agg: 1 elt:   int: -1
ConstExpr top level:   %8 = struct_extract %7 : $Bool, #Bool._value    // user: %10
  RESULT: int: -1

トップレベルのおまけ。


Symbolic計算とString

ConstExprはメモリモデルを持たない事を見てきた。しかし、Swift.Stringはビット最適化されていて、128ビットのデータであり、ビットの状態によって、Small Stringだったりポインタだったりする。


// String.swift
@frozen
public struct String {
  public // @SPI(Foundation)
  var _guts: _StringGuts
}

// StringGuts.swift
@frozen
public // SPI(corelibs-foundation)
struct _StringGuts {
  @usableFromInline
  internal var _object: _StringObject
}

@frozen @usableFromInline
internal struct _StringObject {
  @usableFromInline
  internal var _countAndFlagsBits: UInt64

  @usableFromInline
  internal var _object: Builtin.BridgeObject
}

ConstExprでのStringの表現

標準ライブラリでのStringの実装を無視して、独自に表現する。メソッドもインタプリタ側で実装を持つ。


// ConstExpr.cpp
llvm::Optional<SymbolicValue>
ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply,
                                                   WellKnownFunction callee) {
  switch (callee) {
  case WellKnownFunction::StringInitEmpty: { // String.init()
  }
  case WellKnownFunction::StringMakeUTF8: {
    // String.init(_builtinStringLiteral start: Builtin.RawPointer,
    //             utf8CodeUnitCount: Builtin.Word,
    //             isASCII: Builtin.Int1)
  }
  case WellKnownFunction::StringAppend: {
  }
  case WellKnownFunction::StringEquals: {
  }
  case WellKnownFunction::StringEscapePercent: {
  }
  }
  llvm_unreachable("unhandled WellKnownFunction");
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.