// RUN: %target-swift-frontend %s -O -emit-sil | %FileCheck -check-prefix=CHECK-WMO %s
// RUN: %target-swift-frontend -primary-file %s -O -emit-sil | %FileCheck %s

// Test propagation of non-static let properties with compile-time constant values.

// TODO: Once this optimization can remove the propagated fileprivate/internal let properties or
// mark them as ones without a storage, new tests should be added here to check for this
// functionality.

// FIXME: This test is written in Swift instead of SIL, because there are some problems
// with SIL deserialization (rdar://22636911)

// Check that initializers do not contain a code to initialize fileprivate or
// internal (if used with WMO) properties, because their values are propagated into
// their uses and they cannot be accessed from other modules. Therefore the
// initialization code could be removed.
// Specifically, the initialization code for Prop1, Prop2 and Prop3 can be removed.

// CHECK-WMO-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts3FooC{{[_0-9a-zA-Z]*}}fc : $@convention(method) (Int32, @owned Foo) -> @owned Foo
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop0
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK-WMO: return

// CHECK-WMO-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts3FooC{{[_0-9a-zA-Z]*}}fc : $@convention(method) (Int64, @owned Foo) -> @owned Foo
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK-WMO-NOT: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop0
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK-WMO: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK-WMO: return

// Check that initializers do not contain a code to initialize fileprivate properties, 
// because their values are propagated into their uses and they cannot be accessed
// from other modules. Therefore the initialization code could be removed.
// Specifically, the initialization code for Prop2 can be removed.

// CHECK-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts3FooC{{[_0-9a-zA-Z]*}}fc : $@convention(method) (Int32, @owned Foo) -> @owned Foo
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop0
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK: return

// CHECK-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts3FooC{{[_0-9a-zA-Z]*}}fc : $@convention(method) (Int64, @owned Foo) -> @owned Foo
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop0
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop1
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop2
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo, #Foo.Prop3
// CHECK: return

public class Foo {
  public let Prop0: Int32 = 1
  let Prop1: Int32 = 1 + 4/2 + 8
  fileprivate let Prop2: Int32 = 3*7
  internal let Prop3: Int32  = 4*8
  public init(i:Int32) {}  
  public init(i:Int64) {}
}

public class Foo1 {
  let Prop1: Int32
  fileprivate let Prop2: Int32 = 3*7
  internal let Prop3: Int32 = 4*8
  public init(i:Int32) {
    Prop1  = 11
  }

  public init(i:Int64) {
    Prop1  = 1111
  }
}

public struct Boo {
  public let Prop0: Int32 = 1
  let Prop1: Int32 = 1 + 4/2 + 8  
  fileprivate let Prop2: Int32 = 3*7
  internal let Prop3: Int32 = 4*8
  public init(i:Int32) {}
  public init(i:Int64) {}
}

public class Foo2 {
  internal let x: Int32
  @inline(never)
  init(count: Int32) {
    if count < 2 {
      x = 5
    } else {
      x = 10
    }
  }
}

public class C {}

struct Boo3 {
  //public 
  let Prop0: Int32
  let Prop1: Int32
  fileprivate let Prop2: Int32
  internal let Prop3: Int32

  @inline(__always)
  init(_ f1: C, _ f2: C) {
    self.Prop0 = 0
    self.Prop1 = 1
    self.Prop2 = 2
    self.Prop3 = 3
  }

  init(_ v: C) {
    self.Prop0 = 10
    self.Prop1 = 11
    self.Prop2 = 12
    self.Prop3 = 13
  }
}

// The initializer of this struct can be defined elsewhere,
// e.g. in an extension of this struct in a different module.
public struct StructWithOnlyPublicLetProperties {
  public let Prop0: Int32
  public let Prop1: Int32

  init(_ v: Int32, _ u: Int32) {
    Prop0 = 10
    Prop1 = 11
  }
}

// The initializer of this struct cannot be defined outside
// of the current module, because it contains an internal stored
// property, which is impossible to initialize outside of this module.
public struct StructWithPublicAndInternalLetProperties {
  public let Prop0: Int32
  internal let Prop1: Int32

  init(_ v: Int32, _ u: Int32) {
    Prop0 = 10
    Prop1 = 11
  }
}

// The initializer of this struct cannot be defined elsewhere,
// because it contains a fileprivate stored property, which is
// impossible to initialize outside of this file.
public struct StructWithPublicAndInternalAndPrivateLetProperties {
  public let Prop0: Int32
  internal let Prop1: Int32
  fileprivate let Prop2: Int32

