Swift Intermediate Language A high level IR to complement LLVM Joe - - PowerPoint PPT Presentation

swift intermediate language
SMART_READER_LITE
LIVE PREVIEW

Swift Intermediate Language A high level IR to complement LLVM Joe - - PowerPoint PPT Presentation

Swift Intermediate Language A high level IR to complement LLVM Joe Gro ff and Chris Lattner Why SIL? Clang Parse Sema CodeGen LLVM *.c *.o AST AST' IR Clang Parse Sema CodeGen LLVM *.c *.o AST AST' IR Clang Parse Sema


slide-1
SLIDE 1

Swift Intermediate Language

A high level IR to complement LLVM

Joe Groff and Chris Lattner

slide-2
SLIDE 2

Why SIL?

slide-3
SLIDE 3

Clang

Parse Sema CodeGen LLVM

*.c AST AST' IR *.o

slide-4
SLIDE 4

Clang

Parse Sema CodeGen LLVM

*.c AST AST' IR *.o

slide-5
SLIDE 5

Clang

Parse Sema CodeGen LLVM

*.c AST AST' IR *.o

slide-6
SLIDE 6

Clang

CodeGen

CodeGen

🌳 🌳 🌳 🌳 🌳 🌳🌳 🌳 🌳

Parse Sema LLVM

*.c AST AST' IR *.o

slide-7
SLIDE 7

Clang

CodeGen

CodeGen

🌳 🌳 🌳 🌳 🌳 🌳🌳 🌳 🌳

Parse Sema LLVM

*.c AST AST' IR *.o

slide-8
SLIDE 8

Clang

  • Wunreachable-code
  • Wuninitialized

Static Analyzer

CodeGen

CodeGen

🌳 🌳 🌳 🌳 🌳 🌳🌳 🌳 🌳

Parse Sema LLVM

*.c AST AST' IR *.o

slide-9
SLIDE 9

Clang

Analysis

CFG

  • Wunreachable-code
  • Wuninitialized

Static Analyzer

CodeGen

CodeGen

🌳 🌳 🌳 🌳 🌳 🌳🌳 🌳 🌳

Parse Sema LLVM

*.c AST AST' IR *.o

slide-10
SLIDE 10

Clang

Wide abstraction gap between source and LLVM IR IR isn't suitable for source-level analysis CFG lacks fidelity CFG is off the hot path Duplicated effort in CFG and IR lowering

slide-11
SLIDE 11

Swift

slide-12
SLIDE 12

Swift

Higher-level language

slide-13
SLIDE 13

Swift

Higher-level language

  • Move more of the language into code
slide-14
SLIDE 14

Swift

Higher-level language

  • Move more of the language into code
  • Protocol-based generics
slide-15
SLIDE 15

Swift

Higher-level language

  • Move more of the language into code
  • Protocol-based generics

Safe language

slide-16
SLIDE 16

Swift

Higher-level language

  • Move more of the language into code
  • Protocol-based generics

Safe language

  • Uninitialized vars, unreachable code should be compiler errors
slide-17
SLIDE 17

Swift

Higher-level language

  • Move more of the language into code
  • Protocol-based generics

Safe language

  • Uninitialized vars, unreachable code should be compiler errors
  • Bounds and overflow checks
slide-18
SLIDE 18

Parse Sema

AST

IRGen

IR

LLVM

*.o

Swift

slide-19
SLIDE 19

SIL

SILGen Parse Sema

AST

IRGen

IR

LLVM

*.o

Swift

slide-20
SLIDE 20

SIL

SILGen Parse Sema

AST

IRGen

IR

Swift

Analysis

slide-21
SLIDE 21

SIL

Fully represents program semantics Designed for both code generation and analysis Sits on the hot path of the compiler pipeline Bridges the abstraction gap between source and LLVM

slide-22
SLIDE 22

Design of SIL

slide-23
SLIDE 23

Fibonacci

func fibonacci(lim: Int) { var a = 0, b = 1 while b < lim { print(b) (a, b) = (b, a + b) } }

slide-24
SLIDE 24

Fibonacci

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-25
SLIDE 25

Fibonacci

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-26
SLIDE 26

Fibonacci

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-27
SLIDE 27

High-Level Type System

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-28
SLIDE 28

High-Level Type System

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-29
SLIDE 29

High-Level Type System

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-30
SLIDE 30

High-Level Type System

slide-31
SLIDE 31

High-Level Type System

Keeps machine-level type layout abstract

slide-32
SLIDE 32

High-Level Type System

Keeps machine-level type layout abstract Type-related information is implicit

slide-33
SLIDE 33

High-Level Type System

Keeps machine-level type layout abstract Type-related information is implicit

  • TBAA
slide-34
SLIDE 34

High-Level Type System

