1
/
5

SwiftにはREPLモードが2つある!?

スマフォ開発チームの高地です。

iOS開発界隈ではSwiftに注目が集まっています。
iOS8からはObjective-Cから解放されることで、喜んでいる開発者も多いのではないでしょうか。
ということで、今回はSwiftのREPL機能についてお話しします。

Swiftの2つのREPLモード

SwiftにはREPLが用意されているので、 Ruby、Python、Node.js等のLL言語のように簡単に動作確認ができます。
ところがSwift –helpをみていたところ、実はREPLモードが2つあることにきがつきました。

-integrated-repl Integrated REPL mode-repl REPL mode

たぶん、-replは普通一般的なREPLモードだと思われます。それでは-integrated-replモードとはどんなことができるのでしょうか?
調査環境はXcode6-Beta2です。

SwiftのREPL:-integrated-replモード

まずは-integrated-replモードに切り替え、対話モードに入ります。実際にこのモードのオプションはどのようなものがあるのか:helpでたたいてみました。

:quit - quit the interpreter (you can also use :exit or Control+D or exit(0)) 
:autoindent (on|off) - turn on/off automatic indentation of bracketed lines 
:constraints debug (on|off) - turn on/off the debug output for the constraint-based type checker 
:dump_ir - dump the LLVM IR generated by the REPL 
:dump_ast - dump the AST representation of the REPL input 
:dump_decl  - dump the AST representation of the named declarations 
:dump_source - dump the user input (ignoring lines with errors) 
:print_decl  - print the AST representation of the named declarations 
:print_module  - print the decls in the given module, but not submodules 

-integrated-replモードには9つのオプションが用意されています。
:quite、:autoindentは想像がつくので省略し、1つずつ見てみましょう。

constraints debug

:constraints debug (on|off) – turn on/off the debug output for the constraint-based type checker

XcodeのConstraintsのためのデバッグオプションの設定のようです。
ここでon, offをきりかえて、一般的なREPL modeでデバッグを行うのでしょうか・・・デバッグ環境を用意するのも手間なので、これ以上は時間をかけないでおきます。

dump_ir

:dump_ir – dump the LLVM IR generated by the REPL

LLVMのIR(Intermediate Representation)ということで、LLVMでコンパイルされた中間コードを出力するようです、実際にREPLに入力してみます。MoenyForwardという文字列をdataというmutableな変数に代入するだけという1行コードです。

(swift) var data:String = "MoneyForward" 
// data : String = "MoneyForward"   

たったこれだけではありあますが:dump_irで出力させてみると

swift) :dump_ir 
 
; ModuleID = 'REPL' 
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 
target triple = "x86_64-apple-darwin13.2.0" 
 
%0 = type  
%1 = type  
%2 = type  
%3 = type  
%4 = type  
%5 = type { i8**, %6 } 
%6 = type { i64 } 
%7 = type { void (%8*)*, i8**, %6 } 
%8 = type { %6*, i32, i32 } 
%9 = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i32, i32 } 
%10 = type  
%11 = type { [24 x i8], %6*, i8** } 
%12 = type { i8*, %8* } 
%13 = type  
%14 = type { [24 x i8], %6*, i8** } 
%15 = type  
%16 = type  
%17 = type  
%18 = type  
%19 = type { [24 x i8], %6*, i8** } 
%20 = type { [24 x i8], %6*, i8** } 
%21 = type { [24 x i8], %6*, i8** } 
%22 = type  
%23 = type  
%24 = type  
%25 = type { [24 x i8], %6* } 
%26 = type  
%27 = type  
%28 = type  
%29 = type { %objc_object* } 
%30 = type { %objc_object* } 
%31 = type  
%32 = type  
%33 = type  
%34 = type  
%35 = type  
%36 = type  
%swift.type_pattern = type opaque 
%objc_object = type opaque 
%swift.opaque = type opaque 
%CSs17HeapBufferStorage = type opaque 
 
@_Tv4REPL4dataSS = global %0 zeroinitializer, align 8 
@0 = private unnamed_addr constant [13 x i16] [i16 77, i16 111, i16 110, i16 101, i16 121, i16 70, i16 111, i16 114, i16 119, i16 97, i16 114, i16 100, i16 0] 
@1 = private unnamed_addr constant [20 x i16] [i16 47, i1 
 
 
(省略)・・・ 
 
 
; :13                                      ; preds = %4 
  %14 = bitcast %29* %1 to %34* 
  %._storage = getelementptr inbounds %34* %14, i32 0, i32 0 
  %._storage.storage = getelementptr inbounds %35* %._storage, i32 0, i32 0 
  %15 = bitcast %36* %._storage.storage to i64* 
  %16 = load i64* %15, align 8 
  %17 = inttoptr i64 %16 to %8* 
  call void @swift_retain_noresult(%8* %17) #2 
  %18 = getelementptr inbounds %29* %1, i32 0, i32 0 
  %19 = load %objc_object** %18, align 8 
  call void @objc_release(%objc_object* %19) #2 
  call void @objc_release(%objc_object* %0) #2 
  ret i64 %16 
} 
 
