Slide 1

Slide 1 text

mayHaveSideEffects Θ͍Θ͍swi$c #20 22nd/May/2020 @___freddi___ 1

Slide 2

Slide 2 text

ຊ೔ͷ࿩ͷྲྀΕɾझࢫ • Θ͍Θ͍swi%c ൪֎ฤϫʔΫγϣοϓ #3 - ෱Ԭ ͷ෮श • ʮ෭࡞༻͕͋Γͦ͏ʯͳίʔυΛίϯύΠϥ͕ݟ͚ͭํΛ஌Δ 2

Slide 3

Slide 3 text

Θ͍Θ͍swi$c ൪֎ฤϫʔΫγϣοϓ #3 - ෱Ԭ • ͝ࢀՃ͋Γ͕ͱ͏͍͟͝·ͨ͠ • ΈΜͳҰੜݒ໋ղ͍͍ͯͯخ͍͠Ͱ͢ • C++ ͷ෦෼͕೉͔ͬͨ͠ͷͰղ͚ͳͯ͘΋౰વͰ͢ 3

Slide 4

Slide 4 text

Θ͍Θ͍swi$c ൪֎ฤϫʔΫγϣοϓ #3 - ෱Ԭ • ࢿྉ͸શͯઌिެ։͠·ͨ͠ • ͨͩ͠ϫʔΫγϣοϓͷϨϙδτϦ͸ফ͍ͯ͠·͢ • h#ps:/ /qiita.com/freddi_/items/aa604dd68697f823a41d 4

Slide 5

Slide 5 text

ͪΐͬͱ෮श ~ SILͱ͸ • Swi%ͷதؒදݱݴޠ (Swi% Intermediate Language) • Swi%ͷίʔυΛίϯύΠϧ͍ͯ͠Δ్தͰݱΕΔ • ࠷దԽ͸SILͷίʔυͰߦΘΕΔ • ࠷దԽલͷSIL͕raw SILɺޙ͕canonical SIL 5

Slide 6

Slide 6 text

ͪΐͬͱ෮श ~ SILʢྫʣ SILʹม׵લ var a = 10 6

Slide 7

Slide 7 text

ͪΐͬͱ෮श ~ SILʢྫʣ SILʹม׵ޙ sil_stage canonical import Builtin import Swift import SwiftShims @_hasStorage @_hasInitialValue var a: Int { get set } // a sil_global hidden @$s5swift1aSivp : $Int // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>): alloc_global @$s5swift1aSivp // id: %2 %3 = global_addr @$s5swift1aSivp : $*Int // user: %6 %4 = integer_literal $Builtin.Int64, 10 // user: %5 %5 = struct $Int (%4 : $Builtin.Int64) // user: %6 store %5 to %3 : $*Int // id: %6 %7 = integer_literal $Builtin.Int32, 0 // user: %8 %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9 return %8 : $Int32 // id: %9 } // end sil function 'main' // Int.init(_builtinIntegerLiteral:) sil public_external [transparent] [serialized] @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int { // %0 // user: %2 bb0(%0 : $Builtin.IntLiteral, %1 : $@thin Int.Type): %2 = builtin "s_to_s_checked_trunc_IntLiteral_Int64"(%0 : $Builtin.IntLiteral) : $(Builtin.Int64, Builtin.Int1) // user: %3 %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4 %4 = struct $Int (%3 : $Builtin.Int64) // user: %5 return %4 : $Int // id: %5 } // end sil function '$sSi22_builtinIntegerLiteralSiBI_tcfC' 7

Slide 8

Slide 8 text

ͪΐͬͱ෮श ~ SILίʔυͷߏ଄ • SILͷߏ଄͸ҎԼͷͬ͟ͱҎԼͷ௨Γʢςʔϒϧͱ͔Ұ෦লུʣ • SIL Module (ιʔεϑΝΠϧશମ) • ͷதʹɺSIL Func.on (Swi*Ͱ࡞ͬͨؔ਺͔Βੜ੒) • ͷதʹɺSIL Basic Block (৚݅෼ذͷείʔϓͱ͔) • ͷதʹɺSIL Instruc.on (ίʔυͷҰߦҰߦ) 8

Slide 9

Slide 9 text

ͪΐͬͱ෮श ~ SILίʔυͷߏ଄ʢྫʣ // ϑΝΠϧશମ͕SIL Module sil_stage canonical import Builtin ... // SIL Function // main sil @main : $@convention(c) (Int32, ...) -> Int32 { // bb ͔Β࢝·Δ SIL Basic Block bb0(%0 : $Int32, %1 : $UnsafeMutablePointer

Slide 10

Slide 10 text