Keeps machine-level type layout abstract Type-related information is implicit

  • TBAA
  • Type parameters for generic specialization
slide-35
SLIDE 35

High-Level Type System

Keeps machine-level type layout abstract Type-related information is implicit

  • TBAA
  • Type parameters for generic specialization
  • Classes and protocol conformances for devirtualization
slide-36
SLIDE 36

High-Level Type System

Keeps machine-level type layout abstract Type-related information is implicit

  • TBAA
  • Type parameters for generic specialization
  • Classes and protocol conformances for devirtualization

Strongly-typed IR helps validate compiler correctness

slide-37
SLIDE 37

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int):

slide-38
SLIDE 38

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1

slide-39
SLIDE 39

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-40
SLIDE 40

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-41
SLIDE 41

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-42
SLIDE 42

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-43
SLIDE 43

Builtins

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-44
SLIDE 44

Builtins

slide-45
SLIDE 45

Builtins

Builtins opaquely represent types and operations of the layer below SIL

slide-46
SLIDE 46

Builtins

Builtins opaquely represent types and operations of the layer below SIL Swift's standard library implements user-level interfaces on top of builtins

slide-47
SLIDE 47

Builtins

Builtins opaquely represent types and operations of the layer below SIL Swift's standard library implements user-level interfaces on top of builtins

struct Int { var value: Builtin.Int64 } struct Bool { var value: Builtin.Int1 } func ==(lhs: Int, rhs: Int) -> Bool { return Bool(value: Builtin.icmp_eq_Word(lhs.value, rhs.value)) }

slide-48
SLIDE 48

Builtins

Builtins opaquely represent types and operations of the layer below SIL Swift's standard library implements user-level interfaces on top of builtins

struct Int { var value: Builtin.Int64 } struct Bool { var value: Builtin.Int1 } func ==(lhs: Int, rhs: Int) -> Bool { return Bool(value: Builtin.icmp_eq_Word(lhs.value, rhs.value)) }

SIL is intentionally ignorant of:

  • Machine-level type layout
  • Arithmetic, comparison, etc. machine-level operations
slide-49
SLIDE 49

Literal Instructions

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Word, 0 %b0 = integer_literal $Builtin.Word, 1 %lt = builtin "icmp_lt_Word"(%b: $Builtin.Word, %lim: $Builtin.Word): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-50
SLIDE 50

Literal Instructions

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Word, 0 %b0 = integer_literal $Builtin.Word, 1 %lt = builtin "icmp_lt_Word"(%b: $Builtin.Word, %lim: $Builtin.Word): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-51
SLIDE 51

Literal Instructions

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Word, 0 %b0 = integer_literal $Builtin.Word, 1 %lt = builtin "icmp_lt_Word"(%b: $Builtin.Word, %lim: $Builtin.Word): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-52
SLIDE 52

Literal Instructions

slide-53
SLIDE 53

Literal Instructions

More uniform IR representation

slide-54
SLIDE 54

Literal Instructions

More uniform IR representation

  • No Value/Constant divide
slide-55
SLIDE 55

Literal Instructions

More uniform IR representation

  • No Value/Constant divide

All instructions carry source location information for diagnostics

slide-56
SLIDE 56

Literal Instructions

More uniform IR representation

  • No Value/Constant divide

All instructions carry source location information for diagnostics Especially important for numbers, which need to be statically checked for overflow

slide-57
SLIDE 57

Phi Nodes?

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit

slide-58
SLIDE 58

Phi Nodes?

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit br loop(%a0: $Builtin.Int64, %b0: $Builtin.Int64) loop(%a: $Builtin.Int64, %b: $Builtin.Int64): body: %b1 = struct $Swift.Int (%b: $Builtin.Int64) apply %print(%b1) : $(Swift.Int) -> () %c = builtin "add_Int64"(%a: $Builtin.Int64, %b: $Builtin.Int64): $Builtin.Int64 br loop(%b: $Builtin.Int64, %c: $Builtin.Int64) exit: %unit = tuple () return %unit: $() }

slide-59
SLIDE 59

Phi Nodes?

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit br loop(%a0: $Builtin.Int64, %b0: $Builtin.Int64) loop(%a: $Builtin.Int64, %b: $Builtin.Int64): body: %b1 = struct $Swift.Int (%b: $Builtin.Int64) apply %print(%b1) : $(Swift.Int) -> () %c = builtin "add_Int64"(%a: $Builtin.Int64, %b: $Builtin.Int64): $Builtin.Int64 br loop(%b: $Builtin.Int64, %c: $Builtin.Int64) exit: %unit = tuple () return %unit: $() }

slide-60
SLIDE 60