attributes #0 = { nounwind readnone } 
attributes #1 = { noreturn nounwind } 
attributes #2 = { nounwind } 
 
!llvm.module.flags = !{!0, !1, !2, !5, !6, !7, !8} 
 
!0 = metadata !{i32 2, metadata !"Dwarf Version", i32 3} 
!1 = metadata !{i32 1, metadata !"Debug Info Version", i32 1} 
!2 = metadata !{i32 6, metadata !"Linker Options", metadata !3} 
!3 = metadata !{metadata !4} 
!4 = metadata !{metadata !"-lswift_stdlib_core"} 
!5 = metadata !{i32 1, metadata !"Objective-C Version", i32 2} 
!6 = metadata !{i32 1, metadata !"Objective-C Image Info Version", i32 0} 
!7 = metadata !{i32 1, metadata !"Objective-C Image Info Section", metadata !"__DATA, __objc_imageinfo, regular, no_dead_strip"} 
!8 = metadata !{i32 4, metadata !"Objective-C Garbage Collection", i32 0} 
(swift)  
 

かなりアセンブリチックな中間コードが約6000行ほど表示されました。
グローバルIDやローカルID(%swift.opaque)などが大量に出力されているのがわかります。
まさしく中間コードが出力されました。たった1行の宣言コードでしたが、コンパイルするための最適化のためでしょうか、非常に大量のコードが生成されているようです。

dump_ast

:dump_ast– dump the AST representation of the REPL input

これも上の:dumo_irと似ている印象を受けますが、コンパイル過程のAST(抽象構文木)が出力されます。

swift) :dump_ast 
 
