How can understand tf_executor.graph operation in TensorFlow Executor Dialect?

shows a tf_executor.graph

%fetches = tf_executor.graph : tensor<*xf32> {
  // Operations in the current block execute when their inputs are ready,
  // possibly concurrently.
  // Only operations in the tf_executor dialect are expected here.
  // Ops can return multiple outputs and a control token for control
  // dependencies.
  // We don’t mention the control token in the return type here, it is implicit.
  %0, %ctl0 = tf_executor.opA %feed#0, %feed#1 : tensor<*xf32>
  %1, %ctl1 = tf_executor.opB : tensor<*xf32>
  %2, %ctl2 = tf_executor.opC %1, %ctl0 : tensor<*xf32>
  %3, %ctl3 = tf_executor.opD %2 : tensor<*xf32>
  tf_executor.fetch %3 : tensor<*xf32>
} // end of the “tf_executor.graph" operation/region

the content wrapped by {} is region,

however, when checking region defination mentioned in MLIR Language Reference - MLIR

  "any_op"(%a) ({ // if %a is in-scope in the containing region...
	 // then %a is in-scope here too.
    %new_value = "another_op"(%a) : (i64) -> (i64)
  }) : (i64) -> (i64)

as you can see, parentheses in tf_executor.graph is missing.

I failed to understand it, anyone can help ?

MLIR dialect can provide a custom assembly printing for their operation (see chapter 2 of the tutorial: Chapter 2: Emitting Basic MLIR - MLIR ).

You can always provide the custom IR to tf-opt and have it print the generic form using the --mlir-print-op-generic option.

In this case the tf_executor.graph is effectively creating a nested region, even though it skips the parentheses (most operation with a custom assembly will skip these: see scf.for, affine.if, etc. ; or even the most obvious: func!).

thank you for replying

both and doesnot mention the parenttheses for region can be ignored
if it can be ignored, will it conflict with dictionary-attribute ?

operation            ::= op-result-list? (generic-operation | custom-operation)
generic-operation    ::= string-literal `(` value-use-list? `)`  successor-list?
                         region-list? dictionary-attribute? `:` function-type
custom-operation     ::= bare-id custom-operation-format
op-result-list       ::= op-result (`,` op-result)* `=`
op-result            ::= value-id (`:` integer-literal)
successor-list       ::= `[` successor (`,` successor)* `]`
successor            ::= caret-id (`:` bb-arg-list)?
**region-list          ::= `(` region (`,` region)* `)`**
**dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}`**
trailing-location    ::= (`loc` `(` location `)`)?

You seem to be confusing generic and custom forms. The generic form is quite rigid and, indeed, requires the region list to be parenthesized. The custom form has no such requirement. The only applicable rule from the BNF you quote is

operation            ::= op-result-list? (generic-operation | custom-operation)

with the custom-operation alternative selected. The custom operation format may be, for example,

custom-operation ::= `func` symbol `(` value-and-type-list `)` region? (`attributes` attr-dict)?

there the keyword serves for disambiguation. There can also be a mandatory region or any other way to disambiguate that makes sense for a specific operation and is supported by the parser.

1 Like

clear for me now
many thanks