@@ -1354,6 +1354,80 @@ defmodule Emily.IR do
13541354 { { :multi_refs , leaf_refs } , state }
13551355 end
13561356
1357+ # Nx.logical_not (Nx.Block.LogicalNot). Mirrors
1358+ # Emily.Backend.native_logical_not/2 — emit the :logical_not opcode
1359+ # directly (MLX returns bool), trailing coerce produces {:u, 8}.
1360+ defp lower_block ( % Nx.Block.LogicalNot { } , [ t ] , _expr , out , state ) do
1361+ { rt , state } = lower_node ( t , state )
1362+ emit_coerced ( state , :logical_not , [ rt ] , [ ] , out . type )
1363+ end
1364+
1365+ # Nx.all_close (Nx.Block.AllClose). Mirrors
1366+ # Emily.Backend.native_all_close/4 op-for-op so the native and eager
1367+ # paths land on identical bits: cast both to the merged float type,
1368+ # compute `abs(a - b) <= atol + rtol * abs(b)`, optionally OR with the
1369+ # `equal_nan` mask (`isnan(a) AND isnan(b)`), then reduce over every
1370+ # axis via `:all`.
1371+ defp lower_block (
1372+ % Nx.Block.AllClose { equal_nan: equal_nan , rtol: rtol , atol: atol } ,
1373+ [ a , b ] ,
1374+ _expr ,
1375+ out ,
1376+ state
1377+ ) do
1378+ { ra , state } = lower_node ( a , state )
1379+ { rb , state } = lower_node ( b , state )
1380+
1381+ merged = Nx.Type . merge ( a . type , b . type ) |> Nx.Type . to_floating ( )
1382+ code = dtype_code ( merged )
1383+ { ra , state } = emit ( state , :astype , [ ra ] , [ [ code ] ] )
1384+ { rb , state } = emit ( state , :astype , [ rb ] , [ [ code ] ] )
1385+
1386+ { diff_raw , state } = emit ( state , :subtract , [ ra , rb ] )
1387+ { diff , state } = emit ( state , :abs , [ diff_raw ] )
1388+
1389+ { atol_ref , state } = scalar_const ( atol , merged , state )
1390+ { rtol_ref , state } = scalar_const ( rtol , merged , state )
1391+ { abs_b , state } = emit ( state , :abs , [ rb ] )
1392+ { rtol_x_abs_b , state } = emit ( state , :multiply , [ rtol_ref , abs_b ] )
1393+ { tol , state } = emit ( state , :add , [ atol_ref , rtol_x_abs_b ] )
1394+
1395+ { close , state } = emit ( state , :less_equal , [ diff , tol ] )
1396+
1397+ { close , state } =
1398+ if equal_nan do
1399+ { na , state } = emit ( state , :isnan , [ ra ] )
1400+ { nb , state } = emit ( state , :isnan , [ rb ] )
1401+ { both_nan , state } = emit ( state , :logical_and , [ na , nb ] )
1402+ emit ( state , :logical_or , [ close , both_nan ] )
1403+ else
1404+ { close , state }
1405+ end
1406+
1407+ axes = Enum . to_list ( 0 .. ( tuple_size ( a . shape ) - 1 ) // 1 )
1408+ { result , state } = emit ( state , :all , [ close ] , [ axes , [ 0 ] ] )
1409+ coerce ( result , out . type , state )
1410+ end
1411+
1412+ # Nx.phase (Nx.Block.Phase) := atan2(imag(t), real(t)). Backend
1413+ # falls through to the composed expansion (no fused kernel); the IR
1414+ # does the same here, bound to the real in_args via the TopK-style
1415+ # parameter seeding (block-local :parameter nodes are FRESH and would
1416+ # otherwise resolve to the outer function's input slots). All three
1417+ # primitives in the expansion (atan2/imag/real) already lower
1418+ # natively, so the result is bit-identical to the Evaluator.
1419+ defp lower_block ( % Nx.Block.Phase { } , [ t ] , expr , _out , state ) do
1420+ { arg_ref , state } = lower_node ( t , state )
1421+
1422+ seed =
1423+ expr
1424+ |> collect_block_params ( % { } )
1425+ |> Map . new ( fn { id , 0 } -> { id , arg_ref } end )
1426+
1427+ state = % { state | cache: Map . merge ( state . cache , seed ) }
1428+ lower_node ( expr , state )
1429+ end
1430+
13571431 # Any other block struct raises. Lowering the block's composed
13581432 # expansion would silently diverge from the Evaluator whenever
13591433 # Emily.Backend.block/4 dispatches that struct through a fused / native
0 commit comments