ͪΐͬͱ෮श ~ SILOp(mizer • SIL Λ࠷దԽ͢ΔίϯύΠϥͷϞδϡʔϧ • ͦΕͧΕͷ࠷దԽͷ໾ׂΛ࣋ͭ Pass ͱ͍͏Ϟδϡʔϧ͔ΒͳΔ • Module શମΛݟΔ Pass ͱ Func2on ΛݟΔ Pass ͕͋Δ 10

Slide 11

Slide 11 text

Dead Code Elimina-on • ࢖ΘΕ͍ͯͳ͍ίʔυΛ࡟আ͢Δ࠷దԽPass • ࢖ΘΕ͍ͯͳ͍ίʔυ == "ࢮΜͰΔίʔυ" == Dead Code • େମͷ৔໘Ͱ DCE ͱུ͞Ε͍ͯΔ • ίϯύΠϥͷίʔυͱ͔ • ϫʔΫγϣοϓͷ՝୊Ͱ࡞Β͞Εͨ Pass • ϫʔΫγϣοϓͷ͸࡟আͷ৚͕݅ݫ͘͠ͳ͍minDCE 11

Slide 12

Slide 12 text

Dead Code Elimina-onͷ࢓૊Έ 1. ࡟আ͠ͳ͍৚݅ʹ౰ͯ͸·Δ Instruc+on Λ Live Insturc-on ͱ ͯ͠Mark 2. Mark͞Ε͍ͯͳ͍ Insturc+on Λ Func+on ͔ΒऔΓআ͘ 3. Func+on ͔Β͍Βͳ͍ίʔυ͕ফ͍͑ͯΔ 4. (ƅдƅ)řŵŖ 12

Slide 13

Slide 13 text

Dead Code Elimina-onͷ࢓૊Έ • ຊՈͰͷ Live Insturc.on ͷ͍͍ͩͨͷ৚݅ • ⭕ return ΍ noreturn ͱ͍ͬͨ Insturc.onɻ • ϫʔΫγϣοϓͰ͸ return ͷΈ • ❌ ෭࡞༻ͷ͋ΔՄೳੑ͕͋Δ Insturc.on • ⭕ Live Instruc.on ʹґଘ͍ͯ͠Δ Instruc.on • ❌ Live Insturc.on ͕ ੍ޚґଘ ͍ͯ͠Δ৚݅෼ذ • ෳ਺ͷ Basic Block ͷؒͷ૸ࠪ • ϫʔΫγϣοϓͰ࣮૷ͨ͠ minDCE ͸ ⭕ ͕͍ͭͨ৚͚݅ͩΛར༻ 13

Slide 14

Slide 14 text

Dead Code Elimina-onͷ࢓૊Έ ༨ஊ • ❌ Live Insturc.on ੍͕ޚґଘ͍ͯ͠Δ৚݅෼ذ • ෳ਺ͷ Basic Block ͷؒͷ૸ࠪ • h5ps:/ /blog.wa=.me/2018/02/19/swi=-sil-5/ ΛಡΜͰͶ 14

Slide 15

Slide 15 text

return ΍ noreturn ͱ͍ͬͨ Insturc+on • Basic Block ͷϧʔϧ • Terminator ͱݺ͹ΕΔ Instruc*on ͰऴΘΒͳ͍ͱ͍͚ͳ͍ • return • noreturn • unreachable (FatalErrorͷ͋ͱʹ෇͍ͨΓ͢Δ) • throw ... 15

Slide 16

Slide 16 text

ϫʔΫγϣοϓͰreturn͞Εͨ஋Λusefulͱͯ͠ѻͬͨ ཧ༝ • ྫ͑͹ԼͷSILίʔυ store %5 to %3 : $*Int // id: %6 %7 = integer_literal $Builtin.Int32, 0 // user: %8 %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9 return %8 : $Int32 // id: %9 16

Slide 17

Slide 17 text

ϫʔΫγϣοϓͰreturn͞Εͨ஋Λusefulͱͯ͠ѻͬͨ ཧ༝ • ͜ͷίʔυ͸ ࠷ޙʹ return %8 ͯ͠Δʢid: %9ʣ • id: %9 ͸ return (Terminator) => Live • %8 ͷ஋Λੜ੒͍ͯ͠Δ Instruc,on ͸ ґଘ ͍ͯ͠Δ => Live • %8 Λฦ͍ͯ͠Δ Instruc,on ͸ id: %8 ͷ Inscruc,onͱݺͿ • id: %8 ͕ར༻͍ͯ͠Δ %7ʢid: %7ʣ΋ґଘ => Live • %7 ͔Β΋ɹґଘΛҶͮΔࣜʹ૸ࠪͯ͠ɹLive ͱͯ͠Markͱ͍͚ͳ͍ 17

Slide 18

Slide 18 text

ϫʔΫγϣοϓͰreturn͞Εͨ஋Λusefulͱͯ͠ѻͬͨ ཧ༝ • ྫ͑͹ԼͷSILίʔυ͸ id: %6 ͕ফ͑Δ͔΋͠Εͳ͍ • return ʹґଘ͍ͯ͠ͳ͍Instruc,on • %7, %8, %9 ʹґଘ͍ͯ͠ͳ͍͔Β store %5 to %3 : $*Int // id: %6 %7 = integer_literal $Builtin.Int32, 0 // user: %8 %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9 return %8 : $Int32 // id: %9 18

Slide 19

Slide 19 text

ϫʔΫγϣοϓͰreturn͞Εͨ஋Λusefulͱͯ͠ѻͬͨ ཧ༝ • minDCE ʹ͸໰୊఺ • noreturn ͷ࣌ͱ͔ແ৚݅Ͱ͢΂ͯͷίʔυΛফ͢ͷͰ͸ • ͦͷ࣌͸ Instruc/onʹ ෭࡞༻͕͋Δ͔Ͳ͏͔ Λௐ΂Δ • ϫʔΫγϣοϓͰ͸͕࣌ؒ଍ΓΜͷͰলུ • ࠓ೔ͷ࿩୊͸ίϨ • ʮInstruc/onʹ ෭࡞༻͕͋Δ͔Ͳ͏͔ Λௐ΂Δʯؔ਺ͷ࿩ 19

Slide 20

Slide 20 text

seemsUseful • ҎԼ͸ɺຊՈ DCE ʹ͋Δ seemsUseful ͱ͍͏ؔ਺ͷҰ෦ • ໊લͷ௨ΓʮUsefulͳInstruc1onʯ͔Λ true/false Ͱฦ͢ • Live ͱ Useful ͸ಉ͡ҙຯͱࢥͬͯྑ͍ • ઌ΄Ͳ঺հͨ͠ Terminator ͷνΣοΫ΋͜͜Ͱ൑அͯ͠Δ • Instruc1on ͷґଘͷ૸ࠪ͸·ͨผͷ৔ॴ static bool seemsUseful(SILInstruction *I) { if (I->mayHaveSideEffects()) return true; ... 20

Slide 21

Slide 21 text

seemsUseful • Func&on தͷ Instruc&on Λ Mark ͢Δ markLive ؔ਺Ͱར༻ • seemUseful ͷ݁Ռ͕ trueͳΒ͹ • Instruction Λ markValueLive͢Δ • ݫີʹ͸ Insctruction தͷ SIL Value ΛMark 21

Slide 22

Slide 22 text

mayHaveSideEffects • seemsUseful ͰUseful͔Ͳ͏͔ͷ൑அʹ࢖ΘΕ͍ͯΔؔ਺ • ͜ΕͰʮ෭࡞༻͕͋Δ͔Ͳ͏͔ʯΛ൑அͯ͠ΔΒ͍͠ • ৚݅ʹΑͬͯ͸෭࡞༻Λൃੜ͠ͳ͍͜ͱ΋͋ΔͷͰ may 22

Slide 23

Slide 23 text

mayHaveSideEffects ΁ͷٙ໰ • mayHaveSideEffects ͸ɺ ԿΛʮ෭࡞༻ʯͱ͍ͯ͠Δͷ͔ʁ • ಺෦࣮૷Λݟ͔ͯ֬ΊͯΈ͍ͨ 23

Slide 24

Slide 24 text

mayHaveSideEffects ͷ಺෦࣮૷ • ͜Μͳ͔Μ͡ bool SILInstruction::mayHaveSideEffects() const { // If this instruction traps then it must have side effects. if (mayTrap()) return true; MemoryBehavior B = getMemoryBehavior(); return B == MemoryBehavior::MayWrite || B == MemoryBehavior::MayReadWrite || B == MemoryBehavior::MayHaveSideEffects; } 24

Slide 25

Slide 25 text

mayHaveSideEffects ͷ಺෦࣮૷ͷݟΔϙΠϯτʁ • ͜ͷ̎ͭΛϑΥʔΧε͢Ε͹ɺṖΛղ໌Ͱ͖ͦ͏ʂ • mayTrap • MemoryBehavior 25

Slide 26

Slide 26 text

mayTrapʁ • / / If this instruc.on traps then it must have side effects. • ௚༁: ͜ͷ໋ྩ͕τϥοϓ͢Δ৔߹ɺ෭࡞༻͕͋Δ͸ͣ • trap == ϓϩάϥϜͷҟৗऴྃ 26

Slide 27

Slide 27 text

mayTrap ͷ಺෦࣮૷ΛͱΓ͋͑ͣ೷͘ • ׂͱγϯϓϧͰ΍͍͞͠ bool SILInstruction::mayTrap() const { switch(getKind()) { case SILInstructionKind::CondFailInst: case SILInstructionKind::UnconditionalCheckedCastInst: case SILInstructionKind::UnconditionalCheckedCastAddrInst: return true; default: return false; } } 27

Slide 28

Slide 28 text

dyn_cast ΍ isa • PassͰ͸ɺίʔυதͰݟΔInsctruc+on͸SILInstructionܕ • ۩ମతͳInstruc+onͷछྨΛ஌Δʹ͸ʁ • dyn_cast ΍ isa (Ωϟετ)Λ࢖ͬͯ֬ೝͰ͖Δ 28

Slide 29

Slide 29 text

dyn_cast ΍ isa αϯϓϧίʔυ • ࠓݟ͍ͯΔInstruc)on͕Interger Literal Instruc)on͔ݟ͍ͯΔ // Inst͕ Interger Literal Instruction͔͠Β΂Δ // $0 = integer_literal $Builtin.Int64, 100 if (auto *BI = dyn_cast(Inst)) { 29

Slide 30

Slide 30 text

dyn_cast ΍ isa • ͦΕͧΕͷInstruc)on͸ɺSILInstruction ܕΛܧঝͨ͠ܕͰ ද͞ΕΔ • SILInstruction ܕ͸ɺίϯύΠϥ্Ͱ SIL Instruc)on Λද ݱ͍ͯ͠Δ • ໨తͷInstruc)onܕ΁ͷμ΢ϯΩϟετ͕੒ޭ͢Ε͹ྑ͍ 30