  init(_ v: Int32, _ u: Int32) {
    Prop0 = 10
    Prop1 = 11
    Prop2 = 12
  }
}


// Check that Foo1.Prop1 is not constant-folded, because its value is unknown, since it is initialized differently
// by Foo1 initializers.

// CHECK-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts13testClassLet1s5Int32VAA4Foo1CF : $@convention(thin) (@owned Foo1) -> Int32
// bb0
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop1 
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop2
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop3
// CHECK: return
public func testClassLet1(_ f: Foo1) -> Int32 {
  return f.Prop1 + f.Prop2 + f.Prop3
}

// Check that Foo1.Prop1 is not constant-folded, because its value is unknown, since it is initialized differently
// by Foo1 initializers.

// CHECK-LABEL: sil @_T019let_properties_opts13testClassLet1s5Int32VAA4Foo1CzF : $@convention(thin) (@inout Foo1) -> Int32 
// bb0
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop1 
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop2
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo1, #Foo1.Prop3
// CHECK: return
public func testClassLet1(_ f: inout Foo1) -> Int32 {
  return f.Prop1 + f.Prop2 + f.Prop3
}

// Check that return expressions in all subsequent functions can be constant folded, because the values of let properties
// are known to be constants of simple types.

// CHECK: sil [thunk] [always_inline] @_T019let_properties_opts12testClassLets5Int32VAA3FooCF : $@convention(thin) (@owned Foo) -> Int32
// CHECK: bb0
// CHECK-NEXT: strong_release
// CHECK: integer_literal $Builtin.Int32, 75
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testClassLet(_ f: Foo) -> Int32 {
  return f.Prop1 + f.Prop1 + f.Prop2 + f.Prop3
}

// CHECK-LABEL: sil @_T019let_properties_opts12testClassLets5Int32VAA3FooCzF : $@convention(thin) (@inout Foo) -> Int32
// CHECK: bb0
// CHECK: integer_literal $Builtin.Int32, 75
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testClassLet(_ f: inout Foo) -> Int32 {
  return f.Prop1 + f.Prop1 + f.Prop2 + f.Prop3
}

// CHECK-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts18testClassPublicLets5Int32VAA3FooCF : $@convention(thin) (@owned Foo) -> Int32
// CHECK: bb0
// CHECK-NEXT: strong_release
// CHECK: integer_literal $Builtin.Int32, 1
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testClassPublicLet(_ f: Foo) -> Int32 {
  return f.Prop0
}

// CHECK-LABEL: sil @_T019let_properties_opts13testStructLets5Int32VAA3BooVF : $@convention(thin) (Boo) -> Int32
// CHECK: bb0
// CHECK: integer_literal $Builtin.Int32, 75
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testStructLet(_ b: Boo) -> Int32 {
  return b.Prop1 + b.Prop1 + b.Prop2 + b.Prop3
}

// CHECK-LABEL: sil @_T019let_properties_opts13testStructLets5Int32VAA3BooVzF : $@convention(thin) (@inout Boo) -> Int32
// CHECK: bb0
// CHECK: integer_literal $Builtin.Int32, 75
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testStructLet(_ b: inout Boo) -> Int32 {
  return b.Prop1 + b.Prop1 + b.Prop2 + b.Prop3
}

// CHECK-LABEL: sil @_T019let_properties_opts19testStructPublicLets5Int32VAA3BooVF : $@convention(thin) (Boo) -> Int32
// CHECK: bb0
// CHECK: integer_literal $Builtin.Int32, 1
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return
public func testStructPublicLet(_ b: Boo) -> Int32 {
  return b.Prop0
}

// Check that f.x is not constant folded, because the initializer of Foo2 has multiple
// assignments to the property x with different values.
// CHECK-LABEL: sil [thunk] [always_inline] @_T019let_properties_opts13testClassLet2s5Int32VAA4Foo2CF : $@convention(thin) (@owned Foo2) -> Int32
// bb0
// CHECK: ref_element_addr %{{[0-9]+}} : $Foo2, #Foo2.x
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo2, #Foo2.x
// CHECK-NOT: ref_element_addr %{{[0-9]+}} : $Foo2, #Foo2.x
// CHECK: return
public func testClassLet2(_ f: Foo2) -> Int32 {
  return f.x + f.x
}