Basic Block Arguments

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit br loop(%a0: $Builtin.Int64, %b0: $Builtin.Int64) loop(%a: $Builtin.Int64, %b: $Builtin.Int64): body: %b1 = struct $Swift.Int (%b: $Builtin.Int64) apply %print(%b1) : $(Swift.Int) -> () %c = builtin "add_Int64"(%a: $Builtin.Int64, %b: $Builtin.Int64): $Builtin.Int64 br loop(%b: $Builtin.Int64, %c: $Builtin.Int64) exit: %unit = tuple () return %unit: $() }

slide-61
SLIDE 61

Basic Block Arguments

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit br loop(%a0: $Builtin.Int64, %b0: $Builtin.Int64) loop(%a: $Builtin.Int64, %b: $Builtin.Int64): body: %b1 = struct $Swift.Int (%b: $Builtin.Int64) apply %print(%b1) : $(Swift.Int) -> () %c = builtin "add_Int64"(%a: $Builtin.Int64, %b: $Builtin.Int64): $Builtin.Int64 br loop(%b: $Builtin.Int64, %c: $Builtin.Int64) exit: %unit = tuple () return %unit: $() }

slide-62
SLIDE 62

Basic Block Arguments

slide-63
SLIDE 63

Basic Block Arguments

More uniform IR representation

slide-64
SLIDE 64

Basic Block Arguments

More uniform IR representation

  • Entry block is no longer a special case
slide-65
SLIDE 65

Basic Block Arguments

More uniform IR representation

  • Entry block is no longer a special case
  • No special case code for managing phis
slide-66
SLIDE 66

Basic Block Arguments

More uniform IR representation

  • Entry block is no longer a special case
  • No special case code for managing phis

Provides natural notation for conditional defs

slide-67
SLIDE 67

entry: success failure

Basic Block Arguments

More uniform IR representation

  • Entry block is no longer a special case
  • No special case code for managing phis

Provides natural notation for conditional defs

%s = /* can only use %s here */ %e = landingpad : : invoke @mayThrowException(), label %success, label %failure

slide-68
SLIDE 68

entry: success failure

Basic Block Arguments

More uniform IR representation

  • Entry block is no longer a special case
  • No special case code for managing phis

Provides natural notation for conditional defs

/* can only use %s here */ (%s): (%e): invoke @mayThrowException(), label %success, label %failure

slide-69
SLIDE 69

Fibonacci