Slide 31

Slide 31 text

getKind • getKind ͸ΩϟετΛ࢖Θͳ͍ํ๏ • enumΛฦ͍ͯͯ͠ɺswitchͱ͔࢖͑ͯྑ͍ • SILInstructionKind ͸ɺenumͷ஋ͰInstruction͕Ͳͷछ ྨͷ΋ͷ͔Λද͢ switch(I->getKind()) { case SILInstructionKind::IntegerLiteralInst: ... case SILInstructionKind:: ReturnLiteralInst: ... 31

Slide 32

Slide 32 text

mayTrap ͷ಺෦࣮૷ΛͱΓ͋͑ͣ೷͘ (࠶ܝ) bool SILInstruction::mayTrap() const { switch(getKind()) { case SILInstructionKind::CondFailInst: case SILInstructionKind::UnconditionalCheckedCastInst: case SILInstructionKind::UnconditionalCheckedCastAddrInst: return true; default: return false; } } 32

Slide 33

Slide 33 text

ͭ·Γʁ • ͜ͷ3ͭͷInstruc*on͸trapͳͷͰUsefulʹͳ͍ͬͯΔ • CondFailInst • UnconditionalCheckedCastInst • UnconditionalCheckedCastAddrInst 33

Slide 34

Slide 34 text

Θ͔Βͳ͍͜ͱ͸SIL.rstͰௐ΂Α͏ • SIL.rstʹ͸͍͍ͩͨͷInstruc,onͷ৘ใ͕৐͍ͬͯ·͢ 34

Slide 35

Slide 35 text

SIL.rstͰInstruc+onΛௐ΂Δίπ • ΩϟϜϧέʔεʹͳͬͯΔͷΛεωʔΫέʔεʹ෼ղͯ͠Ctrl+F • cond_fail • unconditional_checked_cast • unconditional_checked_cast_addr • શ෦ώοτ͠·͢ 35

Slide 36

Slide 36 text

cond_fail // BNF sil-instruction ::= 'cond_fail' sil-operand, string-literal • ΋͠Φϖϥϯυ͕ 1 ͷͱ͖Run'me Failure • Φϖϥϯυ == Ҿ਺ͱߟ͑ΔͱΘ͔Γ΍͍͢ • sil-operand ͕Φϖϥϯυ cond_fail %0 : $Builtin.Int1, "failure reason" // %0 must be of type $Builtin.Int1 • h#ps:/ /github.com/apple/swi5/blob/master/docs/SIL.rst#cond_fail 36

Slide 37

Slide 37 text

unconditional_checked_cast // BNF sil-instruction ::= 'unconditional_checked_cast' sil-operand 'to' sil-type • ΩϟετͰ͖Δ͔Λ͔֬ΊΔ • ΦϖϥϯυͷΦϒδΣΫτ͕ΩϟετෆՄͷ৔߹Run%me Failure • as! ͷΑ͏ͳ΋ͷ %1 = unconditional_checked_cast %0 : $A to $B %1 = unconditional_checked_cast %0 : $*A to $*B // $A and $B must be both objects or both addresses // %1 will be of type $B or $*B • h#ps:/ /github.com/apple/swi5/blob/master/docs/SIL.rst#uncondi=onal_checked_cast 37

Slide 38

Slide 38 text

unconditional_checked_cast_addr // BNF sil-instruction ::= 'unconditional_checked_cast_addr' sil-type 'in' sil-operand 'to' sil-type 'in' sil-operand • Ωϟετ • ࠷ॳͷΦϖϥϯυ͕ɺ࠷ޙͷΦϖϥϯυͷܕʹ͕ΩϟετෆՄ ͷ৔߹Run%me Failure 38

Slide 39

Slide 39 text

unconditional_checked_cast_addr • unconditional_checked_cast ͱಉ͡Α͏ʹݟ͑Δ͚Ͳ • Ͳ͏΍ΒΩϟετݩͷΦϒδΣΫτ͸ഁغ͞ΕΔΒ͍͠ unconditional_checked_cast_addr $A in %0 : $*@thick A to $B in $*@thick B // $A and $B must be both addresses // %1 will be of type $*B // $A is destroyed during the conversion. There is no implicit copy. • h#ps:/ /github.com/apple/swi5/blob/master/docs/ SIL.rst#uncondi=onal_checked_cast • h#ps:/ /it1.jp/?p=1231 ͕Θ͔Γ΍͍͔͢΋ 39

Slide 40

Slide 40 text

unconditional_checked_cast_value ͸Ͳ͏ͳ ͷʁ • mayTrap ʹೖͬͯͳ͍ unconditional_checked ܥ౷ͷ Instruc*on • ೖΕ๨Ε͔ɺͦ͏Ͱͳ͍͔ෆ໌ • Run*me Failure ͢Δͷʹ .... 40

Slide 41

Slide 41 text

mayHaveSideEffects ͷ಺෦࣮૷ʢ࠶ܝʣ bool SILInstruction::mayHaveSideEffects() const { // If this instruction traps then it must have side effects. if (mayTrap()) return true; MemoryBehavior B = getMemoryBehavior(); return B == MemoryBehavior::MayWrite || B == MemoryBehavior::MayReadWrite || B == MemoryBehavior::MayHaveSideEffects; } • ͭ·ΓɺϓϩάϥϜ͕৚݅ʹΑͬͯ͸ҟৗऴྃ͢ΔͳΒʮ෭࡞༻͋Γʯͱଊ͑Δ • ͭ͗͸ Memory Behavior! 41

Slide 42

Slide 42 text

getMemoryBehavior ͱ͸ • ͦͷ SIL Instruc,on ͕ϝϞϦ্ͰͲ͏ৼΔ෣͏͔Λௐ΂Δ • ಡΈղ͘ʹ͸ɺΘΓͱLLVMͷ஌͕ࣝඞཁ 42

Slide 43

Slide 43 text

getMemoryBehavior ͷ࣮૷ SILInstruction::MemoryBehavior SILInstruction::getMemoryBehavior() const { if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); if (BInfo.ID != BuiltinValueKind::None) return BInfo.isReadNone() ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; // Handle LLVM intrinsic functions. const IntrinsicInfo &IInfo = BI->getIntrinsicInfo(); if (IInfo.ID != llvm::Intrinsic::not_intrinsic) { // Read-only. if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) && IInfo.hasAttribute(llvm::Attribute::NoUnwind)) return MemoryBehavior::MayRead; // Read-none? return IInfo.hasAttribute(llvm::Attribute::ReadNone) && IInfo.hasAttribute(llvm::Attribute::NoUnwind) ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } } // Handle full apply sites that have a resolvable callee function with an // effects attribute. if (isa(this)) { FullApplySite Site(const_cast(this)); if (auto *F = Site.getCalleeFunction()) { return F->getEffectsKind() == EffectsKind::ReadNone ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } } switch (getKind()) { #define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \ case SILInstructionKind::CLASS: \ return MemoryBehavior::MEMBEHAVIOR; #include "swift/SIL/SILNodes.def" } llvm_unreachable("We've just exhausted the switch."); } 43

Slide 44

Slide 44 text

getMemoryBehavior • ௕͍;; • ·ͣ͸໭Γ஋ͷ enum ͔Βݟ͍ͯ͘ 44

Slide 45

Slide 45 text

SILInstruction::MemoryBehavior • ϝϞϦ্ͷৼΔ෣͍Λఆٛ͢Δ enum • ҎԼͷ5͕ͭఆٛ͞Ε͍ͯΔ • None • MayRead • MayWrite • MayReadWrite • MayHaveSideEffects 45

Slide 46

Slide 46 text

SILInstruction::MemoryBehavior::None • Կ΋͠ͳ͍΍ͭ 46

Slide 47

Slide 47 text

SILInstruction::MemoryBehavior::MayRead • The instruc-on may read memory. • ௚༁) ϝϞϦΛಡΉ͔΋͠Εͳ͍ 47

Slide 48

Slide 48 text

SILInstruction::MemoryBehavior::MayWrite • The instruc-on may write to memory. • ௚༁) ϝϞϦʹॻ͖ࠐΉ͔΋͠Εͳ͍ 48