(source_file 
  (top_level_code_decl 
    (brace_stmt 
      (tuple_expr type='()' location=:1:5 range=[:1:5 - line:1:6])) 
  (top_level_code_decl 
    (brace_stmt 
      (pattern_binding_decl 
        (pattern_typed type='String' 
          (pattern_named type='String' 'data') 
          (type_ident 
            (component id='String' bind=type))) 
        (call_expr implicit type='String' location=:1:19 range=[:1:19 - line:1:19] 
          (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19] 
            (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no) 
            (type_expr implicit type='String.Type' location=:1:19 range=[:1:19 - line:1:19] typerepr='<>')) 
          (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:19 range=[:1:19 - line:1:19] encoding=utf16 value="MoneyForward"))) 
) 
  (top_level_code_decl 
    (brace_stmt 
      (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] 
        (closure_expr type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] discriminator=0 
          (brace_stmt 
            (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] 
              (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).print [with T=String] specialized=no) 
              (call_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] 
                (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] 
                  (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no) 
                  (type_expr implicit type='String.Type' location=:1:1 range=[:1:1 - line:1:1] typerepr='<>')) 
                (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:1 range=[:1:1 - line:1:1] encoding=utf16 value="// data : String = "))) 
            (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1] 
              (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).debugPrintln [with T=String] specialized=no) 
              (declref_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).top-level code.explicit closure discriminator=0.arg@:1:1 specialized=no)))) 
        (paren_expr type='(String)' location=:1:1 range=[:1:1 - line:1:1] 
          (load_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] 
            (declref_expr implicit type='@lvalue String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).data@:1:5 specialized=no))))) 
  (var_decl "data" type='String' storage_kind='stored')) 

まだ、ASTのほうが、コード解析段階なので、人間の目にやさしいです。行数も36行程度なので、そこまで苦労せずにリーディングできそうです。

(declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no) 

このあたりみると、内部的にUTF-16に変換していることが推測できます。

dump_decl

:dump_decl – dump the AST representation of the named declaration

は、AST内で定義されている変数をnameに指定することで、内容が確認できます。上記で書いたdata変数を指定してみると

(swift) :dump_decl data 
(var_decl "data" type='String' storage_kind='stored') 

とAST内での解析されたdata変数の内容が出てきます。var_declが変数定義を表し、typeが型、storage_kindはなんでしょう?変数が参照渡しなのか、値渡しを表しているのでしょうか、、、ちょっと不明ですね。

dump_source

:dump_source – dump the user input (ignoring lines with errors)

これは、ユーザーが入力したソースコードが出力されます。そのままですね。

(swift) :dump_source 
var data:String = "MoneyForward" 

そのまま表示されました。

print_decl

:print_decl – print the AST representation of the named declarations

は、:dump_declオプション時よりももっと簡略的に出力されるようです

(swift) :print_decl data 
var data: String 

こちらはほぼ、素のSwiftコード定義に近い出力結果となりました。

:print_module – print the decls in the given module, but not submodule

は、Swiftで作成されたモジュールの内容を出力してくれるようです。実際にモジュールを作成してみればよいのですが、うまく作成できませんでした。何かよいモジュールはないか、色々とためしたところ、Swift自身を指定できるみたいです。

print_module swift

(swift) :print_module swift 

を行うとSwift内部で定義されている、構造体、Extensions、プロトコル、など基底となる構造がずらずらとでてきます。何個かピックアップしますと、

struct UnsafePointer : BidirectionalIndex, Comparable, Hashable, LogicValue { 
  var value: RawPointer 
  init() 
  init(_ value: RawPointer) 
  init(_ other: COpaquePointer) 
  init(_ value: Int) 
  init(_ from: UnsafePointer) 
  static func null() -> UnsafePointer 
  static func alloc(num: Int) -> UnsafePointer 
  func dealloc(num: Int) 
  var memory: T { 
    @transparent get {} 
    @transparent set {} 
  } 
  func initialize(newvalue: T) 
  func move() -> T 
  func moveInitializeBackwardFrom(source: UnsafePointer, count: Int) 
  func moveAssignFrom(source: UnsafePointer, count: Int) 
  func moveInitializeFrom(source: UnsafePointer, count: Int) 
  func initializeFrom(source: UnsafePointer, count: Int) 
  func initializeFrom(source: C) 
  func destroy() 
  func destroy(count: Int) 
  var _isNull: Bool { 
    @transparent get {} 
  } 
  @transparent func getLogicValue() -> Bool 
  subscript (i: Int) -> T { 
    @transparent get {} 
    @transparent set {} 
  } 
  var hashValue: Int { 
    get {} 
  } 
  func succ() -> UnsafePointer 
  func pred() -> UnsafePointer 
  @conversion @transparent func __conversion() -> CMutablePointer 
  func __conversion() -> CMutableVoidPointer 
  @conversion @transparent func __conversion() -> CConstPointer 
  @conversion @transparent func __conversion() -> CConstVoidPointer 
  @conversion @transparent func __conversion() -> AutoreleasingUnsafePointer 
  init(_ cp: CConstPointer) 
  init(_ cm: CMutablePointer) 
  init(_ op: AutoreleasingUnsafePointer) 
  init(_ cp: CConstVoidPointer) 
  init(_ cp: CMutableVoidPointer) 
} 

ポイントをキャストするためのUnsafePointer構造体や

     
extension Dictionary : Printable, DebugPrintable { 
  func _makeDescription(#isDebug: Bool) -> String 
  var description: String { 
    get {} 
  } 
  var debugDescription: String { 
    get {} 
  } 
} 
extension Dictionary : Reflectable { 
  func getMirror() -> Mirror 
} 

Dictionaryなどなどおなじみの構造が確認できます。ここから考えるに-integrated-replにおいて、Swift自身もモジュールというあつかいなのかなと。

ちなみに、対話モードにしなくても、出力結果をコンソール上に出力する方法もあります。shellのechoコマンドで、内部で実行したいオプションをわたしてあげても、同じ結果が得られます。

echo :print_module Swift | xcrun swift -integrated-repl 

公式の情報が少ないので、本質的な-integrated-replの使い方はまだわかりませんが、このようにコンパイルする過程の状態(AST、IR)を容易に確認できる手段がREPLで用意されているのというのは驚きでした。Swiftを使う開発者が、より最適なコーディングを理解するための情報を得るツールとしても使えますね。
このような開発ツールの充実度を高めようとしている姿勢からAppleのSwiftにかける本気度を感じました。

マネーフォワードでは、他のエンジニアとは異なった視点で、問題を解決できる、マニアックなエンジニアも募集中です。

株式会社マネーフォワードでは一緒に働く仲間を募集しています
元記事: SwiftにはREPLモードが2つある!?
1 いいね!
1 いいね!
今週のランキング