@@ -195,6 +195,7 @@ defmodule Emily.IR do
195195
196196 alias Emily.Fast.Block , as: FB
197197 alias Emily.Quantization.Block , as: QB
198+ alias Nx.Defn.Tree
198199 alias Nx.Tensor , as: T
199200
200201 # Nx Expr op -> IR opcode. Arithmetic/bitwise cast both operands to the
@@ -522,6 +523,76 @@ defmodule Emily.IR do
522523 lower_block ( struct , in_args , expr , t , state )
523524 end
524525
526+ # cond: raw args [clauses, last], clauses = [{pred, body}, ...]. Lower to
527+ # a select chain `where(p1, b1, where(p2, b2, ... last))`. ALL branches
528+ # are evaluated (Nx branches are side-effect-free and shape-compatible);
529+ # the result value matches the Evaluator's chosen branch exactly — only
530+ # the cost differs (not-taken branches are computed and discarded by the
531+ # elementwise select). The predicate is a whole-tensor scalar bool, so
532+ # `where` selects a branch wholesale.
533+ #
534+ # Caveat: a not-taken branch is still computed. On MLX an out-of-bounds
535+ # gather/index there clamps rather than faults, so the discarded value
536+ # never changes the result; a hard-faulting op on a not-taken path would
537+ # diverge from the Evaluator's lazy single-branch eval.
538+ defp lower_op ( % T { data: % Nx.Defn.Expr { op: :cond , args: [ clauses , last ] } } = t , state ) do
539+ { last_ref , state } = lower_node ( last , state )
540+
541+ { result , state } =
542+ Enum . reduce ( Enum . reverse ( clauses ) , { last_ref , state } , fn { pred , body } , { else_ref , st } ->
543+ { pred_ref , st } = lower_node ( pred , st )
544+ { body_ref , st } = lower_node ( body , st )
545+ { pred_ref , st } = emit ( st , :astype , [ pred_ref ] , [ [ dtype_code ( { :pred , 1 } ) ] ] )
546+ emit ( st , :where , [ pred_ref , body_ref , else_ref ] )
547+ end )
548+
549+ coerce ( result , t . type , state )
550+ end
551+
552+ # attach_token: sequences a token (hooks) before `expr`. With no active
553+ # hook the token is a no-op, so pass through to the inner expr. Hooks
554+ # would need a callback into Elixir mid-graph (program-split) — deferred.
555+ defp lower_op ( % T { data: % Nx.Defn.Expr { op: :attach_token , args: [ token , expr ] } } , state ) do
556+ if Tree . has_hooks? ( token , % { } ) do
557+ raise ArgumentError ,
558+ "Emily Expr compiler does not support hooks under native compilation " <>
559+ "(they require a mid-graph callback into Elixir)."
560+ end
561+
562+ lower_node ( expr , state )
563+ end
564+
565+ # reduce / window_reduce with a user-supplied BEAM reducer cannot be
566+ # compiled — the reducer would have to run on the host mid-graph. The
567+ # fixed-identity aggregates (sum/product/max/min) are separate ops and
568+ # already lower natively; only an arbitrary reducer reaches here.
569+ defp lower_op ( % T { data: % Nx.Defn.Expr { op: op } } , _state ) when op in [ :reduce , :window_reduce ] do
570+ raise ArgumentError ,
571+ "Emily Expr compiler cannot lower #{ inspect ( op ) } with an arbitrary " <>
572+ "reducer function (it would require a host callback mid-graph; no " <>
573+ "fallback). Use the native aggregates (sum/product/reduce_max/" <>
574+ "reduce_min) where possible."
575+ end
576+
577+ # while is deferred to a follow-up: the single-NIF replay has no loop
578+ # construct, so a data-dependent while needs static-trip unrolling or a
579+ # worker-side synced loop. defn while is not used by the core transformer
580+ # forwards (decode/generation loops run in Elixir today).
581+ defp lower_op ( % T { data: % Nx.Defn.Expr { op: :while } } , _state ) do
582+ raise ArgumentError ,
583+ "Emily Expr compiler does not yet lower defn `while` loops (deferred — " <>
584+ "the single-NIF replay has no loop construct)."
585+ end
586+
587+ # :elem is a tuple projection, emitted for any tuple-returning expression
588+ # (defn `while`, multi-output ops). Deferred alongside the constructs
589+ # that produce surviving tuples.
590+ defp lower_op ( % T { data: % Nx.Defn.Expr { op: :elem } } , _state ) do
591+ raise ArgumentError ,
592+ "Emily Expr compiler does not yet lower :elem (tuple projection) — it " <>
593+ "arises from defn `while` and multi-output ops, which are deferred."
594+ end
595+
525596 defp lower_op ( % T { data: % Nx.Defn.Expr { op: op } } , _state ) do
526597 raise ArgumentError ,
527598 "Emily Expr compiler does not yet lower op #{ inspect ( op ) } " <>
0 commit comments