Slide 49

Slide 49 text

...::MemoryBehavior::MayReadWrite • The instruc-on may read or write memory. • ௚༁) ಡΉ͔ॻ͖ࠐΉ͔΋͠Εͳ͍ʢ·ͨ͸ͦͷ྆ํʁʣ 49

Slide 50

Slide 50 text

...::MemoryBehavior::MayHaveSideEffects • The instruc-on may have side effects not captured solely by its users. Specifically, it can return, release memory, or store. Note, alloc is not considered to have side effects because its result/users represent its effect. • ௚༁) Instruc-on͸ɺͦͷར༻ऀ͚ͩͰ͸ଊ͑ΒΕͳ͍෭࡞༻Λ࣋ͭ͜ͱ͕ ͋Γ·͢ɻ۩ମతʹ͸ɺϦλʔϯɺϝϞϦղ์ɺετΞͳͲͰ͢ɻ஫ҙɿ alloc͸ɺͦͷ݁Ռ/ར༻ऀ͕ͦͷ࡞༻Λද͢ͷͰɺ෭࡞༻͕͋Δͱ͸ߟ͑ ΒΕ·ͤΜɻ • ҙ༁ʣͳʹ͔͠Βɺͷͪͷͪ෭࡞༻͕͋Δ 50

Slide 51

