@@ -45,8 +45,7 @@ pub(crate) type InstructionId = Id<Instruction>;
45
45
/// - Opcodes which the IR knows the target machine has
46
46
/// special support for. (LowLevel)
47
47
/// - Opcodes which have no function definition in the
48
- /// source code and must be processed by the IR. An example
49
- /// of this is println.
48
+ /// source code and must be processed by the IR.
50
49
#[ derive( Debug , Copy , Clone , PartialEq , Eq , Hash , Serialize , Deserialize ) ]
51
50
pub ( crate ) enum Intrinsic {
52
51
ArrayLen ,
@@ -71,6 +70,8 @@ pub(crate) enum Intrinsic {
71
70
IsUnconstrained ,
72
71
DerivePedersenGenerators ,
73
72
FieldLessThan ,
73
+ ArrayRefCount ,
74
+ SliceRefCount ,
74
75
}
75
76
76
77
impl std:: fmt:: Display for Intrinsic {
@@ -100,6 +101,8 @@ impl std::fmt::Display for Intrinsic {
100
101
Intrinsic :: IsUnconstrained => write ! ( f, "is_unconstrained" ) ,
101
102
Intrinsic :: DerivePedersenGenerators => write ! ( f, "derive_pedersen_generators" ) ,
102
103
Intrinsic :: FieldLessThan => write ! ( f, "field_less_than" ) ,
104
+ Intrinsic :: ArrayRefCount => write ! ( f, "array_refcount" ) ,
105
+ Intrinsic :: SliceRefCount => write ! ( f, "slice_refcount" ) ,
103
106
}
104
107
}
105
108
}
@@ -108,11 +111,18 @@ impl Intrinsic {
108
111
/// Returns whether the `Intrinsic` has side effects.
109
112
///
110
113
/// If there are no side effects then the `Intrinsic` can be removed if the result is unused.
114
+ ///
115
+ /// An example of a side effect is increasing the reference count of an array, but functions
116
+ /// which can fail due to implicit constraints are also considered to have a side effect.
111
117
pub ( crate ) fn has_side_effects ( & self ) -> bool {
112
118
match self {
113
119
Intrinsic :: AssertConstant
114
120
| Intrinsic :: StaticAssert
115
121
| Intrinsic :: ApplyRangeConstraint
122
+ // Array & slice ref counts are treated as having side effects since they operate
123
+ // on hidden variables on otherwise identical array values.
124
+ | Intrinsic :: ArrayRefCount
125
+ | Intrinsic :: SliceRefCount
116
126
| Intrinsic :: AsWitness => true ,
117
127
118
128
// These apply a constraint that the input must fit into a specified number of limbs.
@@ -144,6 +154,39 @@ impl Intrinsic {
144
154
}
145
155
}
146
156
157
+ /// Intrinsics which only have a side effect due to the chance that
158
+ /// they can fail a constraint can be deduplicated.
159
+ pub ( crate ) fn can_be_deduplicated ( & self , deduplicate_with_predicate : bool ) -> bool {
160
+ match self {
161
+ // These apply a constraint in the form of ACIR opcodes, but they can be deduplicated
162
+ // if the inputs are the same. If they depend on a side effect variable (e.g. because
163
+ // they were in an if-then-else) then `handle_instruction_side_effects` in `flatten_cfg`
164
+ // will have attached the condition variable to their inputs directly, so they don't
165
+ // directly depend on the corresponding `enable_side_effect` instruction any more.
166
+ // However, to conform with the expectations of `Instruction::can_be_deduplicated` and
167
+ // `constant_folding` we only use this information if the caller shows interest in it.
168
+ Intrinsic :: ToBits ( _)
169
+ | Intrinsic :: ToRadix ( _)
170
+ | Intrinsic :: BlackBox (
171
+ BlackBoxFunc :: MultiScalarMul
172
+ | BlackBoxFunc :: EmbeddedCurveAdd
173
+ | BlackBoxFunc :: RecursiveAggregation ,
174
+ ) => deduplicate_with_predicate,
175
+
176
+ // Operations that remove items from a slice don't modify the slice, they just assert it's non-empty.
177
+ Intrinsic :: SlicePopBack | Intrinsic :: SlicePopFront | Intrinsic :: SliceRemove => {
178
+ deduplicate_with_predicate
179
+ }
180
+
181
+ Intrinsic :: AssertConstant
182
+ | Intrinsic :: StaticAssert
183
+ | Intrinsic :: ApplyRangeConstraint
184
+ | Intrinsic :: AsWitness => deduplicate_with_predicate,
185
+
186
+ _ => !self . has_side_effects ( ) ,
187
+ }
188
+ }
189
+
147
190
/// Lookup an Intrinsic by name and return it if found.
148
191
/// If there is no such intrinsic by that name, None is returned.
149
192
pub ( crate ) fn lookup ( name : & str ) -> Option < Intrinsic > {
@@ -171,6 +214,8 @@ impl Intrinsic {
171
214
"is_unconstrained" => Some ( Intrinsic :: IsUnconstrained ) ,
172
215
"derive_pedersen_generators" => Some ( Intrinsic :: DerivePedersenGenerators ) ,
173
216
"field_less_than" => Some ( Intrinsic :: FieldLessThan ) ,
217
+ "array_refcount" => Some ( Intrinsic :: ArrayRefCount ) ,
218
+ "slice_refcount" => Some ( Intrinsic :: SliceRefCount ) ,
174
219
175
220
other => BlackBoxFunc :: lookup ( other) . map ( Intrinsic :: BlackBox ) ,
176
221
}
@@ -235,7 +280,7 @@ pub(crate) enum Instruction {
235
280
/// - `code1` will have side effects iff `condition1` evaluates to `true`
236
281
///
237
282
/// This instruction is only emitted after the cfg flattening pass, and is used to annotate
238
- /// instruction regions with an condition that corresponds to their position in the CFG's
283
+ /// instruction regions with a condition that corresponds to their position in the CFG's
239
284
/// if-branching structure.
240
285
EnableSideEffectsIf { condition : ValueId } ,
241
286
@@ -270,9 +315,6 @@ pub(crate) enum Instruction {
270
315
/// else_value
271
316
/// }
272
317
/// ```
273
- ///
274
- /// Where we save the result of !then_condition so that we have the same
275
- /// ValueId for it each time.
276
318
IfElse { then_condition : ValueId , then_value : ValueId , else_value : ValueId } ,
277
319
278
320
/// Creates a new array or slice.
@@ -320,10 +362,53 @@ impl Instruction {
320
362
matches ! ( self . result_type( ) , InstructionResultType :: Unknown )
321
363
}
322
364
365
+ /// Indicates if the instruction has a side effect, ie. it can fail, or it interacts with memory.
366
+ ///
367
+ /// This is similar to `can_be_deduplicated`, but it doesn't depend on whether the caller takes
368
+ /// constraints into account, because it might not use it to isolate the side effects across branches.
369
+ pub ( crate ) fn has_side_effects ( & self , dfg : & DataFlowGraph ) -> bool {
370
+ use Instruction :: * ;
371
+
372
+ match self {
373
+ // These either have side-effects or interact with memory
374
+ EnableSideEffectsIf { .. }
375
+ | Allocate
376
+ | Load { .. }
377
+ | Store { .. }
378
+ | IncrementRc { .. }
379
+ | DecrementRc { .. } => true ,
380
+
381
+ Call { func, .. } => match dfg[ * func] {
382
+ Value :: Intrinsic ( intrinsic) => intrinsic. has_side_effects ( ) ,
383
+ _ => true , // Be conservative and assume other functions can have side effects.
384
+ } ,
385
+
386
+ // These can fail.
387
+ Constrain ( ..) | RangeCheck { .. } => true ,
388
+
389
+ // This should never be side-effectful
390
+ MakeArray { .. } => false ,
391
+
392
+ // These can have different behavior depending on the EnableSideEffectsIf context.
393
+ Binary ( _)
394
+ | Cast ( _, _)
395
+ | Not ( _)
396
+ | Truncate { .. }
397
+ | IfElse { .. }
398
+ | ArrayGet { .. }
399
+ | ArraySet { .. } => self . requires_acir_gen_predicate ( dfg) ,
400
+ }
401
+ }
402
+
323
403
/// Indicates if the instruction can be safely replaced with the results of another instruction with the same inputs.
324
404
/// If `deduplicate_with_predicate` is set, we assume we're deduplicating with the instruction
325
405
/// and its predicate, rather than just the instruction. Setting this means instructions that
326
406
/// rely on predicates can be deduplicated as well.
407
+ ///
408
+ /// Some instructions get the predicate attached to their inputs by `handle_instruction_side_effects` in `flatten_cfg`.
409
+ /// These can be deduplicated because they implicitly depend on the predicate, not only when the caller uses the
410
+ /// predicate variable as a key to cache results. However, to avoid tight coupling between passes, we make the deduplication
411
+ /// conditional on whether the caller wants the predicate to be taken into account or not.
327
412
pub ( crate ) fn can_be_deduplicated (
328
413
& self ,
329
414
dfg : & DataFlowGraph ,
@@ -341,7 +426,9 @@ impl Instruction {
341
426
| DecrementRc { .. } => false ,
342
427
343
428
Call { func, .. } => match dfg[ * func] {
344
- Value :: Intrinsic ( intrinsic) => !intrinsic. has_side_effects ( ) ,
429
+ Value :: Intrinsic ( intrinsic) => {
430
+ intrinsic. can_be_deduplicated ( deduplicate_with_predicate)
431
+ }
345
432
_ => false ,
346
433
} ,
347
434
@@ -403,6 +490,7 @@ impl Instruction {
403
490
// Explicitly allows removal of unused ec operations, even if they can fail
404
491
Value :: Intrinsic ( Intrinsic :: BlackBox ( BlackBoxFunc :: MultiScalarMul ) )
405
492
| Value :: Intrinsic ( Intrinsic :: BlackBox ( BlackBoxFunc :: EmbeddedCurveAdd ) ) => true ,
493
+
406
494
Value :: Intrinsic ( intrinsic) => !intrinsic. has_side_effects ( ) ,
407
495
408
496
// All foreign functions are treated as having side effects.
@@ -418,7 +506,7 @@ impl Instruction {
418
506
}
419
507
}
420
508
421
- /// If true the instruction will depends on enable_side_effects context during acir-gen
509
+ /// If true the instruction will depend on ` enable_side_effects` context during acir-gen.
422
510
pub ( crate ) fn requires_acir_gen_predicate ( & self , dfg : & DataFlowGraph ) -> bool {
423
511
match self {
424
512
Instruction :: Binary ( binary)
0 commit comments