electra 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE-SPIRV-Headers.txt +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/examples/example_shaders.rb +55 -0
- data/lib/electra/binary.rb +475 -0
- data/lib/electra/data_compat.rb +25 -0
- data/lib/electra/function.rb +380 -0
- data/lib/electra/grammar.rb +968 -0
- data/lib/electra/module.rb +299 -0
- data/lib/electra/type.rb +11 -0
- data/lib/electra/value.rb +39 -0
- data/lib/electra/version.rb +6 -0
- data/lib/electra.rb +32 -0
- data/sig/electra.rbs +105 -0
- metadata +58 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Electra
|
|
4
|
+
# Small typed DSL for vertex and fragment shaders, not a general compiler.
|
|
5
|
+
class Function
|
|
6
|
+
attr_reader :owner, :id, :interfaces, :stage
|
|
7
|
+
|
|
8
|
+
def initialize(owner, stage)
|
|
9
|
+
@owner, @stage = owner, stage
|
|
10
|
+
@interfaces = []
|
|
11
|
+
@bindings = {}
|
|
12
|
+
@finished = false
|
|
13
|
+
@terminated = false
|
|
14
|
+
@id = owner.reserve_id
|
|
15
|
+
void = owner.type(:void)
|
|
16
|
+
signature = owner.type([:function, :void])
|
|
17
|
+
owner.emit(:functions, "OpFunction", void.id, id, :None, signature.id)
|
|
18
|
+
start_block(owner.reserve_id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Value] Input variable; specify exactly one location or builtin
|
|
22
|
+
def input(type, location: nil, builtin: nil, flat: false)
|
|
23
|
+
define_interface(:Input, type, location:, builtin:, flat:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Value] writable Output variable
|
|
27
|
+
def output(type, location: nil, builtin: nil)
|
|
28
|
+
define_interface(:Output, type, location:, builtin:)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Value] combined sampled-image descriptor
|
|
32
|
+
def sampled_image_2d(set: 0, binding: 0)
|
|
33
|
+
define_descriptor(owner.type([:sampled_image]), :UniformConstant, set, binding)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Explicit offsets/layout keep GPU ABI decisions visible to the caller.
|
|
37
|
+
def uniform_buffer(*types, set: 0, binding: 0, offsets:)
|
|
38
|
+
define_descriptor(owner.struct(*types, offsets:, block: true), :Uniform, set, binding)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Value] single read-only push-constant block
|
|
42
|
+
def push_constant(*types, offsets:)
|
|
43
|
+
raise Error, "only one push constant block is allowed per shader" if @push_constant
|
|
44
|
+
@push_constant = define_global(owner.struct(*types, offsets:, block: true), :PushConstant)
|
|
45
|
+
@interfaces << @push_constant.id if owner.version >= 0x10400
|
|
46
|
+
@push_constant
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Value] struct-member pointer; loads automatically in expressions
|
|
50
|
+
def member(buffer, index)
|
|
51
|
+
raise Error, "expected a struct variable" unless buffer.is_a?(Value) && buffer.storage && buffer.type.kind == :struct
|
|
52
|
+
owner.identifier(buffer)
|
|
53
|
+
raise Error, "buffer belongs to another function" unless buffer.function.equal?(self)
|
|
54
|
+
raise Error, "member out of bounds" unless index.is_a?(Integer) && index.between?(0, buffer.type.members.length - 1)
|
|
55
|
+
type = buffer.type.members[index]
|
|
56
|
+
pointer = owner.type([:pointer, buffer.storage, type])
|
|
57
|
+
access = result("OpAccessChain", pointer, buffer.id, constant(index, :int).id)
|
|
58
|
+
Value.new(owner, self, type, access.id, buffer.storage)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Value] typed constant; infer float/int/bool or float vector if omitted
|
|
62
|
+
def constant(item, type = nil)
|
|
63
|
+
type ||= case item
|
|
64
|
+
when true, false then :bool
|
|
65
|
+
when Integer then :int
|
|
66
|
+
when Numeric then :float
|
|
67
|
+
when Array then "vec#{item.length}".to_sym
|
|
68
|
+
else raise Error, "cannot infer constant type"
|
|
69
|
+
end
|
|
70
|
+
result = owner.constant(type, item)
|
|
71
|
+
Value.new(owner, self, result.type, result.id)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def value(item, expected = nil)
|
|
75
|
+
expected = owner.type(expected) if expected
|
|
76
|
+
item = constant(item, expected) unless item.is_a?(Value)
|
|
77
|
+
owner.identifier(item)
|
|
78
|
+
raise Error, "value belongs to another function" if item.function && !item.function.equal?(self)
|
|
79
|
+
raise Error, "type mismatch: expected #{expected.kind}, got #{item.type.kind}" if expected && expected.id != item.type.id
|
|
80
|
+
item.load
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Value] composite assembled from typed component values
|
|
84
|
+
def construct(description, *items)
|
|
85
|
+
type = owner.type(description)
|
|
86
|
+
raise Error, "construct supports vectors, matrices, arrays and structs" unless %i[vector matrix array struct].include?(type.kind)
|
|
87
|
+
expected = type.kind == :struct ? type.members.length : type.length
|
|
88
|
+
raise Error, "wrong composite element count" unless items.length == expected
|
|
89
|
+
components = items.each_with_index.map { |item, index| value(item, type.kind == :struct ? type.members[index] : type.element).id }
|
|
90
|
+
result("OpCompositeConstruct", type, *components)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [Value] vector containing the scalar repeated count times
|
|
94
|
+
def splat(item, count = 4)
|
|
95
|
+
item = value(item)
|
|
96
|
+
type = owner.type([:vector, item.type, count])
|
|
97
|
+
result("OpCompositeConstruct", type, *Array.new(count, item.id))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [Value] vec4 sample; implicit LOD is restricted to fragments
|
|
101
|
+
def sample(texture, uv, lod: nil)
|
|
102
|
+
texture = value(texture)
|
|
103
|
+
raise Error, "expected a sampled 2D image" unless texture.type.kind == :sampled_image
|
|
104
|
+
uv = value(uv, :vec2)
|
|
105
|
+
unless lod.nil?
|
|
106
|
+
result("OpImageSampleExplicitLod", owner.type(:vec4), texture.id, uv.id, :Lod, value(lod, :float).id)
|
|
107
|
+
else
|
|
108
|
+
raise Error, "implicit texture LOD is fragment-only" unless stage == :Fragment
|
|
109
|
+
result("OpImageSampleImplicitLod", owner.type(:vec4), texture.id, uv.id)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Value] component-wise sum of matching numeric operands
|
|
114
|
+
def add(left, right) = arithmetic(:add, left, right)
|
|
115
|
+
# @return [Value] component-wise difference
|
|
116
|
+
def sub(left, right) = arithmetic(:sub, left, right)
|
|
117
|
+
# @return [Value] float, signed or unsigned component-wise quotient
|
|
118
|
+
def div(left, right) = arithmetic(:div, left, right)
|
|
119
|
+
# @return [Value] float/signed modulus or unsigned remainder
|
|
120
|
+
def mod(left, right) = arithmetic(:mod, left, right)
|
|
121
|
+
|
|
122
|
+
# @return [Value] numeric, vector/scalar or dimension-checked matrix product
|
|
123
|
+
def mul(left, right)
|
|
124
|
+
left = value(left)
|
|
125
|
+
if right.is_a?(Numeric) && [:matrix, :vector].include?(left.type.kind) && (left.type.kind == :matrix || left.type.element.kind == :float)
|
|
126
|
+
right = value(right, :float)
|
|
127
|
+
else
|
|
128
|
+
right = value(right, left.type) unless right.is_a?(Value)
|
|
129
|
+
end
|
|
130
|
+
right = value(right)
|
|
131
|
+
kinds = [left.type.kind, right.type.kind]
|
|
132
|
+
if kinds == [:matrix, :vector]
|
|
133
|
+
raise Error, "matrix/vector dimensions differ" unless left.type.length == right.type.length && right.type.element.kind == :float
|
|
134
|
+
result("OpMatrixTimesVector", left.type.element, left.id, right.id)
|
|
135
|
+
elsif kinds == [:vector, :matrix]
|
|
136
|
+
raise Error, "vector/matrix dimensions differ" unless left.type.length == right.type.element.length && left.type.element.kind == :float
|
|
137
|
+
result("OpVectorTimesMatrix", owner.type([:vector, :float, right.type.length]), left.id, right.id)
|
|
138
|
+
elsif kinds == [:matrix, :matrix]
|
|
139
|
+
raise Error, "matrix dimensions differ" unless left.type.length == right.type.element.length
|
|
140
|
+
result("OpMatrixTimesMatrix", owner.type([:matrix, left.type.element, right.type.length]), left.id, right.id)
|
|
141
|
+
elsif [:matrix, :vector].include?(left.type.kind) && right.type.kind == :float && (left.type.kind == :matrix || left.type.element.kind == :float)
|
|
142
|
+
result(left.type.kind == :matrix ? "OpMatrixTimesScalar" : "OpVectorTimesScalar", left.type, left.id, right.id)
|
|
143
|
+
elsif left.type.kind == :float && [:matrix, :vector].include?(right.type.kind)
|
|
144
|
+
mul(right, left)
|
|
145
|
+
else
|
|
146
|
+
arithmetic(:mul, left, right)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def negate(item)
|
|
151
|
+
item = value(item)
|
|
152
|
+
raise Error, "negation requires signed numeric type" unless %i[float int].include?(item.type.scalar.kind)
|
|
153
|
+
result(item.type.scalar.kind == :float ? "OpFNegate" : "OpSNegate", item.type, item.id)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def dot(left, right)
|
|
157
|
+
left = value(left)
|
|
158
|
+
right = value(right, left.type)
|
|
159
|
+
raise Error, "dot requires float vectors" unless left.type.kind == :vector && left.type.element.kind == :float
|
|
160
|
+
result("OpDot", owner.type(:float), left.id, right.id)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def transpose(item)
|
|
164
|
+
item = value(item)
|
|
165
|
+
raise Error, "transpose requires a matrix" unless item.type.kind == :matrix
|
|
166
|
+
type = owner.type([:matrix, [:vector, :float, item.type.length], item.type.element.length])
|
|
167
|
+
result("OpTranspose", type, item.id)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def convert(item, description)
|
|
171
|
+
item = value(item)
|
|
172
|
+
type = owner.type(description)
|
|
173
|
+
from, to = item.type.scalar.kind, type.scalar.kind
|
|
174
|
+
raise Error, "conversion dimensions differ" unless item.type.length == type.length
|
|
175
|
+
return item if item.type.id == type.id
|
|
176
|
+
opcode = {[:float, :int] => "OpConvertFToS", [:float, :uint] => "OpConvertFToU", [:int, :float] => "OpConvertSToF", [:uint, :float] => "OpConvertUToF", [:int, :uint] => "OpBitcast", [:uint, :int] => "OpBitcast"}[[from, to]]
|
|
177
|
+
raise Error, "unsupported conversion" unless opcode
|
|
178
|
+
result(opcode, type, item.id)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# @return [Value] scalar/vector equality (ordered for floating-point inputs)
|
|
182
|
+
def equal(left, right) = compare(:equal, left, right)
|
|
183
|
+
# @return [Value] scalar/vector ordered inequality
|
|
184
|
+
def not_equal(left, right) = compare(:not_equal, left, right)
|
|
185
|
+
# @return [Value] component-wise less-than comparison
|
|
186
|
+
def less_than(left, right) = compare(:less_than, left, right)
|
|
187
|
+
# @return [Value] component-wise less-than-or-equal comparison
|
|
188
|
+
def less_equal(left, right) = compare(:less_equal, left, right)
|
|
189
|
+
# @return [Value] component-wise greater-than comparison
|
|
190
|
+
def greater_than(left, right) = compare(:greater_than, left, right)
|
|
191
|
+
# @return [Value] component-wise greater-than-or-equal comparison
|
|
192
|
+
def greater_equal(left, right) = compare(:greater_equal, left, right)
|
|
193
|
+
|
|
194
|
+
# @return [Value] component-wise boolean conjunction
|
|
195
|
+
def logical_and(left, right) = logical("OpLogicalAnd", left, right)
|
|
196
|
+
# @return [Value] component-wise boolean disjunction
|
|
197
|
+
def logical_or(left, right) = logical("OpLogicalOr", left, right)
|
|
198
|
+
def logical_not(item)
|
|
199
|
+
item = value(item)
|
|
200
|
+
raise Error, "logical operation requires bool" unless item.type.scalar.kind == :bool
|
|
201
|
+
result("OpLogicalNot", item.type, item.id)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def select(condition, yes, no)
|
|
205
|
+
condition = value(condition)
|
|
206
|
+
yes = value(yes)
|
|
207
|
+
no = value(no, yes.type)
|
|
208
|
+
expected = yes.type.kind == :vector ? owner.type([:vector, :bool, yes.type.length]) : owner.type(:bool)
|
|
209
|
+
condition = splat(condition, yes.type.length) if condition.type.kind == :bool && yes.type.kind == :vector
|
|
210
|
+
raise Error, "select condition has incompatible type" unless condition.type.id == expected.id
|
|
211
|
+
result("OpSelect", yes.type, condition.id, yes.id, no.id)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Branch callbacks may return a value; matching results become an OpPhi.
|
|
215
|
+
# With no returned value this is ordinary structured conditional control.
|
|
216
|
+
def if_else(condition, yes, no = nil)
|
|
217
|
+
condition = value(condition, :bool)
|
|
218
|
+
then_id, else_id, merge_id = 3.times.map { owner.reserve_id }
|
|
219
|
+
emit("OpSelectionMerge", merge_id, :None)
|
|
220
|
+
emit("OpBranchConditional", condition.id, then_id, else_id)
|
|
221
|
+
then_value, then_label, then_reachable = build_branch(then_id, merge_id) { yes.call(self) }
|
|
222
|
+
else_value, else_label, else_reachable = build_branch(else_id, merge_id) { no&.call(self) }
|
|
223
|
+
start_block(merge_id)
|
|
224
|
+
if then_reachable && else_reachable && then_value.is_a?(Value) && else_value.is_a?(Value)
|
|
225
|
+
raise Error, "branch result types differ" unless then_value.type.id == else_value.type.id
|
|
226
|
+
result("OpPhi", then_value.type, then_value.id, then_label, else_value.id, else_label)
|
|
227
|
+
elsif !then_reachable && !else_reachable
|
|
228
|
+
emit("OpUnreachable")
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def discard
|
|
234
|
+
raise Error, "discard is fragment-only" unless stage == :Fragment
|
|
235
|
+
emit("OpKill")
|
|
236
|
+
nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Discard the fragment when the scalar boolean condition is true.
|
|
240
|
+
# @return [nil]
|
|
241
|
+
def discard_if(condition)
|
|
242
|
+
if_else(condition, ->(function) { function.discard })
|
|
243
|
+
nil
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Terminate the current block with a void return.
|
|
247
|
+
# @return [nil]
|
|
248
|
+
def return_void
|
|
249
|
+
emit("OpReturn")
|
|
250
|
+
nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# GLSL.std.450 operations needed for UI gradients, clipping and transforms.
|
|
254
|
+
def ext(operation, *items)
|
|
255
|
+
names = {round: "Round", abs: "FAbs", floor: "Floor", ceil: "Ceil", fract: "Fract", sin: "Sin", cos: "Cos", pow: "Pow", exp: "Exp", log: "Log", sqrt: "Sqrt", inverse_sqrt: "InverseSqrt", min: "FMin", max: "FMax", clamp: "FClamp", mix: "FMix", step: "Step", smoothstep: "SmoothStep", length: "Length", normalize: "Normalize"}
|
|
256
|
+
arities = {pow: 2, min: 2, max: 2, clamp: 3, mix: 3, step: 2, smoothstep: 3}
|
|
257
|
+
name = names[operation] or raise Error, "unknown GLSL operation #{operation}"
|
|
258
|
+
opcode = GLSL_INSTRUCTIONS.fetch(name)
|
|
259
|
+
raise Error, "wrong GLSL operand count" unless items.length == arities.fetch(operation, 1)
|
|
260
|
+
first = value(items.first)
|
|
261
|
+
raise Error, "GLSL operation requires float scalar/vector" unless first.type.scalar.kind == :float
|
|
262
|
+
operands = [first, *items.drop(1).map { |item| value(item, first.type) }]
|
|
263
|
+
type = operation == :length ? owner.type(:float) : first.type
|
|
264
|
+
result("OpExtInst", type, owner.import("GLSL.std.450"), opcode, *operands.map(&:id))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def emit(opcode, *operands)
|
|
268
|
+
raise Error, "function already finished" if @finished
|
|
269
|
+
raise Error, "cannot emit after block terminator" if @terminated
|
|
270
|
+
owner.emit(:functions, opcode, *operands)
|
|
271
|
+
@terminated = true if %w[OpBranch OpBranchConditional OpSwitch OpKill OpReturn OpReturnValue OpUnreachable].include?(opcode)
|
|
272
|
+
self
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Low-level typed instruction with an automatically allocated result ID.
|
|
276
|
+
# @return [Value]
|
|
277
|
+
def result(opcode, type, *operands)
|
|
278
|
+
type = owner.type(type)
|
|
279
|
+
id = owner.reserve_id
|
|
280
|
+
emit(opcode, type.id, id, *operands)
|
|
281
|
+
Value.new(owner, self, type, id)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Close the function, inserting a return for an unterminated block.
|
|
285
|
+
# @return [self]
|
|
286
|
+
def finish
|
|
287
|
+
return self if @finished
|
|
288
|
+
emit("OpReturn") unless @terminated
|
|
289
|
+
owner.emit(:functions, "OpFunctionEnd")
|
|
290
|
+
@finished = true
|
|
291
|
+
self
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def finished? = @finished
|
|
295
|
+
|
|
296
|
+
private
|
|
297
|
+
|
|
298
|
+
def build_branch(label_id, merge_id)
|
|
299
|
+
start_block(label_id)
|
|
300
|
+
branch_value = yield
|
|
301
|
+
branch_label = @current_label
|
|
302
|
+
reachable = !@terminated
|
|
303
|
+
branch_value = value(branch_value) if reachable && branch_value.is_a?(Value)
|
|
304
|
+
emit("OpBranch", merge_id) if reachable
|
|
305
|
+
[branch_value, branch_label, reachable]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def start_block(id)
|
|
309
|
+
owner.emit(:functions, "OpLabel", id)
|
|
310
|
+
@current_label = id
|
|
311
|
+
@terminated = false
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def define_interface(storage, description, location: nil, builtin: nil, flat: false)
|
|
315
|
+
raise Error, "provide exactly one of location or builtin" unless location.nil? != builtin.nil?
|
|
316
|
+
type = owner.type(description)
|
|
317
|
+
key = [storage, location, builtin]
|
|
318
|
+
raise Error, "duplicate shader interface" if @bindings[key]
|
|
319
|
+
@bindings[key] = true
|
|
320
|
+
variable = define_global(type, storage)
|
|
321
|
+
owner.decorate(variable, :Location, location) unless location.nil?
|
|
322
|
+
owner.decorate(variable, :BuiltIn, builtin) unless builtin.nil?
|
|
323
|
+
owner.decorate(variable, :Flat) if flat
|
|
324
|
+
@interfaces << variable.id
|
|
325
|
+
variable
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def define_descriptor(type, storage, set, binding)
|
|
329
|
+
key = [set, binding]
|
|
330
|
+
raise Error, "duplicate descriptor binding" if @bindings[key]
|
|
331
|
+
@bindings[key] = true
|
|
332
|
+
variable = define_global(type, storage)
|
|
333
|
+
owner.decorate(variable, :DescriptorSet, set)
|
|
334
|
+
owner.decorate(variable, :Binding, binding)
|
|
335
|
+
@interfaces << variable.id if owner.version >= 0x10400
|
|
336
|
+
variable
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def define_global(type, storage)
|
|
340
|
+
pointer = owner.type([:pointer, storage, type])
|
|
341
|
+
id = owner.reserve_id
|
|
342
|
+
owner.emit(:declarations, "OpVariable", pointer.id, id, storage)
|
|
343
|
+
Value.new(owner, self, type, id, storage)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def arithmetic(operation, left, right)
|
|
347
|
+
left = value(left)
|
|
348
|
+
right = value(right, left.type)
|
|
349
|
+
kind = left.type.scalar.kind
|
|
350
|
+
raise Error, "arithmetic requires numeric scalars or vectors" unless %i[float int uint].include?(kind)
|
|
351
|
+
codes = {add: %w[OpFAdd OpIAdd OpIAdd], sub: %w[OpFSub OpISub OpISub], mul: %w[OpFMul OpIMul OpIMul], div: %w[OpFDiv OpSDiv OpUDiv], mod: %w[OpFMod OpSMod OpUMod]}
|
|
352
|
+
result(codes.fetch(operation)[%i[float int uint].index(kind)], left.type, left.id, right.id)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def compare(operation, left, right)
|
|
356
|
+
left = value(left)
|
|
357
|
+
right = value(right, left.type)
|
|
358
|
+
kind = left.type.scalar.kind
|
|
359
|
+
endings = {equal: "Equal", not_equal: "NotEqual", less_than: "LessThan", less_equal: "LessThanEqual", greater_than: "GreaterThan", greater_equal: "GreaterThanEqual"}
|
|
360
|
+
prefix = case kind
|
|
361
|
+
when :float then "FOrd"
|
|
362
|
+
when :int then %i[equal not_equal].include?(operation) ? "I" : "S"
|
|
363
|
+
when :uint then %i[equal not_equal].include?(operation) ? "I" : "U"
|
|
364
|
+
when :bool
|
|
365
|
+
raise Error, "booleans are not ordered" unless %i[equal not_equal].include?(operation)
|
|
366
|
+
"Logical"
|
|
367
|
+
else raise Error, "comparison requires scalars or vectors"
|
|
368
|
+
end
|
|
369
|
+
type = left.type.kind == :vector ? owner.type([:vector, :bool, left.type.length]) : owner.type(:bool)
|
|
370
|
+
result("Op#{prefix}#{endings.fetch(operation)}", type, left.id, right.id)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def logical(opcode, left, right)
|
|
374
|
+
left = value(left)
|
|
375
|
+
right = value(right, left.type)
|
|
376
|
+
raise Error, "logical operation requires bool" unless left.type.scalar.kind == :bool
|
|
377
|
+
result(opcode, left.type, left.id, right.id)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|