Slide 51 text

MemoryBehavior ? • ͭ·Γ͸ɺͦͷInstruc(on͕ɺͲͷΑ͏ʹϝϞϦ্ͰৼΔ෣͏͔ Λද͢ • ݟͨײͩ͡ͱɺ࣍ͷϝϞϦ্ͷৼΔ෣͍͕ʮ෭࡞༻ʯͱͯ֘͠౰͢ΔΑ͏ʹ͍ͯ͠Δ • MayWrite (ॻ͖) • MayReadWrite (ಡΈॻ͖) • MayHaveSideEffects (෭࡞༻͋Γ) • seemUseful ͸ɺࠓݟ͍ͯΔInscruc+onʹ෭࡞༻͕͋Δ͔ௐ΂͍ͯΔ 51

Slide 52

Slide 52 text

getMemoryBehaviorͷ࣮૷ΛݟΔ ࠷ॳͷ if ʹϑΥʔΧεΛ౰ͯΔ if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); if (BInfo.ID != BuiltinValueKind::None) return BInfo.isReadNone() ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; // Handle LLVM intrinsic functions. const IntrinsicInfo &IInfo = BI->getIntrinsicInfo(); if (IInfo.ID != llvm::Intrinsic::not_intrinsic) { // Read-only. if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) && IInfo.hasAttribute(llvm::Attribute::NoUnwind)) return MemoryBehavior::MayRead; // Read-none? return IInfo.hasAttribute(llvm::Attribute::ReadNone) && IInfo.hasAttribute(llvm::Attribute::NoUnwind) ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } } 52

Slide 53

Slide 53 text

getMemoryBehaviorͷ࣮૷ΛݟΔ • ΩϟετͰInstruc)on͕ BuiltinInst ͳͷ͔Λௐ΂ͯΔ if (auto *BI = dyn_cast(this)) { ... 53

Slide 54

Slide 54 text

BuiltinInst ͱ͸ʁ • Represents an invoca/on of buil/n func/onality provided by the code generator. • ௚༁) ίʔυɾδΣωϨʔλ͕ఏڙ͢Δ૊ΈࠐΈػೳͷݺͼग़ ͠Λද͠·͢ɻ • ! ! ! ! ! 54

Slide 55

Slide 55 text

BuiltinInst ͱ͸ʁ • SIL.rst ʹ͋ͬͨ • h+ps:/ /github.com/apple/swi:/blob/master/docs/SIL.rst#buil=n • Invokes func=onality built into the backend code generator, such as LLVM- level instruc=ons and intrinsics. • SIL Instruc=on ͷ๯಄ʹ builtin ͱ͍͍ͭͯΔͷ͕ϙΠϯτ • ௚༁) LLVMϨϕϧͷ໋ྩ΍૊ΈࠐΈؔ਺ͳͲɺόοΫΤϯυίʔυδΣωϨʔλʔʹ૊Έࠐ·ΕͨػೳΛ ݺͼग़͠·͢ɻ swift // Example %1 = builtin "foo"(%1 : $T, %2 : $U) : $V 55

Slide 56

Slide 56 text

Buil%nInst ͱ͸ʁ • BuiltinInst ʹ͸ҎԼͷ̎ͭͷछྨ͕͋Δ • Swi$ buil)n func)on • LLVM intrinsic func)on 56

Slide 57

Slide 57 text

BuiltinInst ͱ͸ʁ Swi% buil)n func)on • Swi$(SIL)ͷίʔυϨϕϧͰදݱͰ͖ͳ͍ॲཧ ͷݺͼग़͠ • LLVMͷॲཧʹॻ͖׵ΘΔ΂͖৔ॴ • ྫ) Int(64)ಉ࢜ͷൺֱԋࢉ // SIL %5 = builtin "cmp_eq_Int64"(%3 : $Builtin.Int64, %4 : $Builtin.Int64) ... ↓ LLVM IRͩͱ͜͏ͳΔ // LLVM IR %10 = icmp eq i64 %8, 10 57

Slide 58

Slide 58 text

BuiltinInst ͱ͸ʁ Swi% buil)n func)on • Buil&ns.def ʹbuil&n func&onͷҰཡ͋Γ • ͪͳΈʹઌఔͷ cmp_eq_Int64 ͸ɺBuil&ns.def Ͱ͸ cmp_eq ͱͯ͠Ϧετ͞Ε͍ͯΔ 58

Slide 59

Slide 59 text

BuiltinInstͱ͸ʁ LLVM intrinsic func-ons • LLVMʹɺطʹ͋Δؔ਺ΛಡΈࠐΉ ίʔυ 59

Slide 60

Slide 60 text

