slidenumber: true autoscale: true
Swiftのようなコンパイル言語は通常、ソースコードをコンパイルする事によって実行コードを生成してからそれを実行する。つまり、実行はコンパイルの後になる。
一方、コンパイル時計算とは、コンパイルの時点で計算をしてしまう技術のことである。
Swift for TensorFlowチームのMarc RasiとChris LattnerがSwiftへのコンパイル時計算の導入を提案している。
-
フォーラム投稿 https://forums.swift.org/t/compile-time-constant-expressions-for-swift/12879
-
プロポーザル下書き https://gist.github.com/marcrasi/b0da27a45bb9925b3387b916e2797789
-
PR (
marcrasi:const-evaluator) swiftlang/swift#19579
彼らの提案では、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 os(iOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif通常の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
func fibonacci(_ n: Int) -> Int {
if n == 0 { return 0 }
if n == 1 { return 1 }
return fibonacci(n - 2) + fibonacci(n - 1)
}コンパイル時に評価されるアサーション。通らないとコンパイル時にエラーになる。
// 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のインタプリタを実装して、コンパイル中に実行する。
型チェック前にASTをインタプリタで評価する。
pros: 型チェック(Sema)フェーズで値が使えるようになる。値ジェネリクスの実装で便利。
cons: SIL生成がそもそもASTの実行向け変換なので作業の重複になる。
コンパイル時実行したいコードを抽出して実際にコンパイルして実行する。
cons: コンパイル実行が複雑になる。コンパイルが遅くなる。
コンパイル時実行用のSwiftのサブセットを定義する(完全な仕様は大変すぎて無理だから?)。 インタプリタが実行できるSIL命令を限定する。 必要なら徐々に追加する。
値を専用の表現形式で保持する。 変数をテーブルで保持する。
メモリモデルを持たない。
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
トップレベルのおまけ。
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
}標準ライブラリでの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");
}