Skip to content

Commit

Permalink
Merge pull request #1001 from hylo-lang/array
Browse files Browse the repository at this point in the history
Add Array to standard library
  • Loading branch information
kyouko-taiga authored Sep 20, 2023
2 parents 61b9d90 + 6a90e54 commit e2c62c0
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 13 deletions.
118 changes: 118 additions & 0 deletions Library/Hylo/Array.hylo
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/// An ordered, random-access collection.
public type Array<Element: Movable & Deinitializable>: Deinitializable {

/// The out-of-line storage of the array.
///
/// The header of the buffer indicates the number of elements contained in the array.
var storage: DynamicBuffer<Int, Element>

/// Creates a new, empty array.
public init() {
&storage = .new()
}

/// Deinitializes `self`.
public fun deinit() sink {
var i = 0
while i < count() {
&pointer_to_element(at: i).unsafe_pointee().deinit()
&i += 1
}
}

/// The number of elements in the array.
public fun count() -> Int {
if storage.capacity() == 0 { 0 } else { storage.header.copy() }
}

/// The number of elements that can be stored in the array before new storage must be allocated.
public fun capacity() -> Int {
return storage.capacity()
}

/// Reserves enough space to store `n` elements
public fun reserve_capacity(_ n: Int) inout {
if n < capacity() { return }

var new_capacity = max[1, capacity()].copy()
while new_capacity < n {
&new_capacity += new_capacity.copy()
}

// TODO: Call `self.copy()` directly in the lambda.
let c = count()
var new_storage = DynamicBuffer<Int, Element>(
capacity: new_capacity,
initializing_header_with: fun (_ h: set Int) -> Void { &h = c.copy() })

var i = 0
var e = storage.first_element_address()
var f = new_storage.first_element_address()
while i < count() {
f.unsafe_initialize_pointee(e.unsafe_pointee())
&e = e.advance(by: 1)
&f = f.advance(by: 1)
&i += 1
}

// Deinitializing the `self.storage` is safe at this point because all its elements must have
// been moved to `new_storage`.
&storage = new_storage
}

/// 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)
&storage.header += 1
}

/// Accesses the element at `position`.
///
/// - Requires: `position` is in the range `0 ..< count()`.
public subscript(_ position: Int): Element {
let {
// precondition(position >= 0 && position < count())
pointer_to_element(at: position).unsafe[]
}
inout {
// precondition(position >= 0 && position < count())
pointer_to_element(at: position).unsafe[]
}
}

/// Returns the address of the element at `position`.
///
/// - Requires: `position` is in the range `0 ..< capacity()`.
fun pointer_to_element(at position: Int) -> PointerToMutable<Element> {
storage.first_element_address().advance(by: position)
}

}

/*

// TODO: Make Array conform to Regular instead of Deinitializable once #1002 is fixed.
// Currently that issue prevents the copy() function below from compiling.
//
// Error is "type 'Element' does not conform to trait 'Movable'"

public conformance Array: Equatable {

/// Returns `true` iff `other` has an equivalent value.
public fun infix== (_ other: Self) -> Bool {
// TODO
return true
}

}

public conformance Array: Copyable {
/// Returns an equivalent instance.
public fun copy() -> Self {
// TODO
.new()
}
}

*/
4 changes: 2 additions & 2 deletions Library/Hylo/Core/PointerToMutable.hylo
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public type PointerToMutable<Pointee>: Regular {
}

/// Returns `self` offset forward by `n` array elements of `Pointee` type.
public fun advance( by n: Int ) -> Self {
public fun advance(by n: Int) -> Self {
let offset_in_bytes = MemoryLayout<Pointee>.stride() * n
return PointerToMutable<Pointee>.new(
base: Builtin.advanced_by_bytes_word( base, offset_in_bytes.value ) )
base: Builtin.advanced_by_bytes_word(base, offset_in_bytes.value))
}