getMemoryBehaviorͷ࣮૷ΛݟΔ Swi$ buil)n func)ons • ݟͨײ͡ɺͦͷ̎ͭͰ৚݅Λ෼͚͍ͯΔ • Swi% buil*n func*ons ΁ͷΞϓϩʔνΛ·ͣ͸ݟͯΈΔ if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); ... // Handle LLVM intrinsic functions. const IntrinsicInfo &IInfo = BI->getIntrinsicInfo(); ... } 60

Slide 61

Slide 61 text

getMemoryBehaviorͷ࣮૷ΛݟΔ Swi$ buil)n func)ons if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); if (BInfo.ID != BuiltinValueKind::None) return BInfo.isReadNone() ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; • Ͳ͏΍Β͜ͷbuil&nͷfunc&onͷ৘ใΛͱͬͯɺҎԼͷ৘ใΛ΋ͱʹMemory BehaviorΛ൑அ • BInfo.ID • BInfo.isReadNone() 61

Slide 62

Slide 62 text

BInfo.ID • BInfo.ID ͱ͸? • ໭Γ஋ʹͳͬͯΔͷ͸ɺBuiltinValueKindܕ • ܕͷৄࡉΛݟͯΈΔ 62

Slide 63

Slide 63 text

BInfo.ID • ݟͨײ͡ BuiltinValueKind ͸ enum /// BuiltinValueKind - The set of (possibly overloaded) builtin functions. enum class BuiltinValueKind { None = 0, #define BUILTIN(Id, Name, Attrs) Id, #include "swift/AST/Builtins.def" }; • Ͳ͏΍Βઌఔݴٴͨ͠ Buil'ns.def ͔ΒɺͳΜ͔औ͍ͬͯΔʁ 63

Slide 64

Slide 64 text

BInfo.ID • Buil&ns.def Λ೷͘ • ͖ͬ͞ݴٴͨ͠ cmp_eq_Int64 पลͷ෦෼ΛݟͯΈΔ ... #ifndef BUILTIN_BINARY_PREDICATE #define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \ BUILTIN(Id, Name, Attrs) #endif BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector) BUILTIN_BINARY_PREDICATE(ICMP_NE, "cmp_ne", "n", IntegerOrRawPointerOrVector) BUILTIN_BINARY_PREDICATE(ICMP_SLE, "cmp_sle", "n", IntegerOrVector) ... 64

Slide 65

Slide 65 text

BInfo.ID BUILTIN_BINARY_PREDICATEͱ͍͏΋ͷ͕ఆٛ͞Ε͍ͯΔ // ϚΫϩ BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector) ICMP_EQ͕͖ͬ͞ྫͰࡌ͍ͬͯͨ cmp_eq ͷId ҎԼͰBUILTINʹม׵Ͱ͖ΔΑ͏ʹͳ͍ͬͯΔ // ϚΫϩ #define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \ BUILTIN(Id, Name, Attrs) 65

Slide 66

Slide 66 text

BInfo.ID /// BuiltinValueKind - The set of (possibly overloaded) builtin functions. enum class BuiltinValueKind { None = 0, #define BUILTIN(Id, Name, Attrs) Id, #include "swift/AST/Builtins.def" }; • ͭ·ΓɺBInfo.ID ͸Builtns.defͰఆٛ͞Εͨɺbuil/n func/onͷID • Buil/ns.defͷ಺෦ͷϚΫϩͰ BUILTIN(Id, Name, Attrs) ʹ͔͑ͯɺ • ͦͷBUILTIN - Id Λenumͷ஋ʹ͍ͯ͠Δ 66

Slide 67

Slide 67 text

getMemoryBehaviorͷ࣮૷ΛݟΔ Swi$ buil)n func)ons • if (BInfo.ID != BuiltinValueKind::None) • false ͳΒLLVM intrinsic func.on ͱͯ࣍͠ͷύʔτͰϋϯυϦϯά if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); if (BInfo.ID != BuiltinValueKind::None) return BInfo.isReadNone() ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; • ࣍͸ isReadNoneɻ͚ͩͲ͍͍ͩͨಉ͡ϝΧχζϜͳͷͰαοͱ঺հ 67

Slide 68

Slide 68 text

isReadNone • buil&n func&on͕ ϝϞϦΞΫηεΛڐՄ͞Ε͍ͯΔ͔Λௐ΂Δ • LLVM IRͰ͸ؔ਺ʹ ଐੑ(A#ribute) ͕͚ͭΒΕΔ • Ͳ͏͍͏ৼΔ෣͍Λߦ͏͔ʁ͕ଐੑ͔ΒಡΈऔΕΔ • readnone ଐੑ͕͍͍ͭͯΔͱɺϝϞϦΞΫηε͕ڐՄ͞Εͳ͍ • ٯʹݴ͑͹ɺreadnone͕͍ͭͯͳ͚Ε͹ɺͳʹ͔ϝϞϦͰ΍Δ͔ ΋͠Εͳ͍ͱ͍͏͜ͱʹͳΔ 68

Slide 69

Slide 69 text

isReadNone // Builtins.def ͔Β·ͨൈਮ #ifndef BUILTIN_BINARY_PREDICATE #define BUILTIN_BINARY_PREDICATE(Id, Name, Attrs, Overload) \ BUILTIN(Id, Name, Attrs) #endif BUILTIN_BINARY_PREDICATE(ICMP_EQ, "cmp_eq", "n", IntegerOrRawPointerOrVector) • BUILTIN(Id, Name, Attrs) ͷͳ͔Ͱ Attrs="n" ͩͬͨΒ readnone • ϝϞϦΞΫηε͕Ͱ͖ͳ͍ॲཧʹม׵͞ΕΔͱ͍͏͜ͱ • bul&n func&ons ͸ Bul&ns.def Ͱ ଐੑΛҰͭҰͭ ϥϕϦϯάͯ͠Δ • ม׵ઌ͸ɺଐੑ͕͚ͭΕͳ͍ Instruc&on ୯ମ͔ͩΒʁ 69

Slide 70

Slide 70 text

getMemoryBehavior ͷ࣮૷ΛݟΔ Swi$ buil)n func)ons if (auto *BI = dyn_cast(this)) { // Handle Swift builtin functions. const BuiltinInfo &BInfo = BI->getBuiltinInfo(); if (BInfo.ID != BuiltinValueKind::None) return BInfo.isReadNone() ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; • ͭ·Γ↑͸ ҎԼͷΑ͏ʹಡΈऔΕΔ • Insruc*onͷ৘ใ͔Βɺbuil*n func*on ͷଐੑΛݟΔ • ΋͠ɺreadnone (ϝϞϦΞΫηε͕ڐՄ͞Εͯͳ͍ʣͳΒ෭࡞༻͸౰વͳ͠ • ͦ͏Ͱͳ͍ͳΒɺ෭࡞༻͸ԿΒ͔ͷܗͰ͋Δ͔΋͠Εͳ͍ 70

Slide 71

Slide 71 text

getMemoryBehaviorͷ࣮૷ΛݟΔ Swi$ buil)n func)ons • ͜ͷ·ͰͷϊϦ͕Θ͔Δͱޙ൒΋Θ͔Δ • ࣍͸ɺLLVM intrinsic func.onsͷϋϯυϦϯά • ͬͪ͜΋ଐੑΛݟͯΔͷ͕ϊϦͰΘ͔Δ // Handle LLVM intrinsic functions. const IntrinsicInfo &IInfo = BI->getIntrinsicInfo(); if (IInfo.ID != llvm::Intrinsic::not_intrinsic) { // Read-only. if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) && IInfo.hasAttribute(llvm::Attribute::NoUnwind)) return MemoryBehavior::MayRead; // Read-none? return IInfo.hasAttribute(llvm::Attribute::ReadNone) && IInfo.hasAttribute(llvm::Attribute::NoUnwind) ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } 71

