-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvector.py
More file actions
194 lines (147 loc) · 6.07 KB
/
vector.py
File metadata and controls
194 lines (147 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
from typing import List, Tuple
from numpy.typing import ArrayLike, DTypeLike
import dataclasses as dc
import numpy as np
import compute_graph as cg
import scalar_lang as sl
import opcodes
@dc.dataclass
class Vector(cg.Data):
"A one-dimension array whose size and type is known at compile time."
length: int
dtype: np.dtype
address: sl.AllocateMemory
def set_name(self, num_allocations: int) -> None:
self.name = f"v{num_allocations}"
def referenced_ops(self) -> Tuple[sl.ScalarOp]:
return (self.address,)
def free(self, memory: sl.ScalarOp) -> sl.FreeMemory:
return sl.FreeMemory(memory, self.address)
@dc.dataclass
class ConstVector(Vector):
value: np.ndarray
address: sl.AllocateMemory
def set_name(self, num_allocations: int) -> None:
self.name = f"cv{num_allocations}"
class VectorOp(sl.ScalarOp):
def __hash__(self) -> int:
return id(self)
def AddressOf(self) -> sl.AllocateMemory:
return self.out.address
def BoundsCheck(self, offset: sl.ScalarOp) -> sl.ScalarOp:
bounds_checkings = False
if bounds_checkings:
offset = sl.immediate_optionally(offset)
return sl.If(offset >= self.out.length, sl.Halt(-1), self)
else:
return self
def __getitem__(self, i):
if isinstance(i, slice):
if (i.step != 1) and (i.step is not None):
raise NotImplementedError("steps other than 1 are not yet supported")
return Slice(self, i.start, i.stop)
else:
return LoadElement(self, i)
def new_value(self, parent: "VectorOp") -> "NewEmptyVector":
return NewEmptyVector(parent)
def copy_from(self, src: "VectorOp") -> "CopyByReference":
return CopyByReference(self, src)
class NewEmptyVector(VectorOp):
"Make a new vector with the same length, dtype, and address as `parent`."
def __init__(self, parent: VectorOp):
super().__init__(
out=Vector(
# Data args
name="<unnamed vector for phi>",
# Vector args
length=parent.out.length,
dtype=parent.out.dtype,
address=sl.NewValue(parent.out.address),
),
input_ops=(parent,),
)
class CopyByReference(VectorOp):
"Copy the address of one vector to the address of another."
def __init__(self, dst: VectorOp, src: VectorOp):
if dst.out.dtype != src.out.dtype:
raise TypeError("dst and src must have the same dtype")
if dst.out.length != src.out.length:
raise ValueError("dst and src must have the same length")
if isinstance(dst, Slice) or isinstance(src, Slice):
raise NotImplementedError("cannot copy slices by reference yet")
super().__init__(
out=dst.out,
input_ops=(dst, src, sl.CopyValue(dst.out.address, src.out.address)),
)
class Allocate(VectorOp):
"Create a vector with the given length and dtype and allocate storage for its data."
def __init__(self, memory: sl.ScalarOp, length: int, dtype: DTypeLike):
allocate_memory = sl.AllocateMemory(memory, length, dtype)
vec = Vector("<unnamed>", length, dtype, allocate_memory)
super().__init__(out=vec, input_ops=(allocate_memory,))
def extra_args(self) -> str:
return f"length={self.out.length}, dtype={self.out.dtype}"
class Constant(VectorOp):
def __init__(self, memory: sl.ScalarOp, value: ArrayLike):
value = np.asarray(value)
if value.ndim != 1:
raise ValueError("value must be a 1-dimensional array")
address = sl.AllocateMemory(memory, len(value), value.dtype)
copy = sl.CopyLiteralArray(address, value)
super().__init__(
out=ConstVector(
# Data args
name="<unnamed>",
# Vector args
length=len(value),
dtype=value.dtype,
# ConstVector args
value=value,
address=address,
),
input_ops=(copy, memory),
)
def extra_args(self) -> str:
return f"value={self.out.value}"
class Slice(VectorOp):
def __init__(self, vec: VectorOp, start: sl.ScalarOp, end: sl.ScalarOp):
start = sl.immediate_optionally(start)
end = sl.immediate_optionally(end)
super().__init__(out=vec.out, input_ops=(vec, start, end))
def BoundsCheck(self, offset: sl.ScalarOp) -> sl.ScalarOp:
_, start, _ = self.input_ops
return super().BoundsCheck(start + offset)
def AddressOf(self) -> sl.ScalarOp:
_, start, _ = self.input_ops
return super().AddressOf() + start
def Copy(
dst: VectorOp,
src: VectorOp,
size: sl.ScalarOp,
dst_offset: sl.ScalarOp = 0,
src_offset: sl.ScalarOp = 0,
) -> VectorOp:
if isinstance(dst.out, ConstVector):
raise TypeError("dst must be mutable")
if isinstance(dst, Slice) or isinstance(src, Slice):
raise NotImplementedError("cannot copy slices yet")
copy = sl.CopyMemory(
dst.BoundsCheck(dst_offset + size),
dst.AddressOf() + dst_offset,
src.AddressOf() + src_offset,
size,
)
# TODO: just return the copy here instead of creating a new VectorOp.
return VectorOp(out=dst.out, input_ops=(copy, src))
def LoadElement(src: VectorOp, offset: sl.ScalarOp) -> sl.ScalarOp:
return sl.Load(src.BoundsCheck(offset), src.AddressOf() + offset)
# TODO: This should be a VectorOp class. Otherwise it creates an unhelpful VectorOp.
def StoreElement(dst: VectorOp, offset: sl.ScalarOp, value: sl.ScalarOp) -> VectorOp:
if isinstance(dst.out, ConstVector):
raise TypeError("dst must be mutable")
store = sl.Store(dst.BoundsCheck(offset), dst.AddressOf() + offset, value)
return VectorOp(out=dst.out, input_ops=(store,))
def Clone(v: VectorOp) -> VectorOp:
return Copy(dst=Allocate(v, v.out.length, v.out.dtype), src=v, size=v.out.length)
def lower(program: cg.Operator) -> List[opcodes.OpCode]:
return sl.lower(program)