/// Creates an instance that does not address any usable storage.
Expand Down
36 changes: 31 additions & 5 deletions Library/Hylo/DynamicBuffer.hylo
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// - Warning: The deinitializer of `DynamicBuffer` does not deinitialize the elements that may
/// be stored in its payload. You must ensure that they are properly deinitialized before
/// `deinit` is called.
public type DynamicBuffer<Header: Deinitializable, Element>: Deinitializable {
public type DynamicBuffer<Header: Deinitializable, Element>: Deinitializable, Movable {

/// The description of payloads in `DynamicBuffer`s.
typealias BufferHeader = {
Expand All @@ -16,7 +16,7 @@ public type DynamicBuffer<Header: Deinitializable, Element>: Deinitializable {
static fun payload_offset() -> Int {
let a = MemoryLayout<Element>.alignment()
let s = MemoryLayout<BufferHeader>.size()
return s + (a - s % a)
return s + (a - s) % a
}

/// A pointer to the base of the buffer's out-of-line storage.
Expand Down Expand Up @@ -49,11 +49,12 @@ public type DynamicBuffer<Header: Deinitializable, Element>: Deinitializable {
}

/// Returns the address of the initial element.
/// - Precondition: `capacity != 0`
///
/// - Requires: `capacity() > 0`.
public fun first_element_address() -> PointerToMutable<Element> {
// TODO: Uncomment the following line when #995 is fixed
// precondition(self.capacity() == 0)
return PointerToMutable<Element>(type_punning: storage.advance(by_bytes: payload_offset()))
// precondition(self.capacity() > 0)
PointerToMutable<Element>(type_punning: storage.advance(by_bytes: payload_offset()))
}

/// Deinitializes `self`.
Expand All @@ -73,4 +74,29 @@ public type DynamicBuffer<Header: Deinitializable, Element>: Deinitializable {
return p.unsafe[].0.copy()
}

/// Accesses the header of `self`.
///
/// - Requires: `capacity() > 0`.
public property header: Header {
let { buffer_header().unsafe[].1 }
inout { &buffer_header().unsafe[].1 }
}

/// Returns the address of the `self`'s header.
fun buffer_header() -> PointerToMutable<BufferHeader> {
PointerToMutable<BufferHeader>(type_punning: storage)
}

}

// TODO: This trait was added as a workaround for #1002 since DynamicBuffer
// is initialized in the Array constructor
public conformance DynamicBuffer: Movable {

fun take_value(from source: sink Self) -> {self: Self, Void} {
// TODO: Fix defects preventing the following line from working
// &storage.base = source.storage.base
(self: source, ())
}

}
5 changes: 1 addition & 4 deletions Sources/CodeGen/LLVM/Transpilation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -903,10 +903,7 @@ extension LLVM.Module {
let base = llvm(s.operands[0])
let byteOffset = llvm(s.operands[1])
register[.register(i)] = insertGetElementPointerInBounds(
of: base,
typed: ptr,
indices: [byteOffset],
at: insertionPoint)
of: base, typed: i8, indices: [byteOffset], at: insertionPoint)

default:
unreachable("unexpected LLVM instruction '\(s.instruction)'")
Expand Down
4 changes: 2 additions & 2 deletions Sources/Core/NativeInstruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ extension NativeInstruction: CustomStringConvertible {
return "fptosi_\(l)_\(r)"
case .zeroinitializer(let t):
return "zeroinitializer_\(t)"
case .advancedByBytes(let byteOffset):
return "advanced_by_bytes_\(byteOffset)"
case .advancedByBytes(let t):
return "advanced_by_bytes_\(t)"
}
}

Expand Down
22 changes: 22 additions & 0 deletions Tests/LibraryTests/TestCases/ArrayTests.hylo
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//- compileAndRun expecting: success

fun test_init_empty() {
var d = Array<Int>()
precondition(d.count() == 0)
}

fun test_append() {
var d = Array<Int>()
&d.append(21)
&d.append(42)
&d.append(84)

precondition(d[0] == 21)
precondition(d[1] == 42)
precondition(d[2] == 84)
}

public fun main() {
test_init_empty()
test_append()
}

0 comments on commit e2c62c0

Please sign in to comment.