Slide 72

Slide 72 text

LLVMͷؔ਺ͷଐੑ NoUnwind • ଐੑ NoUnwind • ྫ֎Λ౤͛ͳ͍ͱ͍͏ଐੑ 72

Slide 73

Slide 73 text

getMemoryBehaviorͷ࣮૷ΛݟΔ Swi$ buil)n func)ons if (IInfo.hasAttribute(llvm::Attribute::ReadOnly) && IInfo.hasAttribute(llvm::Attribute::NoUnwind)) return MemoryBehavior::MayRead; // Read-none? return IInfo.hasAttribute(llvm::Attribute::ReadNone) && IInfo.hasAttribute(llvm::Attribute::NoUnwind) ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; • ͭ·Γ↑ͷίʔυ͸ • ྫ֎Λ֎ʹ౤͛ͳ͍Ͱɺ͔ͭಡΈࠐΈΦϯϦʔͷؔ਺͸ MayRead • ྫ֎Λ֎ʹ౤͛Δɺ·ͨ͸ɺϝϞϦΞΫηεͰ͖Δؔ਺͸ MayHaveSideEffects • ͦΕҎ֎͸ None 73

Slide 74

Slide 74 text

getMemoryBehaviorͷ࣮૷ΛݟΔ ͜͜·Ͱͷ·ͱΊ • ·ͣ͸ɺͦͷInstruc)on͕bul)n instruc)on͔ݟΔ • ΋ͦ͠͏Ͱ͋Ε͹ɺLLVM IR ʹͳͬͨޙͷଐੑΛݟͯɺϝϞ Ϧ্ͷৼΔ෣͍Λ൑அ 74

Slide 75

Slide 75 text

getMemoryBehaviorͷ࣮૷ΛݟΔ • bul%n instruc%on Ͱ͸ͳ͍৔߹͸Ͳ͏͔ • ࣍ͷύʔτΛݟͯΈΔ // Handle full apply sites that have a resolvable callee function with an // effects attribute. if (isa(this)) { FullApplySite Site(const_cast(this)); if (auto *F = Site.getCalleeFunction()) { return F->getEffectsKind() == EffectsKind::ReadNone ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } } 75

Slide 76

Slide 76 text

FullApplySite • apply, begin_apply ͱ͍ͬͨؔ਺ݺͼग़͠ͷͨΊͷInsruc*on͕ ͋Δ • apply • begin_apply • try_apply • PassͷίʔυͰ͸ɺFullApplySite ͱ͍͏ܕͰ·ͱΊΒΕ͍ͯΔ 76

Slide 77

Slide 77 text

SIL Func)onͷA,ribute • SIL Func)on ʹ΋ଐੑ͸͋Δ • h#ps:/ /github.com/apple/swi5/blob/master/docs/SIL.rst#func>ons // BNF sil-function-attribute ::= '[' sil-function-effects ']' sil-function-effects ::= 'readonly' sil-function-effects ::= 'readnone' sil-function-effects ::= 'readwrite' sil-function-effects ::= 'releasenone' 77

Slide 78

Slide 78 text

SIL Func)onͷA,ribute • sil-function-attribute ͸SIL Func)onͷએݴ࣌ʹඞਢ // BNF sil-function ::= 'sil' sil-linkage? sil-function-attribute+ sil-function-name ':' sil-type '{' sil-basic-block+ '}' 78

Slide 79

Slide 79 text

getMemoryBehaviorͷ࣮૷ΛݟΔ • Έ͔ͨΜ͡ • ؔ਺ݺͼग़͠ͷInstruc)on͔Ͳ͏͔ௐ΂͍ͯΔ • ؔ਺ݺͼग़ͩͬͨ͠Β callee(ݺͼͨؔ͢਺)ͷଐੑΛௐ΂Δ • ReadNone ͡Όͳ͍ͳΒ MayHaveSideEffects if (isa(this)) { FullApplySite Site(const_cast(this)); if (auto *F = Site.getCalleeFunction()) { return F->getEffectsKind() == EffectsKind::ReadNone ? MemoryBehavior::None : MemoryBehavior::MayHaveSideEffects; } } 79

Slide 80

Slide 80 text