sil @fibonacci: $(Swift.Int) -> () { entry(%limi: $Swift.Int): %lim = struct_extract %limi: $Swift.Int, #Int.value %print = function_ref @print: $(Swift.Int) -> () %a0 = integer_literal $Builtin.Int64, 0 %b0 = integer_literal $Builtin.Int64, 1 br loop(%a0: $Builtin.Int64, %b0: $Builtin.Int64) loop(%a: $Builtin.Int64, %b: $Builtin.Int64): %lt = builtin "icmp_lt_Int64"(%b: $Builtin.Int64, %lim: $Builtin.Int64): $Builtin.Int1 cond_br %lt: $Builtin.Int1, body, exit body: %b1 = struct $Swift.Int (%b: $Builtin.Int64) apply %print(%b1) : $(Swift.Int) -> () %c = builtin "add_Int64"(%a: $Builtin.Int64, %b: $Builtin.Int64): $Builtin.Int64 br loop(%b: $Builtin.Int64, %c: $Builtin.Int64) exit: %unit = tuple () return %unit: $() }

slide-70
SLIDE 70

Method Lookup

entry(%c: $SomeClass): %foo = class_method %c: $SomeClass, #SomeClass.foo : $(SomeClass) -> () apply %foo(%c) : $(SomeClass) -> ()

slide-71
SLIDE 71

Method Lookup

entry(%c: $SomeClass): %foo = class_method %c: $SomeClass, #SomeClass.foo : $(SomeClass) -> () apply %foo(%c) : $(SomeClass) -> ()

slide-72
SLIDE 72

Method Lookup

entry(%c: $SomeClass): %foo = class_method %c: $SomeClass, #SomeClass.foo : $(SomeClass) -> () apply %foo(%c) : $(SomeClass) -> () sil_vtable SomeClass { #SomeClass.foo : @SomeClass_foo }

slide-73
SLIDE 73

Method Lookup

entry(%c: $SomeClass): %foo = class_method %c: $SomeClass, #SomeClass.foo : $(SomeClass) -> () apply %foo(%c) : $(SomeClass) -> () sil_vtable SomeClass { #SomeClass.foo : @SomeClass_foo } sil @SomeClass_foo : $(SomeClass) -> ()

slide-74
SLIDE 74

Method Lookup

entry(%c: $SomeClass): %foo = function_ref @SomeClass_foo : $(SomeClass) -> () apply %foo(%c) : $(SomeClass) -> ()

slide-75
SLIDE 75

Method Lookup

entry(%x: $T, %y: $T): %plus = witness_method $T, #Addable.+ : $<U: Addable> (U, U) -> U %z = apply %plus<T>(%x, %y) : $<U: Addable> (U, U) -> U

slide-76
SLIDE 76

Method Lookup

entry(%x: $T, %y: $T): %plus = witness_method $T, #Addable.+ : $<U: Addable> (U, U) -> U %z = apply %plus<T>(%x, %y) : $<U: Addable> (U, U) -> U

slide-77
SLIDE 77

Method Lookup

entry(%x: $T, %y: $T): %plus = witness_method $T, #Addable.+ : $<U: Addable> (U, U) -> U %z = apply %plus<T>(%x, %y) : $<U: Addable> (U, U) -> U sil_witness_table Int: Addable { #Addable.+ : @Int_plus }

slide-78
SLIDE 78

Method Lookup

entry(%x: $T, %y: $T): %plus = witness_method $T, #Addable.+ : $<U: Addable> (U, U) -> U %z = apply %plus<T>(%x, %y) : $<U: Addable> (U, U) -> U sil_witness_table Int: Addable { #Addable.+ : @Int_plus } sil @Int_plus : $(Int, Int) -> Int

slide-79
SLIDE 79

Method Lookup

entry(%x: $Int, %y: $Int): %plus = function_ref @Int_plus : $(Int, Int) -> Int %z = apply %plus(%x, %y) : $(Int, Int) -> Int

slide-80
SLIDE 80

Memory Allocation

slide-81
SLIDE 81

Memory Allocation

%stack = alloc_stack $Int

slide-82
SLIDE 82

Memory Allocation

%stack = alloc_stack $Int store %x to %stack: $*Int %y = load %stack: $*Int

slide-83
SLIDE 83

Memory Allocation

%stack = alloc_stack $Int store %x to %stack: $*Int %y = load %stack: $*Int dealloc_stack %stack: $*Int

slide-84
SLIDE 84

Memory Allocation

%stack = alloc_stack $Int store %x to %stack: $*Int %y = load %stack: $*Int dealloc_stack %stack: $*Int %box = alloc_box $Int

slide-85
SLIDE 85

Memory Allocation

%stack = alloc_stack $Int store %x to %stack: $*Int %y = load %stack: $*Int dealloc_stack %stack: $*Int %box = alloc_box $Int %object = alloc_ref $SomeClass

slide-86
SLIDE 86

Memory Allocation

%stack = alloc_stack $Int store %x to %stack: $*Int %y = load %stack: $*Int dealloc_stack %stack: $*Int %box = alloc_box $Int %object = alloc_ref $SomeClass strong_retain %object : $SomeClass strong_release %object : $SomeClass

slide-87
SLIDE 87

Control Flow

slide-88
SLIDE 88

Control Flow

br loop cond_br %flag: $Builtin.Int1, yes, no return %x: $Int unreachable

slide-89
SLIDE 89

Control Flow

br loop cond_br %flag: $Builtin.Int1, yes, no return %x: $Int unreachable switch_enum %e: $Optional<Int>, case #Optional.Some: some, case #Optional.None: none some(%x: $Int):

slide-90
SLIDE 90

Control Flow

br loop cond_br %flag: $Builtin.Int1, yes, no return %x: $Int unreachable switch_enum %e: $Optional<Int>, case #Optional.Some: some, case #Optional.None: none some(%x: $Int): checked_cast_br %c: $BaseClass, $DerivedClass, success, failure success(%d: $DerivedClass):

slide-91
SLIDE 91

Program Failure

slide-92
SLIDE 92

Program Failure

%result = builtin "sadd_with_overflow_Int64" (%x : $Builtin.Int64, %y : $Builtin.Int64) : $(Builtin.Int64, Builtin.Int1) %overflow = tuple_extract %result, 1

slide-93
SLIDE 93

Program Failure

%result = builtin "sadd_with_overflow_Int64" (%x : $Builtin.Int64, %y : $Builtin.Int64) : $(Builtin.Int64, Builtin.Int1) %overflow = tuple_extract %result, 1 cond_br %overflow : $Builtin.Int1, fail, cont cont: %z = tuple_extract %result, 0 /* ... */ fail: builtin "int_trap"() unreachable

slide-94
SLIDE 94

Program Failure

%result = builtin "sadd_with_overflow_Int64" (%x : $Builtin.Int64, %y : $Builtin.Int64) : $(Builtin.Int64, Builtin.Int1) %overflow = tuple_extract %result, 1 %z = tuple_extract %result, 0

slide-95
SLIDE 95

Program Failure

%result = builtin "sadd_with_overflow_Int64" (%x : $Builtin.Int64, %y : $Builtin.Int64) : $(Builtin.Int64, Builtin.Int1) %overflow = tuple_extract %result, 1 %z = tuple_extract %result, 0 cond_fail %overflow : $Builtin.Int1

slide-96
SLIDE 96

Swift’s use of SIL

slide-97
SLIDE 97

Two Phases of SIL Passes

Early SIL: Data flow sensitive lowering SSA-based diagnostics “Guaranteed” optimizations Late SIL: Performance optimizations Serialization LLVM IRGen

AST

IRGen LLVM

IR *.o

SIL

Analysis SILGen Optimization

slide-98
SLIDE 98

Early SIL

Many individual passes:

slide-99
SLIDE 99

Early SIL

Mandatory inlining Capture promotion Box-to-stack promotion inout argument deshadowing Diagnose unreachable code Definitive initialization Guaranteed memory optimizations Constant folding / overflow diagnostics

Many individual passes:

slide-100
SLIDE 100

Early SIL

Mandatory inlining Capture promotion Box-to-stack promotion inout argument deshadowing Diagnose unreachable code Definitive initialization Guaranteed memory optimizations Constant folding / overflow diagnostics

Problems we’ll look at:

  • Diagnosing Overflow
  • Enabling natural closure semantics with memory safety
  • Removing requirement for default construction

Many individual passes:

slide-101
SLIDE 101

Diagnosing Overflow

Arithmetic overflow is guaranteed to trap in Swift Not undefined behavior Not 2’s complement (unless explicitly using &+ operator)

let v = Int8(127)+1

slide-102
SLIDE 102

Diagnosing Overflow

Arithmetic overflow is guaranteed to trap in Swift Not undefined behavior Not 2’s complement (unless explicitly using &+ operator) How can we statically diagnose overflow? … and produce a useful error message?

let v = Int8(127)+1

slide-103
SLIDE 103

Output of SILGen

let v = Int8(127)+1

%1 = integer_literal $Builtin.Int2048, 127 %2 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %4 = apply [transparent] %2(%1) : $(Builtin.Int2048) -> Int8 %9 = function_ref @“Swift.+” : $(Int8, Int8) -> Int8 %10 = apply [transparent] %0(%4, %8) : $(Int8, Int8) -> Int8 debug_value %10 : $Int8 // let v %5 = integer_literal $Builtin.Int2048, 1 %6 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %8 = apply [transparent] %6(%5) : $(Builtin.Int2048) -> Int8

slide-104
SLIDE 104

Output of SILGen

let v = Int8(127)+1

%1 = integer_literal $Builtin.Int2048, 127 %2 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %4 = apply [transparent] %2(%1) : $(Builtin.Int2048) -> Int8 %9 = function_ref @“Swift.+” : $(Int8, Int8) -> Int8 %10 = apply [transparent] %0(%4, %8) : $(Int8, Int8) -> Int8 debug_value %10 : $Int8 // let v %5 = integer_literal $Builtin.Int2048, 1 %6 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %8 = apply [transparent] %6(%5) : $(Builtin.Int2048) -> Int8

slide-105
SLIDE 105

Output of SILGen

let v = Int8(127)+1

%1 = integer_literal $Builtin.Int2048, 127 %2 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %4 = apply [transparent] %2(%1) : $(Builtin.Int2048) -> Int8 %9 = function_ref @“Swift.+” : $(Int8, Int8) -> Int8 %10 = apply [transparent] %0(%4, %8) : $(Int8, Int8) -> Int8 debug_value %10 : $Int8 // let v %5 = integer_literal $Builtin.Int2048, 1 %6 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %8 = apply [transparent] %6(%5) : $(Builtin.Int2048) -> Int8

slide-106
SLIDE 106

Output of SILGen

let v = Int8(127)+1

%1 = integer_literal $Builtin.Int2048, 127 %2 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %4 = apply [transparent] %2(%1) : $(Builtin.Int2048) -> Int8 %9 = function_ref @“Swift.+” : $(Int8, Int8) -> Int8 %10 = apply [transparent] %0(%4, %8) : $(Int8, Int8) -> Int8 debug_value %10 : $Int8 // let v %5 = integer_literal $Builtin.Int2048, 1 %6 = function_ref @“Swift.Int8.init" : $(Builtin.Int2048) -> Int8 %8 = apply [transparent] %6(%5) : $(Builtin.Int2048) -> Int8

slide-107
SLIDE 107

After mandatory inlining

let v = Int8(127)+1

slide-108
SLIDE 108

After mandatory inlining

%0 = integer_literal $Builtin.Int8, 127 %4 = integer_literal $Builtin.Int8, 1 %11 = builtin "sadd_with_overflow_Int8"(%0 : $Builtin.Int8, %4 : $Builtin.Int8) %12 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 1 cond_fail %12 : $Builtin.Int1 %13 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 0 %15 = struct $Int8 (%13 : $Builtin.Int8) debug_value %15 : $Int8 // let v

let v = Int8(127)+1

slide-109
SLIDE 109

After mandatory inlining

%0 = integer_literal $Builtin.Int8, 127 %4 = integer_literal $Builtin.Int8, 1 %11 = builtin "sadd_with_overflow_Int8"(%0 : $Builtin.Int8, %4 : $Builtin.Int8) %12 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 1 cond_fail %12 : $Builtin.Int1 %13 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 0 %15 = struct $Int8 (%13 : $Builtin.Int8) debug_value %15 : $Int8 // let v

let v = Int8(127)+1

slide-110
SLIDE 110

After mandatory inlining

%0 = integer_literal $Builtin.Int8, 127 %4 = integer_literal $Builtin.Int8, 1 %11 = builtin "sadd_with_overflow_Int8"(%0 : $Builtin.Int8, %4 : $Builtin.Int8) %12 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 1 cond_fail %12 : $Builtin.Int1 %13 = tuple_extract %11 : $(Builtin.Int8, Builtin.Int1), 0 %15 = struct $Int8 (%13 : $Builtin.Int8) debug_value %15 : $Int8 // let v

let v = Int8(127)+1

slide-111
SLIDE 111

Diagnostic constant folding

%0 = integer_literal $Builtin.Int8, -128 %1 = integer_literal $Builtin.Int1, -1 %2 = tuple (%0 : $Builtin.Int8, %1 : $Builtin.Int1) // folded “sadd_overflow” cond_fail %1 : $Builtin.Int1 // unconditional failure

let v = Int8(127)+1

slide-112
SLIDE 112

Diagnostic constant folding

%0 = integer_literal $Builtin.Int8, -128 %1 = integer_literal $Builtin.Int1, -1 %2 = tuple (%0 : $Builtin.Int8, %1 : $Builtin.Int1) // folded “sadd_overflow” cond_fail %1 : $Builtin.Int1 // unconditional failure

let v = Int8(127)+1

slide-113
SLIDE 113

Diagnostic constant folding

Each SIL instruction maintains full location information:

  • Pointer back to AST node it came from
  • Including SIL inlining information

t.swift:2:20: error: arithmetic operation '127 + 1' (on type 'Int8') results in an overflow let v = Int8(127) + 1 ~~~~~~~~~ ^ ~

%0 = integer_literal $Builtin.Int8, -128 %1 = integer_literal $Builtin.Int1, -1 %2 = tuple (%0 : $Builtin.Int8, %1 : $Builtin.Int1) // folded “sadd_overflow” cond_fail %1 : $Builtin.Int1 // unconditional failure

let v = Int8(127)+1

slide-114
SLIDE 114

Local variable optimization

Memory safety with closures provides challenges:

  • Closures can capture references to local variables
  • Closure lifetime is not limited to a stack discipline

func doSomething() -> Int { var x = 1 takeClosure { x = 2 } return x }

slide-115
SLIDE 115

Local variable optimization

Memory safety with closures provides challenges:

  • Closures can capture references to local variables
  • Closure lifetime is not limited to a stack discipline

func doSomething() -> Int { var x = 1 takeClosure { x = 2 } return x }

Solution: Semantic model is for all stack variables to be on the heap Code … x Closure

slide-116
SLIDE 116

Local variables after SILGen

SILGen emits all local ‘var’iables as heap boxes with alloc_box

func f() -> Int { var x = 42 return x }

slide-117
SLIDE 117

Local variables after SILGen

SILGen emits all local ‘var’iables as heap boxes with alloc_box

func f() -> Int { var x = 42 return x } %0 = alloc_box $Int // var x %4 = ... store %4 to %0#1 : $*Int

slide-118
SLIDE 118

Local variables after SILGen

SILGen emits all local ‘var’iables as heap boxes with alloc_box

func f() -> Int { var x = 42 return x } %0 = alloc_box $Int // var x %4 = ... store %4 to %0#1 : $*Int %6 = load %0#1 : $*Int strong_release %0#0 return %6 : $Int

slide-119
SLIDE 119

Local variables after SILGen

SILGen emits all local ‘var’iables as heap boxes with alloc_box

func f() -> Int { var x = 42 return x } %0 = alloc_box $Int // var x %4 = ... store %4 to %0#1 : $*Int

Box-to-stack promotes heap boxes to stack allocations All closure captures are by reference

  • Not acceptable to leave them on the heap!

%6 = load %0#1 : $*Int strong_release %0#0 return %6 : $Int

slide-120
SLIDE 120

Promotion eliminates byref capture

Safe to promote to by-value capture in many cases: … e.g. when no mutations happen after closure formation This enables the captured value to be promoted to the stack/registers

slide-121
SLIDE 121

Promotion eliminates byref capture

Safe to promote to by-value capture in many cases: … e.g. when no mutations happen after closure formation This enables the captured value to be promoted to the stack/registers

var x = … x += 42 arr1 = arr2.map { elt in elt+x }

slide-122
SLIDE 122

Promotion eliminates byref capture

Safe to promote to by-value capture in many cases: … e.g. when no mutations happen after closure formation This enables the captured value to be promoted to the stack/registers

var x = … x += 42 arr1 = arr2.map { elt in elt+x } var x = … x += 42 let x2 = x arr1 = arr2.map { elt in elt+x2 }

slide-123
SLIDE 123

Naive SIL for by-ref closure capture

arr = arr.map { elt in elt+x }

slide-124
SLIDE 124

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

slide-125
SLIDE 125

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

%11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-126
SLIDE 126

%9 = function_ref @“closure1” : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int %10 = partial_apply %9(%2#0, %2#1) : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

%11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-127
SLIDE 127

%9 = function_ref @“closure1” : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int %10 = partial_apply %9(%2#0, %2#1) : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

%11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-128
SLIDE 128

%9 = function_ref @“closure1” : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int %10 = partial_apply %9(%2#0, %2#1) : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

sil @“closure1” { bb0(%0 : $Int, %1 : $Builtin.NativeObject, %2 : $*Int): debug_value %0 : $Int // let elt %4 = load %2 : $*Int ... %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-129
SLIDE 129

%9 = function_ref @“closure1” : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int %10 = partial_apply %9(%2#0, %2#1) : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

sil @“closure1” { bb0(%0 : $Int, %1 : $Builtin.NativeObject, %2 : $*Int): debug_value %0 : $Int // let elt %4 = load %2 : $*Int ... %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-130
SLIDE 130

%9 = function_ref @“closure1” : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int %10 = partial_apply %9(%2#0, %2#1) : $(Int, @owned Builtin.NativeObject, @inout Int) -> Int

Naive SIL for by-ref closure capture

%2 = alloc_box $Int // var x

arr = arr.map { elt in elt+x }

sil @“closure1” { bb0(%0 : $Int, %1 : $Builtin.NativeObject, %2 : $*Int): debug_value %0 : $Int // let elt %4 = load %2 : $*Int ... %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

slide-131
SLIDE 131

SIL after capture promotion

%2 = alloc_box $Int // var x %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

arr = arr.map { elt in elt+x }

slide-132
SLIDE 132

SIL after capture promotion

%4 = load %2#1 : $*Int %7 = function_ref @“closure1” : $(Int, Int) -> Int %10 = partial_apply %7(%4) : $(Int, Int) -> Int %2 = alloc_box $Int // var x %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

arr = arr.map { elt in elt+x }

slide-133
SLIDE 133

SIL after capture promotion

%4 = load %2#1 : $*Int %7 = function_ref @“closure1” : $(Int, Int) -> Int %10 = partial_apply %7(%4) : $(Int, Int) -> Int %2 = alloc_box $Int // var x %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array

arr = arr.map { elt in elt+x }

slide-134
SLIDE 134

SIL after capture promotion

%4 = load %2#1 : $*Int %7 = function_ref @“closure1” : $(Int, Int) -> Int %10 = partial_apply %7(%4) : $(Int, Int) -> Int %2 = alloc_box $Int // var x %11 = function_ref @“Array.map” : $((Int) -> Int, Array) -> Array %12 = apply %11(%10, %0) : $((Int) -> Int, Array) -> Array sil @“closure1” { bb0(%0 : $Int, %1 : $Int): debug_value %0 : $Int // let elt debug_value %1 : $Int // var x ...

arr = arr.map { elt in elt+x }

slide-135
SLIDE 135

Definitive Initialization

Problem: Not all values can be default initialized

func testDI(cond : Bool) { var v : SomeClass if cond { v = SomeClass(1234) } v.foo() } else { v = SomeClass(4321) }

slide-136
SLIDE 136

Definitive Initialization

Problem: Not all values can be default initialized

func testDI(cond : Bool) { var v : SomeClass if cond { v = SomeClass(1234) } v.foo() } else { v = SomeClass(4321) }

Desires: Don’t want magic numbers for primitive types Want to allow flexible initialization patterns

slide-137
SLIDE 137

Definitive Initialization

Problem: Not all values can be default initialized

func testDI(cond : Bool) { var v : SomeClass if cond { v = SomeClass(1234) } v.foo() } else { v = SomeClass(4321) }

Solution: Dataflow driven liveness analysis Desires: Don’t want magic numbers for primitive types Want to allow flexible initialization patterns

slide-138
SLIDE 138

Definitive Initialization

Problem: Not all values can be default initialized

func testDI(cond : Bool) { var v : SomeClass if cond { v = SomeClass(1234) } v.foo() }

error: 'v' used before being initialized

Solution: Dataflow driven liveness analysis Desires: Don’t want magic numbers for primitive types Want to allow flexible initialization patterns

slide-139
SLIDE 139

Definitive Initialization Algorithm

Check each use of value to determine: Guaranteed initialized Guaranteed uninitialized Initialized only on some paths

struct Pair { var a, b : Int init() { a = 42 } }

slide-140
SLIDE 140

Definitive Initialization Algorithm

Check each use of value to determine: Guaranteed initialized Guaranteed uninitialized Initialized only on some paths

struct Pair { var a, b : Int init() { a = 42 } }

error: return from initializer without initializing all stored properties note: 'self.b' not initialized

Diagnostics must be great

slide-141
SLIDE 141

DI covers many similar cases

func test() -> Float { var local : (Int, Float) local.0 = 42 return local.1 } class Base { init(x : Int) {} } class Derived : Base { var x, y : Int init() { x = 42; y = 1 } }

slide-142
SLIDE 142

DI covers many similar cases

func test() -> Float { var local : (Int, Float) local.0 = 42 return local.1 } class Base { init(x : Int) {} } class Derived : Base { var x, y : Int init() { x = 42; y = 1 } }

error: 'local.1' used before being initialized error: super.init isn't called before returning from initializer

slide-143
SLIDE 143

DI Lowering: Initialization vs Assignment

Semantics depend on data flow properties First assignment is initialization:

  • Raw memory → Valid value

Subsequent assignments are replacements:

  • Valid value → Valid value

x = y

slide-144
SLIDE 144

DI Lowering: Initialization vs Assignment

Semantics depend on data flow properties First assignment is initialization:

  • Raw memory → Valid value

Subsequent assignments are replacements:

  • Valid value → Valid value

x = y

strong_retain %y : $C store %y to %x : $*C

slide-145
SLIDE 145

DI Lowering: Initialization vs Assignment

Semantics depend on data flow properties First assignment is initialization:

  • Raw memory → Valid value

Subsequent assignments are replacements:

  • Valid value → Valid value

x = y

strong_retain %y : $C store %y to %x : $*C strong_retain %y : $C %tmp = load %x : $*C strong_release %tmp : $C store %y to %x : $*C

slide-146
SLIDE 146

Conditional Liveness

Inherently a dataflow problem Requires dynamic logic in some cases Conditional destruction too

func testDI(cond : Bool) { var c : SomeClass c = SomeClass(4321) // init or assign? c.foo() }

slide-147
SLIDE 147

Conditional Liveness

Inherently a dataflow problem Requires dynamic logic in some cases Conditional destruction too

func testDI(cond : Bool) { var c : SomeClass c = SomeClass(4321) // init or assign? c.foo() } if cond { c = SomeClass(1234) }

slide-148
SLIDE 148

Language-Specific IR: Retrospective

slide-149
SLIDE 149

Diagnostics

Clear improvement over Clang CFG for data flow diagnostics:

  • Diagnostics always up to date as language evolves
  • Great location information, source level type information
  • DCE before diagnostics eliminates “false” positives

IMHO Clang should pull clang::CFG (or something better) into its IRGen path

slide-150
SLIDE 150

Lowering

Nice separation between SILGen and IRGen:

  • SILGen handles operational lowering
  • IRGen handles type lowering & concretization of the target
slide-151
SLIDE 151

Lowering

Nice separation between SILGen and IRGen:

  • SILGen handles operational lowering
  • IRGen handles type lowering & concretization of the target

Dataflow Lowering:

  • Great way to handle things like swift assignment vs initialization
  • Can be emulated by generating LLVM intrinsics and lowering on IR
slide-152
SLIDE 152

Performance Optimizations

Necessary for generics specialization:

Requires full source level type system Specialization produces extreme changes to generated IR

slide-153
SLIDE 153

Performance Optimizations

Necessary for generics specialization:

Requires full source level type system Specialization produces extreme changes to generated IR

Less clear for other optimizations

ARC Optimization, devirt, etc could all be done on IR (with tradeoffs)

slide-154
SLIDE 154

Performance Optimizations

Necessary for generics specialization:

Requires full source level type system Specialization produces extreme changes to generated IR

Required a ton of infrastructure:

SILCombine Passmanager for analyses …

Less clear for other optimizations

ARC Optimization, devirt, etc could all be done on IR (with tradeoffs)

slide-155
SLIDE 155

Summary

SIL was a lot of work, but necessary given the scope of Swift May make sense (or not) based on your language We’re pretty happy with it… …but there is still a ton of work left to do Know LLVM and use it for what it is good for … don’t reinvent everything just for fun :-)