From ace94350f72bf69e0995c5e38b7e18316867d2e5 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:28:58 +0200 Subject: [PATCH 01/14] Improve the consistency of Array's documentation --- Library/Hylo/Array.hylo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index 577455ca1..bf8d40e5e 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -6,7 +6,7 @@ public type Array: Deinitializable { /// The header of the buffer indicates the number of elements contained in the array. var storage: DynamicBuffer - /// Creates a new, empty array. + /// Creates an empty array. public init() { &storage = .new() } @@ -20,7 +20,7 @@ public type Array: Deinitializable { } } - /// The number of elements in the array. + /// Returns the number of elements in `self`. public fun count() -> Int { if storage.capacity() == 0 { 0 } else { storage.header.copy() } } @@ -30,7 +30,7 @@ public type Array: Deinitializable { return storage.capacity() } - /// Reserves enough space to store `n` elements + /// Reserves enough space to store `n` elements in `self`. public fun reserve_capacity(_ n: Int) inout { if n < capacity() { return } From c9590040fe3276432b38e52dcd021342beed775b Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:29:46 +0200 Subject: [PATCH 02/14] Redefine 'Array.pointer_to_element' as a subscript --- Library/Hylo/Array.hylo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index bf8d40e5e..6ee1e63c4 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -15,7 +15,7 @@ public type Array: Deinitializable { public fun deinit() sink { var i = 0 while i < count() { - &pointer_to_element(at: i).unsafe_pointee().deinit() + &pointer_to_element[at: i].unsafe_pointee().deinit() &i += 1 } } @@ -63,7 +63,7 @@ public type Array: Deinitializable { /// Adds a new element at the end of the array. public fun append(_ source: sink Element) inout { &reserve_capacity(count() + 1) - pointer_to_element(at: count()).unsafe_initialize_pointee(source) + pointer_to_element[at: count()].unsafe_initialize_pointee(source) &storage.header += 1 } @@ -73,18 +73,18 @@ public type Array: Deinitializable { public subscript(_ position: Int): Element { let { // precondition(position >= 0 && position < count()) - pointer_to_element(at: position).unsafe[] + pointer_to_element[at: position].unsafe[] } inout { // precondition(position >= 0 && position < count()) - pointer_to_element(at: position).unsafe[] + pointer_to_element[at: position].unsafe[] } } - /// Returns the address of the element at `position`. + /// Projects the address of the element at `position`. /// /// - Requires: `position` is in the range `0 ..< capacity()`. - fun pointer_to_element(at position: Int) -> PointerToMutable { + subscript pointer_to_element(at position: Int): PointerToMutable { storage.first_element_address().advance(by: position) } From e69255b349041c83400316fda986023a6a38206f Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:30:54 +0200 Subject: [PATCH 03/14] Implement 'Array.is_empty' --- Library/Hylo/Array.hylo | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index 6ee1e63c4..6844d2fd0 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -25,6 +25,11 @@ public type Array: Deinitializable { if storage.capacity() == 0 { 0 } else { storage.header.copy() } } + /// Returns `true` if `self` is empty. + public fun is_empty() -> Bool { + count() == 0 + } + /// The number of elements that can be stored in the array before new storage must be allocated. public fun capacity() -> Int { return storage.capacity() From eadbf78587292ed72152510dea0415805a085c0b Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:31:43 +0200 Subject: [PATCH 04/14] Implement unsafe projections of an Array's contiguous storage --- Library/Hylo/Array.hylo | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index 6844d2fd0..868df3957 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -65,6 +65,24 @@ public type Array: Deinitializable { &storage = new_storage } + /// Projects a pointer to the start of the array's contiguous storage. + /// + /// The projected pointer is valid only for the duration of the projection and can be advanced up + /// to `count()`. It may be null if `self` is empty. + public property contiguous_storage: Pointer { + yield if capacity() == 0 { .null() } else { .new(pointer_to_element[at: 0]) } + } + + /// Calls `action` with a pointer to the start of the array's mutable contiguous storage. + /// + /// The projected pointer is valid only for the duration of the projection and can be advanced up + /// to `count()`. It may be null if `self` is empty. + public fun with_mutable_contiguous_storage( + _ action: inout [E](PointerToMutable) inout -> T + ) inout -> T { + if capacity() == 0 { &action(.null()) } else { &action(pointer_to_element[at: 0]) } + } + /// Adds a new element at the end of the array. public fun append(_ source: sink Element) inout { &reserve_capacity(count() + 1) From 2fd606b46451dec92876ffc2bc5543a16264bf20 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:47:47 +0200 Subject: [PATCH 05/14] Remove duplicate function --- Library/Hylo/Core/Bitcast.hylo | 2 +- Library/Hylo/Core/PointerToMutable.hylo | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Library/Hylo/Core/Bitcast.hylo b/Library/Hylo/Core/Bitcast.hylo index 8f6589a0c..9d1c2f06f 100644 --- a/Library/Hylo/Core/Bitcast.hylo +++ b/Library/Hylo/Core/Bitcast.hylo @@ -5,7 +5,7 @@ public subscript unsafe_bitcast(_ value: T): U { yield p.unsafe[] } inout { - sink let p: PointerToMutable = PointerToMutable(type_punning: pointerToMutable[&value]) + sink let p: PointerToMutable = PointerToMutable(type_punning: mutable_pointer[to: &value]) yield &p.unsafe[] } } diff --git a/Library/Hylo/Core/PointerToMutable.hylo b/Library/Hylo/Core/PointerToMutable.hylo index 1e9a7c5aa..b77a11ddb 100644 --- a/Library/Hylo/Core/PointerToMutable.hylo +++ b/Library/Hylo/Core/PointerToMutable.hylo @@ -55,13 +55,6 @@ public type PointerToMutable: Regular { } - -// FIXME: rename pointer[toMutable:] -/// The address of `x`. -public subscript pointerToMutable(_ x: inout T): PointerToMutable { - let { yield PointerToMutable(base: Builtin.address(of: x)) } -} - public conformance PointerToMutable: Copyable { /// Returns an equivalent instance. @@ -103,12 +96,12 @@ public extension PointerToMutable where Pointee: Movable { } +// TODO: Rename to `pointer[to_mutable:]` /// The address of `x`. public subscript mutable_pointer(to x: inout T): PointerToMutable { let { yield PointerToMutable(base: Builtin.address(of: x)) } } - /// Initializes `x` to `y`. /// /// - Note: This function is a workaround for the lack of `set` bindings (see #925). From f9853786bb7f40ad400f4589c98398dd6deebf11 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 01:48:04 +0200 Subject: [PATCH 06/14] Implement a consuming unsafe bitcast --- Library/Hylo/Core/Bitcast.hylo | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Library/Hylo/Core/Bitcast.hylo b/Library/Hylo/Core/Bitcast.hylo index 9d1c2f06f..fd46d8acb 100644 --- a/Library/Hylo/Core/Bitcast.hylo +++ b/Library/Hylo/Core/Bitcast.hylo @@ -9,3 +9,9 @@ public subscript unsafe_bitcast(_ value: T): U { yield &p.unsafe[] } } + +/// Returns `value` with its memory representation reinterpreted as a value of type `U`. +public fun unsafe_bitcast(consuming value: T) -> U { + sink let p: PointerToMutable = PointerToMutable(type_punning: mutable_pointer[to: &value]) + return p.unsafe_pointee() +} From 9187bd76b14483b4b6c9884d2ce99c373fdb2290 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 04:00:09 +0200 Subject: [PATCH 07/14] Fix the gathering of traits to which generic type parameters conform --- .../FrontEnd/TypeChecking/TypeChecker.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 241f34c57..b0cad6bec 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -246,6 +246,15 @@ struct TypeChecker { for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type { let e = environment(of: s)! result.formUnion(e.conformedTraits(of: ^t)) + + // Note: `s` might be extending the type whose declaration introduced the generic environment + // that declared `t`. + if s.kind.value is TypeExtendingDecl.Type { + let d = AnyDeclID(s)! + if let g = environment(introducedByDeclOf: uncheckedType(of: d)) { + result.formUnion(g.conformedTraits(of: ^t)) + } + } } return result } @@ -1337,6 +1346,20 @@ struct TypeChecker { return result } + /// Returns the generic environment introduced by the declaration of `t`, if any. + private mutating func environment(introducedByDeclOf t: AnyType) -> GenericEnvironment? { + switch t.base { + case let u as ProductType: + return environment(of: u.decl) + case let u as TraitType: + return environment(of: u.decl) + case let u as TypeAliasType: + return environment(of: u.decl) + default: + return nil + } + } + /// Insert's `d`'s constraints in `e`. private mutating func insertConstraints( of d: AssociatedTypeDecl.ID, in e: inout GenericEnvironment From 819d49619a823ef1bec6dd176b0bf14fb8edb83b Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 04:00:29 +0200 Subject: [PATCH 08/14] Implement 'swap(_:_:)' --- Library/Hylo/Core/Movable.hylo | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Library/Hylo/Core/Movable.hylo b/Library/Hylo/Core/Movable.hylo index 6be7a1cf6..7bcceb159 100644 --- a/Library/Hylo/Core/Movable.hylo +++ b/Library/Hylo/Core/Movable.hylo @@ -8,3 +8,21 @@ public trait Movable { } } + +public extension Movable { + + /// Exchanges the value of `self` with that of `other`. + public fun exchange(with other: inout Self) inout { + sink let x = self + &self = other + &other = x + } + +} + +// TODO: Remove in favor of `Movable.exchange(with:)` when #1064 is fixed +public fun swap(_ a: inout T, _ b: inout T) { + sink let x = a + &a = b + &b = x +} From a49c64093244196c3fee2ce8ce7514f17a1ffdde Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 04:06:20 +0200 Subject: [PATCH 09/14] Implement 'Array.swap_at(_:_:)' --- Library/Hylo/Array.hylo | 8 ++++++++ Tests/LibraryTests/TestCases/ArrayTests.hylo | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index 868df3957..e8914b720 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -90,6 +90,14 @@ public type Array: Deinitializable { &storage.header += 1 } + /// Exchanges the values at the given positions in `self`. + public fun swap_at(_ i: Int, _ j: Int) inout { + // precondition(i >= 0 && i < count()) + // precondition(j >= 0 && j < count()) + if i == j { return } + swap(&pointer_to_element[at: i].unsafe[], &pointer_to_element[at: j].unsafe[]) + } + /// Accesses the element at `position`. /// /// - Requires: `position` is in the range `0 ..< count()`. diff --git a/Tests/LibraryTests/TestCases/ArrayTests.hylo b/Tests/LibraryTests/TestCases/ArrayTests.hylo index a975a83b8..cdbab7f0e 100644 --- a/Tests/LibraryTests/TestCases/ArrayTests.hylo +++ b/Tests/LibraryTests/TestCases/ArrayTests.hylo @@ -16,7 +16,16 @@ fun test_append() { precondition(d[2] == 84) } +fun test_swap_at() { + var a = Array() + &a.append(false) + &a.append(true) + &a.swap_at(0, 1) + precondition(a[0]) +} + public fun main() { test_init_empty() test_append() + test_swap_at() } From b8abd6862ee2c5591477e17f478317838e0319f4 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 04:09:50 +0200 Subject: [PATCH 10/14] Implement 'Array.reverse' --- Library/Hylo/Array.hylo | 13 +++++++++++++ Tests/LibraryTests/TestCases/ArrayTests.hylo | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo index e8914b720..fbd2abf4d 100644 --- a/Library/Hylo/Array.hylo +++ b/Library/Hylo/Array.hylo @@ -98,6 +98,19 @@ public type Array: Deinitializable { swap(&pointer_to_element[at: i].unsafe[], &pointer_to_element[at: j].unsafe[]) } + /// Reverses the elements of `self` in place. + /// + /// - Complexity: O(n), where n is the number of elements in `self`. + public fun reverse() inout { + var i = count() - 1 + var j = 0 + while i > j { + swap_at(i, j) + &i -= 1 + &j += 1 + } + } + /// Accesses the element at `position`. /// /// - Requires: `position` is in the range `0 ..< count()`. diff --git a/Tests/LibraryTests/TestCases/ArrayTests.hylo b/Tests/LibraryTests/TestCases/ArrayTests.hylo index cdbab7f0e..d18038b3c 100644 --- a/Tests/LibraryTests/TestCases/ArrayTests.hylo +++ b/Tests/LibraryTests/TestCases/ArrayTests.hylo @@ -24,8 +24,21 @@ fun test_swap_at() { precondition(a[0]) } +fun test_reverse() { + var a = Array() + &a.append(21) + &a.append(42) + &a.append(84) + + &a.reverse() + precondition(a[0] == 84) + precondition(a[1] == 42) + precondition(a[2] == 21) +} + public fun main() { test_init_empty() test_append() test_swap_at() + test_reverse() } From 5501c8a392b4b2c43cb70747200328e49bf89c5a Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 09:45:48 +0200 Subject: [PATCH 11/14] Implement a 'print' overload for integers This implementation is temporary. It is meant to help debugging while the necessary components are put in place to support a universal `print` function. --- Library/Hylo/Print.hylo | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Library/Hylo/Print.hylo b/Library/Hylo/Print.hylo index fefe5f60f..fe89ebf83 100644 --- a/Library/Hylo/Print.hylo +++ b/Library/Hylo/Print.hylo @@ -5,6 +5,33 @@ public fun print(_ item: String, terminator: String = "\n") { _ = fwrite(CVoidPointer(base: terminator.utf8.base), 1, 1, CVoidPointer(base: stream.base)) } +/// Writes the textual representation of `item` to the standard output. +public fun print(_ item: Int, radix: Int = 10, terminator: String = "\n") { + if item == 0 { + print("0") + return + } + + var a = Array() + var v = item.abs() + + while v != 0 { + let i = v % radix + &v /= radix + // Note: 48 = "0" and 97 = "a" + &a.append((Int8(truncating_or_extending: i + if i < 10 { 48 } else { 97 }))) + } + + // Note: 45 = "-" + if item < 0 { &a.append(45) } + &a.reverse() + + let stream = stdout() + let buffer = a.contiguous_storage.base + _ = fwrite(CVoidPointer(base: buffer), 1, a.count(), CVoidPointer(base: stream.base)) + _ = fwrite(CVoidPointer(base: terminator.utf8.base), 1, 1, CVoidPointer(base: stream.base)) +} + /// The standard output of the current process. fun stdout() -> MemoryAddress { .new(base: fdopen(1, CVoidPointer(base: "w".utf8.base)).base) From bb687cabfe60deb84bdaf7dac72b80a0ff2b3e99 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Thu, 5 Oct 2023 09:47:32 +0200 Subject: [PATCH 12/14] Fix name resolution for properties with a bound generic type --- Sources/FrontEnd/TypeChecking/TypeChecker.swift | 7 +++++++ .../HyloTests/TestCases/TypeChecking/Property.hylo | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index b0cad6bec..a28a219de 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -2979,6 +2979,7 @@ struct TypeChecker { // The specialization of the match includes that of context in which it was looked up. var specialization = genericArguments(inScopeIntroducing: m, resolvedIn: context) + candidateType = specialize(candidateType, for: specialization, in: scopeOfUse) // Keep track of generic arguments that should be captured later on. let candidateSpecialization = genericArguments( @@ -3009,6 +3010,12 @@ struct TypeChecker { } } + // Re-specialize the candidate's type now that the substitution map is complete. + // + // The specialization map now contains the substitutions accumulated from the candidate's + // qualification as well as the ones related to the resolution of the candidate itself. For + // example, if we resolved `A.f`, we'd get `X` from the resolution of the qualification + // and `Y` from the resolution of the candidate. candidateType = specialize(candidateType, for: specialization, in: scopeOfUse) let r = program.makeReference( diff --git a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo index fc9958a6f..ab849921a 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Property.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Property.hylo @@ -1,12 +1,24 @@ //- typeCheck expecting: success -type A { +type A: Deinitializable { public memberwise init public property x: Int { let { 0 } } } +type B: Deinitializable { + public memberwise init +} + +type C: Deinitializable { + public memberwise init + public property x: B { .new() } +} + fun check(_ x: T) {} public fun main() { check(A().x) + + let a = C().x + check>(a) } From a1aa6bae562bf25ec12204614c7d58e9c731fb83 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Fri, 6 Oct 2023 17:28:46 +0200 Subject: [PATCH 13/14] Use 'hylo_aligned_alloc' instead of 'aligned_alloc' --- Library/Hylo/Pointers+DynamicAllocation.hylo | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Library/Hylo/Pointers+DynamicAllocation.hylo b/Library/Hylo/Pointers+DynamicAllocation.hylo index 841d3dc76..29fe31d64 100644 --- a/Library/Hylo/Pointers+DynamicAllocation.hylo +++ b/Library/Hylo/Pointers+DynamicAllocation.hylo @@ -2,7 +2,7 @@ public extension PointerToMutable where Pointee == Never { /// Allocates memory for `count` bytes at given `alignment`. public static fun allocate_bytes(count: Int, aligned_at alignment: Int) -> Self { - Self.new(base: aligned_alloc(alignment, count).base) + Self.new(base: hylo_aligned_alloc(alignment, count).base) } } @@ -23,12 +23,14 @@ public extension PointerToMutable { /// Allocates memory for `count` instances of `Pointee`. public static fun allocate(count: Int) -> Self { - Self.new(base: aligned_alloc(MemoryLayout.alignment(), MemoryLayout.stride() * count).base) + return Self.new(base: hylo_aligned_alloc( + MemoryLayout.alignment(), + MemoryLayout.stride() * count).base) } /// Deallocates the memory previously allocated at `self`. public fun deallocate() { - free(CVoidPointer(base: base)) + hylo_aligned_free(MemoryAddress(base: base)) } } From 44d1c69edae654c5947cc1179bb7bc942860f53d Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Fri, 6 Oct 2023 17:29:10 +0200 Subject: [PATCH 14/14] Fix ASCII offset in 'print()' --- Library/Hylo/Print.hylo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Hylo/Print.hylo b/Library/Hylo/Print.hylo index fe89ebf83..5f1278fb2 100644 --- a/Library/Hylo/Print.hylo +++ b/Library/Hylo/Print.hylo @@ -19,7 +19,7 @@ public fun print(_ item: Int, radix: Int = 10, terminator: String = "\n") { let i = v % radix &v /= radix // Note: 48 = "0" and 97 = "a" - &a.append((Int8(truncating_or_extending: i + if i < 10 { 48 } else { 97 }))) + &a.append((Int8(truncating_or_extending: i + if i < 10 { 48 } else { 87 }))) } // Note: 45 = "-"