// Check that the sum of properties is not folded into a constant.
// CHECK-WMO-LABEL: sil hidden [noinline] @_T019let_properties_opts27testStructWithMultipleInitss5Int32VAA4Boo3V_AFtF : $@convention(thin) (Boo3, Boo3) -> Int32
// CHECK-WMO: bb0
// No constant folding should have been performed.
// CHECK-WMO-NOT: integer_literal $Builtin.Int32, 92
// CHECK-WMO: struct_extract
// CHECK-WMO: }
@inline(never)
func testStructWithMultipleInits( _ boos1: Boo3, _ boos2: Boo3) -> Int32 {
  let count1 =  boos1.Prop0 + boos1.Prop1 + boos1.Prop2 + boos1.Prop3
  let count2 =  boos2.Prop0 + boos2.Prop1 + boos2.Prop2 + boos2.Prop3
  return count1 + count2
}

public func testStructWithMultipleInitsAndInlinedInitializer() {
  let things = [C()]
  // This line results in inlining of the initializer Boo3(C, C) and later
  // removal of this initializer by the dead function elimination pass.
  // As a result, only one initializer, Boo3(C) is seen by the Let Properties Propagation
  // pass. This pass may think that there is only one initializer and take the
  // values of let properties assigned there as constants and try to propagate
  // those values into uses. But this is wrong! The pass should be clever enough
  // to detect all stores to the let properties, including those outside of
  // initializers, e.g. inside inlined initializers. And if it detects all such
  // stores it should understand that values of let properties in Boo3 are not
  // statically known constant initializers with the same value and thus
  // cannot be propagated.
  let boos1 = things.map { Boo3($0, C()) }
  let boos2 = things.map(Boo3.init)
  print(testStructWithMultipleInits(boos1[0], boos2[0]))
}

// Since all properties are public, they can be initialized in a
// different module.
// Their values are not known and cannot be propagated.

// CHECK-LABEL: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0E27WithOnlyPublicLetPropertiesVF 
// CHECK: struct_extract %0 : $StructWithOnlyPublicLetProperties, #StructWithOnlyPublicLetProperties.Prop0
// CHECK: return

// CHECK-WMO-LABEL: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0E27WithOnlyPublicLetPropertiesVF
// CHECK-WMO: struct_extract %0 : $StructWithOnlyPublicLetProperties, #StructWithOnlyPublicLetProperties.Prop0
// CHECK-WMO: return
public func testStructPropertyAccessibility(_ b: StructWithOnlyPublicLetProperties) -> Int32 {
  return b.Prop0 + b.Prop1
}

// Properties can be initialized in a different file in the same module.
// Their values are not known and cannot be propagated,
// unless it is a WMO compilation.

// CHECK-LABEL: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0E34WithPublicAndInternalLetPropertiesVF
// CHECK: struct_extract %0 : $StructWithPublicAndInternalLetProperties, #StructWithPublicAndInternalLetProperties.Prop0
// CHECK-NOT: integer_literal $Builtin.Int32, 21
// CHECK: return

// CHECK-WMO-LABEL: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0E34WithPublicAndInternalLetPropertiesVF
// CHECK-WMO: integer_literal $Builtin.Int32, 21
// CHECK-WMO-NEXT: struct $Int32
// CHECK-WMO-NEXT: return
public func testStructPropertyAccessibility(_ b: StructWithPublicAndInternalLetProperties) -> Int32 {
  return b.Prop0 + b.Prop1
}

// Properties can be initialized only in this file, because one of the
// properties is fileprivate.
// Therefore their values are known and can be propagated.

// CHECK: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0e21WithPublicAndInternalK20PrivateLetPropertiesVF
// CHECK: integer_literal $Builtin.Int32, 33
// CHECK-NEXT: struct $Int32
// CHECK-NEXT: return

// CHECK-WMO-LABEL: sil @_T019let_properties_opts31testStructPropertyAccessibilitys5Int32VAA0e21WithPublicAndInternalK20PrivateLetPropertiesVF
// CHECK-WMO: integer_literal $Builtin.Int32, 33
// CHECK-WMO-NEXT: struct $Int32
// CHECK-WMO-NEXT: return
public func testStructPropertyAccessibility(_ b: StructWithPublicAndInternalAndPrivateLetProperties) -> Int32 {
  return b.Prop0 + b.Prop1 + b.Prop2
}

// Force use of initializers, otherwise they got removed by the dead-function-elimination pass
// and the values of let properties cannot be determined.
public func useInitializers() -> StructWithOnlyPublicLetProperties {
  return StructWithOnlyPublicLetProperties(1, 1)
}

public func useInitializers() -> StructWithPublicAndInternalLetProperties {
  return StructWithPublicAndInternalLetProperties(1, 1)
}

public func useInitializers() -> StructWithPublicAndInternalAndPrivateLetProperties {
  return StructWithPublicAndInternalAndPrivateLetProperties(1, 1)
}
