truffle/docs/bytecode_dsl/ShortCircuitOperations.md
One limitation of regular operations is that they are eager: all of the child operations are evaluated before the operation itself evaluates.
Many languages define short-circuiting operators (e.g., Java's &&) which can evaluate a subset of their operands, terminating early when an operand meets a particular condition (e.g., when it is true).
The Bytecode DSL allows you to define @ShortCircuitOperations to implement short-circuiting behaviour.
A short-circuit operation implements AND or OR semantics, executing each child operation until the first false or true value, respectively.
By default, the operands to a short-circuit operation must be boolean so that they can be compared with false/true.
Below we define a simple short-circuit BoolOr operation by annotating the root class with @ShortCircuitOperation:
@GenerateBytecode(...)
@ShortCircuitOperation(
name = "BoolOr",
operator = ShortCircuitOperation.Operator.OR_RETURN_VALUE
)
public abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { ... }
BoolOr uses the OR_RETURN_VALUE operator.
This means it executes its operands until the first true value, and then produces that value as a result.
In pseudocode, it implements:
if (boolean) child_1.execute() == true:
return true
if (boolean) child_2.execute() == true:
return true
# ... more children
return child_n.execute()
Each operand is casted to boolean to perform the comparisons, so a non-boolean operand will cause a ClassCastException or NullPointerException to be thrown.
The last operand is not compared against true/false, so no cast is performed.
It is the language implementation's responsibility to ensure the last operand produces a boolean (or to properly handle a non-boolean result in the consuming operation).
Sometimes you don't expect the operands to be booleans, or the short-circuit operation has its own notion of "truthy" and "falsy" values.
For example, Python's or operator evaluates to the first non-"falsy" operand, where values like 0 and "" are falsy, and values like 42 and "hello" are not.
In such cases, you can supply a booleanConverter that coerces each operand to boolean to perform the boolean comparison.
Suppose there already exists a CoerceToBoolean operation that coerces values to booleans.
We can emulate Python's or operator with a FalsyCoalesce operation that returns the first non-"falsy" operand:
@GenerateBytecode(...)
@ShortCircuitOperation(
name = "FalsyCoalesce",
operator = ShortCircuitOperation.Operator.OR_RETURN_VALUE,
booleanConverter = CoerceToBoolean.class
)
public abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { ... }
This specification declares a FalsyCoalesce operation that executes its child operations in sequence, producing the first operand that coerces to true.
In pseudocode, it implements:
value_1 = child_1.execute()
if CoerceToBoolean(value_1) == true:
return value_1
value_2 = child_2.execute()
if CoerceToBoolean(value_2) == true:
return value_2
# ... more children
return child_n.execute()
Observe that FalsyCoalesce produces the original operand value, and not the converted boolean value.
This is because it uses the OR_RETURN_VALUE operator.
For both AND and OR operators there are variations that instead produce the converted boolean value: AND_RETURN_CONVERTED and OR_RETURN_CONVERTED.
Below, we define a CoerceAnd operation that uses CoerceToBoolean to convert its operands to boolean and produces the converted value:
@GenerateBytecode(...)
@ShortCircuitOperation(
name = "CoerceAnd",
operator = ShortCircuitOperation.Operator.AND_RETURN_CONVERTED,
booleanConverter = CoerceToBoolean.class
)
public abstract class MyBytecodeRootNode extends RootNode implements BytecodeRootNode { ... }
CoerceAnd executes its child operations in sequence until an operand coerces to false.
It produces the converted boolean value of the last operand executed.
In pseudocode:
if CoerceToBoolean(child_1.execute()) == false:
return false
if CoerceToBoolean(child_2.execute()) == false:
return false
# ... more children
return CoerceToBoolean(child_n.execute())