getMemoryBehaviorͷ࣮૷ΛݟΔ ͜͜·ͰΘ͔ͬͨ͜ͱ • SIL Instruc,onͷϝϞϦͷৼΔ෣͍Λௐ΂Δͱ͖͸ɺLLVM ͷଐ ੑΛར༻͍ͯ͠Δ • ΋͘͠͸ SIL Func,on ͷଐੑ 80

Slide 81

Slide 81 text

getMemoryBehaviorͷ࣮૷ΛݟΔ • ࠷ޙͷύʔτ • Ͳ͏ݟͯ΋Buil&n.def(Swi/ buil&n funcion)ͷͱ͖ͱಉ͡ύλʔϯ switch (getKind()) { #define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \ case SILInstructionKind::CLASS: \ return MemoryBehavior::MEMBEHAVIOR; #include "swift/SIL/SILNodes.def" } llvm_unreachable("We've just exhausted the switch."); 81

Slide 82

Slide 82 text

SILNodes.def ͷMEMBEHAVIORʹ͍ͭͯ • SILNodes.defʹ֤Instruc1on͕ͲͷΑ͏ʹϝϞϦ্ͷৼΔ෣͏͔ॻ͍ͯ͋Δ • FULL_INST ͱ͍͏৘ใͰ·ͱΊΒΕ͍ͯΔ • CLASS ʹ͸ɺ͖ͬ͞ͷ SILInstructionKind ͱಉ͡஋ׂ͕ΓৼΒΕ͍ͯ Δ ( getKind ͷ໭Γ஋ͷenumܕ) • MEMBEHAVIOR ʹ͸ɺMemoryBehavior ͱಉ͡஋ׂ͕ΓৼΒΕ͍ͯΔ ʢenumܕ) • ઌఔͷ Buil1ns.def ͷͱ͖ͱಉ͡ɹ 82

Slide 83

Slide 83 text

getMemoryBehavior ͷ࣮૷ΛݟΔ • ͱ͍͏͜ͱ͸ɺ • SILNodes.def ʹ͋ΔɺʮInscrutc2on͕ϝϞϦ্ͰͲͷΑ͏ʹৼΔ෣͏͔ ʢMEMBEHAVIORʣʯͷఆٛΛϚΫϩͰ switch-case ʹల։͍ͯ͠Δ switch (getKind()) { #define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \ case SILInstructionKind::CLASS: \ return MemoryBehavior::MEMBEHAVIOR; #include "swift/SIL/SILNodes.def" } llvm_unreachable("We've just exhausted the switch."); 83

Slide 84

Slide 84 text

getMemoryBehaviorͷ࣮૷ΛݟΔ ·ͱΊ • getMemoryBehavior ͸ҎԼͷ؍఺ͰInstruc*onͷϝϞϦͷৼΔ෣͍Λݟ͍ͯΔ • ม׵ઌͷ LLVM IR ͷ Instrucion ͸ɺͲΜͳଐੑʹͳΔ͔ʁ • Attrs in Buil*ns.def • ݺͼग़͢ LLVM IR ͷ Func0on ͸ɺͲΜͳଐੑ͔ʁ • ݺͼग़͍ͯ͠Δ SIL Function ͸ɺͲΜͳଐੑ͔ʁ • ↑ Ͱ΋Θ͔Μͳ͍ͳΒɺطʹ Instruc0on ʹఆٛ͞Ε͍ͯΔଐੑΛݟʹߦ͘ • MEMBEHAVIOR in SILNode.def 84

Slide 85

Slide 85 text

getMemoryBehaviorͷ࣮૷ΛݟΔ શ෦ಡΊͨʂ 85

Slide 86

Slide 86 text

mayHaveSideEffects ·ͱΊ • ࣍ͷ৔߹ɺSIL Instruc,on ʹ෭࡞༻͕͋ΔͱίϯύΠϥ͸൑அ͢Δ • ৚݅ʹΑͬͯ͸ɺRun,me Failure ͢Δ͔ʁ • ৚݅ʹΑͬͯ͸ɺϝϞϦ্Ͱॻ͖ࠐΈͳͲͱ͍ͬͨ͜ͱΛߦ͏͔ʁ bool SILInstruction::mayHaveSideEffects() const { // If this instruction traps then it must have side effects. if (mayTrap()) return true; MemoryBehavior B = getMemoryBehavior(); return B == MemoryBehavior::MayWrite || B == MemoryBehavior::MayReadWrite || B == MemoryBehavior::MayHaveSideEffects; } 86

Slide 87

Slide 87 text

·ͱΊ 87

Slide 88

Slide 88 text

ίϯύΠϥʹͱͬͯʮ෭࡞༻͕͋Γͦ͏ʯͳίʔυͱ͸ Ұମʁ • Run%me Error͕ى͜Γͦ͏ͳίʔυ • ϝϞϦॻ͖ࠐΈͳͲ͕ى͜Γͦ͏ͳίʔυ 88

Slide 89

Slide 89 text

ίϯύΠϥʹͱͬͯʮ෭࡞༻͕͋Γͦ͏ʯͷ൑ผͷ࢓ํ • LLVM ͷଐੑͳͲΛར༻ͯ͠൑அ͍ͯ͠Δ • Buil*ns.def ΍ LLVM ͷؔ਺͔ΒಡΈऔͬͯ൑அ • SILNodes.def Ͱ SIL Instruc*on ͷϝϞϦͷৼΔ෣͍͸ఆٛ͞Ε ͍ͯΔ 89

Slide 90

Slide 90 text

ࠓ೔ษڧͨ͜͠ͱ • LLVMɺSIL ͷ ଐੑ(A*ribute) • SILNodes.def • Buil:ns.def • mayHaveSideEffects • getMemoryBehavior 90

Slide 91

Slide 91 text

ࢀߟจݙ • Θ͍Θ͍swi%c ϫʔΫγϣοϓ Vol.3 ෱Ԭ • Swi%ͷதؒݴޠSILΛಡΉ ͦͷ5 - Dead Code Elimina:on • SILOp:mizerʹܰ͘ೖ໳͢Δ • SIL.rst • LLVM/Clang࣮ફ׆༻ϋϯυϒοΫ 91