///|
pub fn wgsl_ir_function_expression_is_atomic_compare_exchange_projection_role(
  function : Function,
  handle : Handle,
) -> Bool {
  wgsl_ir_function_expression_is_atomic_compare_exchange_result_projection(
    function, handle,
  ) ||
  wgsl_ir_function_expression_is_atomic_compare_exchange_old_value_base_load(
    function, handle,
  )
}

///|
fn wgsl_ir_function_expression_is_atomic_compare_exchange_result_projection(
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(AccessIndex(base, _)) | Some(Component(base, _)) =>
      match function.expressions.items.get(base.index()) {
        Some(Load(inner)) =>
          wgsl_ir_function_expression_is_atomic_compare_exchange_result_ref(
            function, inner,
          )
        _ => false
      }
    _ => false
  }
}

///|
fn wgsl_ir_function_expression_is_atomic_compare_exchange_old_value_base_load(
  function : Function,
  handle : Handle,
) -> Bool {
  guard function.expressions.items.get(handle.index()) is Some(Load(inner)) else {
    return false
  }
  guard wgsl_ir_function_expression_is_atomic_compare_exchange_result_ref(
    function, inner,
  ) else {
    return false
  }
  match function.expressions.items.get(handle.index() + 1) {
    Some(AccessIndex(base, 0)) | Some(Component(base, X)) => base == handle
    _ => false
  }
}

///|
fn wgsl_ir_function_expression_is_atomic_compare_exchange_result_ref(
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(LocalVariable(local_handle)) =>
      match function.local_variables.items.get(local_handle.index()) {
        Some(local_var) =>
          match local_var.init {
            Some(init) =>
              match function.expressions.items.get(init.index()) {
                Some(AtomicCall(CompareExchangeWeak, _)) => true
                _ => false
              }
            None => false
          }
        None => false
      }
    _ => false
  }
}

///|
fn wgsl_ir_function_expression_is_synthetic_local_reference(
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(Load(inner)) =>
      wgsl_ir_function_expression_is_synthetic_local_reference(function, inner)
    Some(LocalVariable(local_handle)) =>
      match function.local_variables.items.get(local_handle.index()) {
        Some(local_var) =>
          if local_var.kind != Let {
            false
          } else if local_var.generated_temporary {
            true
          } else {
            true
          }
        None => false
      }
    _ => false
  }
}

///|
fn wgsl_ir_function_expression_is_generated_temporary_reference(
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(Load(inner)) =>
      wgsl_ir_function_expression_is_generated_temporary_reference(
        function, inner,
      )
    Some(LocalVariable(local_handle)) =>
      match function.local_variables.items.get(local_handle.index()) {
        Some(local_var) => local_var.generated_temporary
        None => false
      }
    _ => false
  }
}

///|
fn wgsl_ir_local_variable_initializes_materialized_value_call(
  function : Function,
  local_var : LocalVariable,
) -> Bool {
  match local_var.init {
    Some(init) =>
      match function.expressions.items.get(init.index()) {
        Some(FunctionCall(_, _))
        | Some(AtomicCall(_, _))
        | Some(ImageSample(_, _, _, _, _, _, _, _, _))
        | Some(ImageLoad(_, _, _, _, _))
        | Some(ImageQuery(_, _))
        | Some(WorkGroupUniformLoad(_)) => true
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::materialize_function_expression(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  match function.expressions.items.get(value.index()) {
    Some(FunctionCall(function_handle, arguments)) => {
      function.expressions.set(value, CallResult(function_handle))
      self.push_user_function_call_emit_ranges(
        block,
        function,
        wgsl_ir_next_statement_emit_mark(block),
        function_handle,
        arguments,
        value,
      )
      block.push(Call(function_handle, arguments, Some(value)))
      return value
    }
    Some(AtomicCall(atomic_function, arguments)) => {
      let lowered_arguments : Array[Handle] = []
      for argument in arguments {
        lowered_arguments.push(
          self.materialize_nested_call_argument(
            function, scope, block, argument,
          ),
        )
      }
      let result_ty = match self.function_expression_type(function, value) {
        Some(ty) => ty
        None =>
          raise MissingType(
            "materialized atomic expression \{value.index()}: \{wgsl_ir_function_expression_debug(function, value)}",
          )
      }
      let comparison = atomic_function == CompareExchangeWeak
      function.expressions.set(value, AtomicResult(result_ty, comparison))
      BodyEmissionPlanner::push_statement_emit_ranges(
        block,
        function,
        wgsl_ir_next_statement_emit_mark(block),
        value,
      )
      match lowered_arguments {
        [pointer, compare, atomic_value] if comparison =>
          block.push(
            Atomic(
              pointer,
              atomic_function,
              atomic_value,
              Some(compare),
              Some(value),
            ),
          )
        [pointer, atomic_value] =>
          block.push(
            Atomic(pointer, atomic_function, atomic_value, None, Some(value)),
          )
        _ =>
          raise UnsupportedExpression(
            "materialized atomic expression has invalid argument shape",
          )
      }
      return value
    }
    _ => ()
  }
  match
    self.existing_materialized_function_expression(
      function, scope, block, value,
    ) {
    Some(existing) => return existing
    None => ()
  }
  let ty = match self.function_expression_type(function, value) {
    Some(ty) => ty
    None =>
      raise MissingType(
        "materialized expression \{value.index()}: \{wgsl_ir_function_expression_debug(function, value)}",
      )
  }
  let local_handle = function.local_variables.append({
    name: None,
    kind: Let,
    generated_temporary: true,
    generated_temporary_store_value_reference: false,
    short_circuit_result: false,
    ty,
    init: Some(value),
  })
  scope.materialized_expression_locals.set(value.index(), value)
  block.push(Declare(local_handle))
  value
}

///|
fn WgslIrLowerer::existing_materialized_function_expression(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle? {
  ignore(self)
  match scope.materialized_expression_locals.get(value.index()) {
    Some(existing) => return Some(existing)
    None => ()
  }
  for statement in block.statements {
    match statement {
      Declare(local_handle) =>
        match function.local_variables.items.get(local_handle.index()) {
          Some(local_var) =>
            if local_var.generated_temporary && local_var.init == Some(value) {
              scope.materialized_expression_locals.set(value.index(), value)
              return Some(value)
            }
          _ => ()
        }
      _ => ()
    }
  }
  None
}

///|
fn wgsl_ir_next_statement_emit_mark(block : Block) -> Int32 {
  let mut next = 0
  for statement in block.statements {
    match wgsl_ir_statement_max_expression_index(statement) {
      Some(index) => if index + 1 > next { next = index + 1 }
      None => ()
    }
  }
  next
}

///|
fn wgsl_ir_statement_max_expression_index(statement : Statement) -> Int32? {
  match statement {
    Declare(handle) => Some(handle.index())
    Emit(range) => Some(range.end.index())
    Phony(handle) => Some(handle.index())
    If(condition, accept, reject) => {
      let mut index = condition.index()
      match wgsl_ir_block_max_expression_index(accept) {
        Some(block_index) =>
          index = wgsl_ir_max_expression_index(
            Handle(index),
            Handle(block_index),
          )
        None => ()
      }
      match wgsl_ir_block_max_expression_index(reject) {
        Some(block_index) =>
          index = wgsl_ir_max_expression_index(
            Handle(index),
            Handle(block_index),
          )
        None => ()
      }
      Some(index)
    }
    Switch(selector, cases) => {
      let mut index = selector.index()
      for case in cases {
        match wgsl_ir_block_max_expression_index(case.body) {
          Some(block_index) =>
            index = wgsl_ir_max_expression_index(
              Handle(index),
              Handle(block_index),
            )
          None => ()
        }
      }
      Some(index)
    }
    Loop(body, continuing, break_if) => {
      let mut index : Int32? = None
      match break_if {
        Some(handle) => index = Some(handle.index())
        None => ()
      }
      match wgsl_ir_block_max_expression_index(body) {
        Some(block_index) =>
          index = Some(
            match index {
              Some(current) =>
                wgsl_ir_max_expression_index(
                  Handle(current),
                  Handle(block_index),
                )
              None => block_index
            },
          )
        None => ()
      }
      match wgsl_ir_block_max_expression_index(continuing) {
        Some(block_index) =>
          index = Some(
            match index {
              Some(current) =>
                wgsl_ir_max_expression_index(
                  Handle(current),
                  Handle(block_index),
                )
              None => block_index
            },
          )
        None => ()
      }
      index
    }
    Return(value) =>
      match value {
        Some(handle) => Some(handle.index())
        None => None
      }
    ConstAssert(condition) => Some(condition.index())
    Store(pointer, value) => Some(wgsl_ir_max_expression_index(pointer, value))
    ImageStore(image, coordinate, array_index, value) => {
      let mut index = wgsl_ir_max_expression_index(image, coordinate)
      match array_index {
        Some(handle) =>
          index = wgsl_ir_max_expression_index(Handle(index), handle)
        None => ()
      }
      Some(wgsl_ir_max_expression_index(Handle(index), value))
    }
    Atomic(pointer, _, value, compare, result) => {
      let mut index = wgsl_ir_max_expression_index(pointer, value)
      match compare {
        Some(handle) =>
          index = wgsl_ir_max_expression_index(Handle(index), handle)
        None => ()
      }
      match result {
        Some(handle) =>
          index = wgsl_ir_max_expression_index(Handle(index), handle)
        None => ()
      }
      Some(index)
    }
    ImageAtomic(image, coordinate, array_index, _, value) => {
      let mut index = wgsl_ir_max_expression_index(image, coordinate)
      match array_index {
        Some(handle) =>
          index = wgsl_ir_max_expression_index(Handle(index), handle)
        None => ()
      }
      Some(wgsl_ir_max_expression_index(Handle(index), value))
    }
    WorkGroupUniformLoad(pointer, result) =>
      Some(wgsl_ir_max_expression_index(pointer, result))
    Call(_, arguments, result) => {
      let mut index : Int32? = None
      for argument in arguments {
        index = Some(
          match index {
            Some(current) =>
              wgsl_ir_max_expression_index(Handle(current), argument)
            None => argument.index()
          },
        )
      }
      match result {
        Some(handle) =>
          Some(
            match index {
              Some(current) =>
                wgsl_ir_max_expression_index(Handle(current), handle)
              None => handle.index()
            },
          )
        None => index
      }
    }
    RayQuery(query, _) => Some(query.index())
    RayPipelineFunction(_) => None
    SubgroupBallot(value, result) =>
      match result {
        Some(handle) => Some(wgsl_ir_max_expression_index(value, handle))
        None => Some(value.index())
      }
    SubgroupGather(_, value, result) =>
      Some(wgsl_ir_max_expression_index(value, result))
    SubgroupCollectiveOperation(_, _, value, result) =>
      Some(wgsl_ir_max_expression_index(value, result))
    CooperativeStore(pointer, _) => Some(pointer.index())
    Block(block) => wgsl_ir_block_max_expression_index(block)
    Break
    | Continue
    | ImplicitReturn
    | Kill
    | ControlBarrier(_)
    | MemoryBarrier(_) => None
  }
}

///|
fn wgsl_ir_block_max_expression_index(block : Block) -> Int32? {
  let mut index : Int32? = None
  for statement in block.statements {
    match wgsl_ir_statement_max_expression_index(statement) {
      Some(statement_index) =>
        index = Some(
          match index {
            Some(current) =>
              wgsl_ir_max_expression_index(
                Handle(current),
                Handle(statement_index),
              )
            None => statement_index
          },
        )
      None => ()
    }
  }
  index
}

///|
fn wgsl_ir_max_expression_index(left : Handle, right : Handle) -> Int32 {
  if left.index() >= right.index() {
    left.index()
  } else {
    right.index()
  }
}

///|
fn wgsl_ir_function_expression_debug(
  function : Function,
  value : Handle,
) -> String {
  match function.expressions.items.get(value.index()) {
    Some(expression) =>
      match expression {
        Literal(_) => "literal"
        Constant(_) => "constant"
        Override(_) => "override"
        ZeroValue(_) => "zero-value"
        Compose(_, _) => "compose"
        Access(_, _) => "access"
        AccessIndex(_, _) => "access-index"
        Component(_, _) => "component"
        Splat(_, _) => "splat"
        Swizzle(_, _, _) => "swizzle"
        FunctionArgument(_) => "function-argument"
        GlobalVariable(_) => "global-variable"
        LocalVariable(_) => "local-variable"
        Load(inner) =>
          "load(\{wgsl_ir_function_expression_debug(function, inner)})"
        AddressOf(_, _) => "address-of"
        AtomicCall(_, _) => "atomic-call"
        ImageSample(_, _, _, _, _, _, _, _, _) => "image-sample"
        ImageLoad(_, _, _, _, _) => "image-load"
        ImageQuery(_, _) => "image-query"
        Unary(_, _) => "unary"
        Binary(_, _, _) => "binary"
        FunctionCall(_, _) => "function-call"
        Select(_, _, _) => "select"
        Bitcast(_, _) => "bitcast"
        Derivative(_, _, _) => "derivative"
        Relational(_, _) => "relational"
        Math(_, _, _, _, _) => "math"
        As(_, _, _) => "as"
        CallResult(_) => "call-result"
        AtomicResult(_, _) => "atomic-result"
        WorkGroupUniformLoadResult(_) => "workgroup-uniform-load-result"
        WorkGroupUniformLoad(_) => "workgroup-uniform-load"
        ArrayLength(_) => "array-length"
        RayQueryInitialize(_, _, _) => "ray-query-initialize"
        RayQueryProceed(_) => "ray-query-proceed"
        RayQueryGenerateIntersection(_, _) => "ray-query-generate-intersection"
        RayQueryConfirmIntersection(_) => "ray-query-confirm-intersection"
        RayQueryTerminate(_) => "ray-query-terminate"
        RayQueryVertexPositions(_, _) => "ray-query-vertex-positions"
        RayQueryProceedResult => "ray-query-proceed-result"
        RayQueryGetIntersection(_, _) => "ray-query-get-intersection"
        SubgroupCall(_, _) => "subgroup-call"
        SubgroupBallotResult => "subgroup-ballot-result"
        SubgroupOperationResult(_) => "subgroup-operation-result"
        CooperativeLoad(_, _, _, _) => "cooperative-load"
        CooperativeMultiplyAdd(_, _, _) => "cooperative-multiply-add"
      }
    None => "missing expression handle \{value.index()}"
  }
}

///|
fn WgslIrLowerer::materialize_return_expression_if_needed(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_nested_value(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else if self.function_expression_can_return_directly(function, normalized) {
    normalized
  } else {
    self.materialize_function_expression(function, scope, block, normalized)
  }
}

///|
fn WgslIrLowerer::materialize_return_subexpressions(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return value
  }
  match function.expressions.items.get(value.index()) {
    Some(Compose(ty, components)) => {
      let lowered : Array[Handle] = []
      let mut changed = false
      for component in components {
        let lowered_component = self.materialize_nested_constructor_argument(
          function, scope, block, component,
        )
        let parent_component = wgsl_ir_parent_expression_operand(
          function, component, lowered_component,
        )
        if parent_component != component {
          changed = true
        }
        lowered.push(parent_component)
      }
      if changed {
        function.expressions.append(Compose(ty, lowered))
      } else {
        value
      }
    }
    Some(Splat(size, inner)) => {
      let lowered_inner = self.materialize_nested_constructor_argument(
        function, scope, block, inner,
      )
      let parent_inner = wgsl_ir_parent_expression_operand(
        function, inner, lowered_inner,
      )
      if parent_inner != inner {
        function.expressions.append(Splat(size, parent_inner))
      } else {
        value
      }
    }
    Some(Binary(op, left, right)) => {
      if wgsl_ir_binary_operator_is_short_circuit(op) {
        return self
          .short_circuit_planner(function, scope, block)
          .materialize_value_handle(value)
      }
      let lowered_left = self.materialize_nested_return_argument(
        function, scope, block, left,
      )
      let lowered_right = self.materialize_nested_return_argument(
        function, scope, block, right,
      )
      let parent_left = wgsl_ir_parent_expression_operand(
        function, left, lowered_left,
      )
      let parent_right = wgsl_ir_parent_expression_operand(
        function, right, lowered_right,
      )
      if parent_left != left || parent_right != right {
        function.expressions.append(Binary(op, parent_left, parent_right))
      } else {
        value
      }
    }
    Some(Access(base, index)) => {
      let lowered_base = self.materialize_nested_access_base(
        function, scope, block, base,
      )
      let lowered_index = self.materialize_nested_access_index(
        function, scope, block, index,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      let parent_index = wgsl_ir_parent_expression_operand(
        function, index, lowered_index,
      )
      if parent_base != base || parent_index != index {
        function.expressions.append(Access(parent_base, parent_index))
      } else {
        value
      }
    }
    Some(AccessIndex(base, index)) => {
      let lowered_base = self.materialize_nested_access_base(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(AccessIndex(parent_base, index))
      } else {
        value
      }
    }
    Some(Load(pointer)) => {
      let lowered_pointer = self.materialize_pointer_subexpressions(
        function, scope, block, pointer,
      )
      let parent_pointer = wgsl_ir_parent_expression_operand(
        function, pointer, lowered_pointer,
      )
      if parent_pointer != pointer {
        function.expressions.append(Load(parent_pointer))
      } else {
        value
      }
    }
    Some(Component(base, component)) => {
      let lowered_base = self.materialize_nested_access_base(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(Component(parent_base, component))
      } else {
        value
      }
    }
    Some(Swizzle(size, base, pattern)) => {
      let lowered_base = self.materialize_nested_swizzle_base(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(Swizzle(size, parent_base, pattern))
      } else {
        value
      }
    }
    Some(Unary(op, inner)) => {
      let lowered_inner = self.materialize_nested_return_argument(
        function, scope, block, inner,
      )
      let parent_inner = wgsl_ir_parent_expression_operand(
        function, inner, lowered_inner,
      )
      if parent_inner != inner {
        function.expressions.append(Unary(op, parent_inner))
      } else {
        value
      }
    }
    Some(Bitcast(ty, inner)) => {
      let lowered_inner = self.materialize_nested_return_argument(
        function, scope, block, inner,
      )
      let parent_inner = wgsl_ir_parent_expression_operand(
        function, inner, lowered_inner,
      )
      if parent_inner != inner {
        function.expressions.append(Bitcast(ty, parent_inner))
      } else {
        value
      }
    }
    Some(Derivative(axis, control, expr)) => {
      let lowered = self.materialize_nested_call_argument(
        function, scope, block, expr,
      )
      let parent = wgsl_ir_parent_expression_operand(function, expr, lowered)
      if parent != expr {
        function.expressions.append(Derivative(axis, control, parent))
      } else {
        value
      }
    }
    Some(Relational(relational_function, expr)) => {
      let lowered = self.materialize_nested_call_argument(
        function, scope, block, expr,
      )
      let parent = wgsl_ir_parent_expression_operand(function, expr, lowered)
      if parent != expr {
        function.expressions.append(Relational(relational_function, parent))
      } else {
        value
      }
    }
    Some(As(expr, kind, width)) => {
      let lowered = self.materialize_nested_call_argument(
        function, scope, block, expr,
      )
      let parent = wgsl_ir_parent_expression_operand(function, expr, lowered)
      if parent != expr {
        function.expressions.append(As(parent, kind, width))
      } else {
        value
      }
    }
    Some(Select(condition, accept, reject)) => {
      let lowered_reject = self.materialize_nested_call_argument(
        function, scope, block, reject,
      )
      let lowered_accept = self.materialize_nested_call_argument(
        function, scope, block, accept,
      )
      let lowered_condition = self.materialize_nested_call_argument(
        function, scope, block, condition,
      )
      let parent_condition = wgsl_ir_parent_expression_operand(
        function, condition, lowered_condition,
      )
      let parent_accept = wgsl_ir_parent_expression_operand(
        function, accept, lowered_accept,
      )
      let parent_reject = wgsl_ir_parent_expression_operand(
        function, reject, lowered_reject,
      )
      if parent_condition != condition ||
        parent_accept != accept ||
        parent_reject != reject {
        function.expressions.append(
          Select(parent_condition, parent_accept, parent_reject),
        )
      } else {
        value
      }
    }
    Some(FunctionCall(handle, arguments)) => {
      let lowered : Array[Handle] = []
      let mut changed = false
      for index in 0.. {
      let lowered_coordinate = self.materialize_nested_call_argument(
        function, scope, block, coordinate,
      )
      let lowered_array_index = self.materialize_optional_nested_call_argument(
        function, scope, block, array_index,
      )
      let lowered_offset = self.materialize_optional_nested_call_argument(
        function, scope, block, offset,
      )
      let lowered_level = self.materialize_sample_level(
        function, scope, block, level,
      )
      let lowered_depth_ref = self.materialize_optional_nested_call_argument(
        function, scope, block, depth_ref,
      )
      let parent_coordinate = wgsl_ir_parent_expression_operand(
        function, coordinate, lowered_coordinate,
      )
      let parent_array_index = wgsl_ir_parent_optional_expression_operand(
        function, array_index, lowered_array_index,
      )
      let parent_offset = wgsl_ir_parent_optional_expression_operand(
        function, offset, lowered_offset,
      )
      let parent_level = wgsl_ir_parent_sample_level_operand(
        function, level, lowered_level,
      )
      let parent_depth_ref = wgsl_ir_parent_optional_expression_operand(
        function, depth_ref, lowered_depth_ref,
      )
      if parent_coordinate != coordinate ||
        parent_array_index != array_index ||
        parent_offset != offset ||
        parent_level != level ||
        parent_depth_ref != depth_ref {
        function.expressions.append(
          ImageSample(
            image, sampler, gather, parent_coordinate, parent_array_index, parent_offset,
            parent_level, parent_depth_ref, clamp_to_edge,
          ),
        )
      } else {
        value
      }
    }
    Some(ImageLoad(image, coordinate, array_index, sample, level)) => {
      let lowered_coordinate = self.materialize_nested_call_argument(
        function, scope, block, coordinate,
      )
      let lowered_array_index = self.materialize_optional_nested_call_argument(
        function, scope, block, array_index,
      )
      let lowered_sample = self.materialize_optional_nested_call_argument(
        function, scope, block, sample,
      )
      let lowered_level = self.materialize_optional_nested_call_argument(
        function, scope, block, level,
      )
      let parent_coordinate = wgsl_ir_parent_expression_operand(
        function, coordinate, lowered_coordinate,
      )
      let parent_array_index = wgsl_ir_parent_optional_expression_operand(
        function, array_index, lowered_array_index,
      )
      let parent_sample = wgsl_ir_parent_optional_expression_operand(
        function, sample, lowered_sample,
      )
      let parent_level = wgsl_ir_parent_optional_expression_operand(
        function, level, lowered_level,
      )
      if parent_coordinate != coordinate ||
        parent_array_index != array_index ||
        parent_sample != sample ||
        parent_level != level {
        function.expressions.append(
          ImageLoad(
            image, parent_coordinate, parent_array_index, parent_sample, parent_level,
          ),
        )
      } else {
        value
      }
    }
    Some(ImageQuery(image, query)) => {
      let lowered_query = self.materialize_image_query(
        function, scope, block, query,
      )
      if lowered_query != query {
        function.expressions.append(ImageQuery(image, lowered_query))
      } else {
        value
      }
    }
    Some(AtomicCall(atomic_function, arguments)) => {
      let lowered : Array[Handle] = []
      let mut changed = false
      for argument in arguments {
        let lowered_argument = self.materialize_nested_call_argument(
          function, scope, block, argument,
        )
        let parent_argument = wgsl_ir_parent_expression_operand(
          function, argument, lowered_argument,
        )
        if parent_argument != argument {
          changed = true
        }
        lowered.push(parent_argument)
      }
      if changed {
        function.expressions.set(value, AtomicCall(atomic_function, lowered))
      }
      value
    }
    Some(Math(math_function, a, b, c, d)) => {
      let lowered_a = self.materialize_nested_call_argument(
        function, scope, block, a,
      )
      let lowered_b = self.materialize_optional_nested_call_argument(
        function, scope, block, b,
      )
      let lowered_c = self.materialize_optional_nested_call_argument(
        function, scope, block, c,
      )
      let lowered_d = self.materialize_optional_nested_call_argument(
        function, scope, block, d,
      )
      let parent_a = wgsl_ir_parent_expression_operand(function, a, lowered_a)
      let parent_b = wgsl_ir_parent_optional_expression_operand(
        function, b, lowered_b,
      )
      let parent_c = wgsl_ir_parent_optional_expression_operand(
        function, c, lowered_c,
      )
      let parent_d = wgsl_ir_parent_optional_expression_operand(
        function, d, lowered_d,
      )
      if parent_a != a || parent_b != b || parent_c != c || parent_d != d {
        function.expressions.append(
          Math(math_function, parent_a, parent_b, parent_c, parent_d),
        )
      } else {
        value
      }
    }
    Some(SubgroupCall(subgroup_function, arguments)) => {
      let lowered : Array[Handle] = []
      let mut changed = false
      for argument in arguments {
        let lowered_argument = self.materialize_nested_call_argument(
          function, scope, block, argument,
        )
        let parent_argument = wgsl_ir_parent_expression_operand(
          function, argument, lowered_argument,
        )
        if parent_argument != argument {
          changed = true
        }
        lowered.push(parent_argument)
      }
      if changed {
        function.expressions.append(SubgroupCall(subgroup_function, lowered))
      } else {
        value
      }
    }
    Some(CooperativeLoad(columns, rows, role, data)) => {
      let lowered_data = self.materialize_cooperative_data(
        function, scope, block, data,
      )
      if lowered_data != data {
        function.expressions.append(
          CooperativeLoad(columns, rows, role, lowered_data),
        )
      } else {
        value
      }
    }
    Some(CooperativeMultiplyAdd(a, b, c)) => {
      let lowered_a = self.materialize_nested_call_argument(
        function, scope, block, a,
      )
      let lowered_b = self.materialize_nested_call_argument(
        function, scope, block, b,
      )
      let lowered_c = self.materialize_nested_call_argument(
        function, scope, block, c,
      )
      let parent_a = wgsl_ir_parent_expression_operand(function, a, lowered_a)
      let parent_b = wgsl_ir_parent_expression_operand(function, b, lowered_b)
      let parent_c = wgsl_ir_parent_expression_operand(function, c, lowered_c)
      if parent_a != a || parent_b != b || parent_c != c {
        function.expressions.append(
          CooperativeMultiplyAdd(parent_a, parent_b, parent_c),
        )
      } else {
        value
      }
    }
    _ => value
  }
}

///|
fn WgslIrLowerer::materialize_pointer_subexpressions(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  pointer : Handle,
) -> Handle raise WgslIrLowerError {
  match function.expressions.items.get(pointer.index()) {
    Some(Access(base, index)) => {
      let lowered_base = self.materialize_pointer_subexpressions(
        function, scope, block, base,
      )
      let lowered_index = self.materialize_nested_access_index(
        function, scope, block, index,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      let parent_index = wgsl_ir_parent_expression_operand(
        function, index, lowered_index,
      )
      if parent_base != base || parent_index != index {
        function.expressions.append(Access(parent_base, parent_index))
      } else {
        pointer
      }
    }
    Some(AccessIndex(base, index)) => {
      let lowered_base = self.materialize_pointer_subexpressions(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(AccessIndex(parent_base, index))
      } else {
        pointer
      }
    }
    Some(Component(base, component)) => {
      let lowered_base = self.materialize_pointer_subexpressions(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(Component(parent_base, component))
      } else {
        pointer
      }
    }
    Some(Swizzle(size, base, pattern)) => {
      let lowered_base = self.materialize_pointer_subexpressions(
        function, scope, block, base,
      )
      let parent_base = wgsl_ir_parent_expression_operand(
        function, base, lowered_base,
      )
      if parent_base != base {
        function.expressions.append(Swizzle(size, parent_base, pattern))
      } else {
        pointer
      }
    }
    Some(Load(inner)) => {
      let lowered_inner = self.materialize_pointer_subexpressions(
        function, scope, block, inner,
      )
      let parent_inner = wgsl_ir_parent_expression_operand(
        function, inner, lowered_inner,
      )
      if parent_inner != inner {
        function.expressions.append(Load(parent_inner))
      } else {
        pointer
      }
    }
    _ => pointer
  }
}

///|
fn wgsl_ir_binary_operator_is_short_circuit(op : BinaryOperator) -> Bool {
  op == LogicalAnd || op == LogicalOr
}

///|
fn wgsl_ir_expr_node_is_short_circuit_binary(node : @ast.WgslExprNode) -> Bool {
  match node.kind() {
    Binary(op, _, _) => op == LogicalAnd || op == LogicalOr
    Group(inner) | Template(inner, _) =>
      wgsl_ir_expr_node_is_short_circuit_binary(inner)
    _ => false
  }
}

///|
fn WgslIrLowerer::materialize_sample_level(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  level : SampleLevel,
) -> SampleLevel raise WgslIrLowerError {
  match level {
    Exact(handle) =>
      Exact(
        self.materialize_nested_call_argument(function, scope, block, handle),
      )
    Bias(handle) =>
      Bias(
        self.materialize_nested_call_argument(function, scope, block, handle),
      )
    Gradient(x, y) =>
      Gradient(
        self.materialize_nested_call_argument(function, scope, block, x),
        self.materialize_nested_call_argument(function, scope, block, y),
      )
    Auto | Zero => level
  }
}

///|
fn WgslIrLowerer::materialize_image_query(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  query : ImageQuery,
) -> ImageQuery raise WgslIrLowerError {
  match query {
    Size(level) =>
      Size(
        self.materialize_optional_nested_call_argument(
          function, scope, block, level,
        ),
      )
    NumLevels | NumLayers | NumSamples => query
  }
}

///|
fn WgslIrLowerer::materialize_cooperative_data(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  data : CooperativeData,
) -> CooperativeData raise WgslIrLowerError {
  {
    pointer: data.pointer,
    stride: self.materialize_nested_call_argument(
      function,
      scope,
      block,
      data.stride,
    ),
    row_major: data.row_major,
  }
}

///|
fn WgslIrLowerer::materialize_optional_nested_call_argument(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle?,
) -> Handle? raise WgslIrLowerError {
  match value {
    Some(handle) =>
      Some(
        self.materialize_nested_call_argument(function, scope, block, handle),
      )
    None => None
  }
}

///|
fn WgslIrLowerer::materialize_image_store_operand(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  self.materialize_nested_call_argument(function, scope, block, value)
}

///|
fn WgslIrLowerer::materialize_optional_image_store_operand(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle?,
) -> Handle? raise WgslIrLowerError {
  match value {
    Some(handle) =>
      Some(self.materialize_image_store_operand(function, scope, block, handle))
    None => None
  }
}

///|
fn WgslIrLowerer::materialize_nested_return_argument(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_nested_value(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::materialize_nested_access_base(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_access_base(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::materialize_nested_access_index(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_nested_value(
      function, normalized,
    ) ||
    self.function_expression_should_materialize_as_call_argument(
      function, normalized,
    ) ||
    self.function_expression_should_materialize_as_access_index(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::function_expression_should_materialize_as_access_index(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  ignore(self)
  match function.expressions.items.get(value.index()) {
    Some(LocalVariable(handle)) =>
      match function.local_variables.items.get(handle.index()) {
        Some(local_variable) => local_variable.kind == Var
        None => false
      }
    Some(Load(pointer)) =>
      match function.expressions.items.get(pointer.index()) {
        Some(LocalVariable(handle)) =>
          match function.local_variables.items.get(handle.index()) {
            Some(local_variable) => local_variable.kind == Var
            None => false
          }
        _ => false
      }
    _ => false
  }
}

///|
fn WgslIrLowerer::materialize_nested_swizzle_base(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_projection_base_value(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    self.materialize_nested_access_base(function, scope, block, normalized)
  }
}

///|
fn WgslIrLowerer::materialize_nested_user_call_argument(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  callee : Handle,
  index : Int32,
  argument : Handle,
) -> Handle raise WgslIrLowerError {
  match self.shader_module.functions.items.get(callee.index()) {
    Some(callee_function) =>
      if self.callee_parameter_is_pointer(callee_function, index) {
        return argument
      }
    None => ()
  }
  self.materialize_nested_call_argument(function, scope, block, argument)
}

///|
fn WgslIrLowerer::materialize_nested_call_argument(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if wgsl_ir_function_expression_is_projection_of_generated_temporary(
      function, normalized,
    ) {
    return normalized
  }
  if self.function_expression_should_materialize_as_call_argument(
      function, normalized,
    ) ||
    self.function_expression_should_materialize_as_nested_value(
      function, normalized,
    ) ||
    self.function_expression_should_materialize_as_store_value(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::materialize_nested_constructor_argument(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_nested_value(
      function, normalized,
    ) ||
    self.function_expression_should_materialize_as_call_argument(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::materialize_store_value_if_needed(
  self : WgslIrLowerer,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
  value : Handle,
) -> Handle raise WgslIrLowerError {
  let normalized = self.materialize_return_subexpressions(
    function, scope, block, value,
  )
  if self.function_expression_should_materialize_as_store_value(
      function, normalized,
    ) {
    self.materialize_function_expression(function, scope, block, normalized)
  } else {
    normalized
  }
}

///|
fn WgslIrLowerer::compound_assignment_target_value(
  self : WgslIrLowerer,
  function : Function,
  target : Handle,
) -> Handle {
  ignore(self)
  function.expressions.append(Load(target))
}

///|
fn WgslIrLowerer::lowerer_type_is_pointer(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.shader_module.types.get(ty) {
    Some(type_) =>
      match type_.inner {
        Pointer(_, _) | ValuePointer(_, _, _) => true
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_should_materialize_as_store_value(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return false
  }
  if wgsl_ir_function_expression_is_projection_of_generated_temporary(
      function, value,
    ) {
    return false
  }
  let should_materialize = match function.expressions.items.get(value.index()) {
    Some(FunctionCall(_, _))
    | Some(AtomicCall(_, _))
    | Some(ImageSample(_, _, _, _, _, _, _, _, _))
    | Some(ImageLoad(_, _, _, _, _))
    | Some(ImageQuery(_, _))
    | Some(WorkGroupUniformLoad(_)) => true
    Some(Load(_)) =>
      self.function_expression_reads_global_binding(function, value) ||
      self.function_expression_reads_var_pointer_load(function, value)
    Some(Access(_, _))
    | Some(AccessIndex(_, _))
    | Some(Component(_, _))
    | Some(Swizzle(_, _, _)) =>
      self.function_projection_expression_reads_pointer_base(function, value)
    _ => false
  }
  should_materialize &&
  self.function_expression_can_materialize_to_let(function, value)
}

///|
fn WgslIrLowerer::function_expression_should_materialize_as_nested_value(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return false
  }
  if wgsl_ir_function_expression_is_projection_of_generated_temporary(
      function, value,
    ) {
    return false
  }
  let should_materialize = match function.expressions.items.get(value.index()) {
    Some(FunctionCall(_, _))
    | Some(AtomicCall(_, _))
    | Some(CooperativeLoad(_, _, _, _))
    | Some(CooperativeMultiplyAdd(_, _, _))
    | Some(Derivative(_, _, _))
    | Some(ImageSample(_, _, _, _, _, _, _, _, _))
    | Some(ImageLoad(_, _, _, _, _))
    | Some(ImageQuery(_, _))
    | Some(WorkGroupUniformLoad(_)) => true
    Some(Load(_)) =>
      self.function_expression_reads_global_binding(function, value) ||
      self.function_expression_reads_var_pointer_load(function, value)
    Some(Access(base, _))
    | Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      !wgsl_ir_function_expression_is_generated_temporary_reference(
        function, base,
      ) &&
      self.function_projection_expression_reads_pointer_base(function, value)
    _ => false
  }
  should_materialize &&
  self.function_expression_can_materialize_to_let(function, value)
}

///|
fn wgsl_ir_function_expression_is_projection_of_generated_temporary(
  function : Function,
  handle : Handle,
) -> Bool {
  match function.expressions.items.get(handle.index()) {
    Some(Access(base, _))
    | Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      wgsl_ir_function_expression_is_generated_temporary_reference(
        function, base,
      ) ||
      wgsl_ir_function_expression_has_generated_temporary_snapshot(
        function, base,
      )
    _ => false
  }
}

///|
fn wgsl_ir_function_expression_has_generated_temporary_snapshot(
  function : Function,
  handle : Handle,
) -> Bool {
  for local_var in function.local_variables.items {
    if local_var.generated_temporary && local_var.init == Some(handle) {
      return true
    }
  }
  false
}

///|
fn WgslIrLowerer::function_expression_should_materialize_as_access_base(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  ignore(self)
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return false
  }
  let should_materialize = match function.expressions.items.get(value.index()) {
    Some(FunctionCall(_, _))
    | Some(AtomicCall(_, _))
    | Some(ImageSample(_, _, _, _, _, _, _, _, _))
    | Some(ImageLoad(_, _, _, _, _))
    | Some(ImageQuery(_, _)) => true
    _ => false
  }
  should_materialize
}

///|
fn WgslIrLowerer::function_expression_should_materialize_projection_base_value(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return false
  }
  let should_materialize = match function.expressions.items.get(value.index()) {
    Some(Load(inner)) =>
      wgsl_ir_function_expression_is_projection_graph_node(function, inner) ||
      self.function_expression_reads_var_pointer_load(function, value)
    Some(Access(_, _)) | Some(AccessIndex(_, _)) | Some(Swizzle(_, _, _)) =>
      self.function_projection_expression_reads_pointer_base(function, value)
    _ => false
  }
  should_materialize &&
  self.function_expression_can_materialize_to_let(function, value)
}

///|
fn WgslIrLowerer::function_expression_should_materialize_as_call_argument(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return false
  }
  if wgsl_ir_function_expression_is_projection_of_generated_temporary(
      function, value,
    ) {
    return false
  }
  self.function_expression_is_var_pointer_load(function, value) &&
  self.function_expression_can_materialize_to_let(function, value)
}

///|
fn wgsl_ir_function_expression_has_arena_name(
  function : Function,
  value : Handle,
) -> Bool {
  wgsl_ir_function_named_expression_name(function, value) != None
}

///|
fn WgslIrLowerer::function_expression_is_non_pointer_arena_name(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  match wgsl_ir_function_named_expression_type(function, value) {
    Some(ty) => !self.lowerer_type_is_pointer(ty)
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_is_var_pointer_load(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  match function.expressions.items.get(value.index()) {
    Some(Load(pointer)) =>
      self.function_pointer_expression_is_var_pointer(function, pointer)
    _ => false
  }
}

///|
fn WgslIrLowerer::function_pointer_expression_is_var_pointer(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  match function.expressions.items.get(value.index()) {
    Some(LocalVariable(handle)) =>
      match function.local_variables.items.get(handle.index()) {
        Some(local_variable) =>
          local_variable.kind == Var ||
          self.local_variable_is_handle_value(function, handle)
        None => false
      }
    Some(FunctionArgument(index)) =>
      match function.arguments.get(index) {
        Some(argument) =>
          self.function_argument_type_is_function_pointer(argument.ty)
        None => false
      }
    Some(Access(base, index)) =>
      self.function_pointer_expression_is_var_pointer(function, base) ||
      self.function_expression_reads_var_pointer_load(function, index)
    Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      self.function_pointer_expression_is_var_pointer(function, base)
    _ => false
  }
}

///|
fn WgslIrLowerer::function_argument_type_is_function_pointer(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.shader_module.types.get(ty) {
    Some(type_) =>
      match type_.inner {
        Pointer(_, Function) | ValuePointer(_, _, Function) => true
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_reads_global_binding(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if self.function_expression_is_non_pointer_arena_name(function, value) {
    return false
  }
  match function.expressions.items.get(value.index()) {
    Some(GlobalVariable(_)) => true
    Some(Load(inner)) =>
      self.function_expression_reads_global_binding(function, inner)
    Some(Access(base, index)) =>
      self.function_expression_reads_global_binding(function, base) ||
      self.function_expression_reads_global_binding(function, index)
    Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      self.function_expression_reads_global_binding(function, base)
    _ => false
  }
}

///|
fn WgslIrLowerer::function_projection_expression_reads_pointer_base(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  match function.expressions.items.get(value.index()) {
    Some(Access(base, _))
    | Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      self.function_expression_reads_global_binding(function, base) ||
      self.function_expression_reads_var_pointer_load(function, base)
    _ =>
      self.function_expression_reads_global_binding(function, value) ||
      self.function_expression_reads_var_pointer_load(function, value)
  }
}

///|
fn WgslIrLowerer::function_expression_reads_var_pointer_load(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if self.function_expression_is_non_pointer_arena_name(function, value) {
    return false
  }
  match function.expressions.items.get(value.index()) {
    Some(LocalVariable(_)) | Some(FunctionArgument(_)) =>
      self.function_pointer_expression_is_var_pointer(function, value)
    Some(Load(pointer)) =>
      self.function_pointer_expression_is_var_pointer(function, pointer)
    Some(Access(base, index)) =>
      self.function_expression_reads_var_pointer_load(function, base) ||
      self.function_expression_reads_var_pointer_load(function, index)
    Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) =>
      self.function_expression_reads_var_pointer_load(function, base)
    _ => false
  }
}

///|
fn WgslIrLowerer::function_expression_can_materialize_to_let(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  match self.function_expression_type(function, value) {
    Some(ty) => self.type_can_materialize_to_local_let(ty)
    None => false
  }
}

///|
fn WgslIrLowerer::type_can_materialize_to_local_let(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.shader_module.types.get(ty) {
    Some(type_) =>
      match type_.inner {
        Scalar(_)
        | Vector(_, _)
        | Matrix(_, _, _)
        | CooperativeMatrix(_, _, _, _)
        | ValuePointer(_, _, _) => true
        Array(element, size, _) =>
          match size {
            Dynamic => false
            Constant(_) | Pending(_) =>
              self.type_can_materialize_to_local_let(element)
          }
        Struct(members, _) | PredeclaredStruct(members, _) => {
          for struct_member in members {
            if !self.type_can_materialize_to_local_let(struct_member.ty) {
              return false
            }
          }
          true
        }
        Atomic(_)
        | Pointer(_, _)
        | BindingArray(_, _)
        | Image(_, _, _)
        | Sampler(_)
        | AccelerationStructure(_)
        | RayQuery(_) => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::function_expression_can_return_directly(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  if wgsl_ir_function_expression_has_arena_name(function, value) {
    return true
  }
  match function.expressions.items.get(value.index()) {
    Some(expression) =>
      match expression {
        Literal(_)
        | Constant(_)
        | Override(_)
        | FunctionArgument(_)
        | Binary(_, _, _)
        | Compose(_, _)
        | Splat(_, _)
        | Access(_, _)
        | AccessIndex(_, _)
        | Component(_, _)
        | Swizzle(_, _, _)
        | Unary(_, _)
        | Select(_, _, _)
        | Bitcast(_, _)
        | Math(_, _, _, _, _)
        | Relational(_, _)
        | CooperativeLoad(_, _, _, _)
        | CooperativeMultiplyAdd(_, _, _) => true
        As(inner, _, _) =>
          self.function_as_expression_can_return_directly(function, inner)
        LocalVariable(local_handle) =>
          match function.local_variables.items.get(local_handle.index()) {
            Some(local_variable) => local_variable.kind == Let
            None => false
          }
        Load(inner) =>
          self.function_pointer_expression_can_return_directly(function, inner)
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::function_as_expression_can_return_directly(
  self : WgslIrLowerer,
  function : Function,
  inner : Handle,
) -> Bool {
  if self.function_expression_can_return_directly(function, inner) {
    return true
  }
  if wgsl_ir_function_expression_has_generated_temporary_snapshot(
      function, inner,
    ) {
    return true
  }
  match function.expressions.items.get(inner.index()) {
    Some(Load(pointer)) =>
      match function.expressions.items.get(pointer.index()) {
        Some(LocalVariable(_)) => true
        _ => false
      }
    _ => false
  }
}

///|
fn WgslIrLowerer::function_pointer_expression_can_return_directly(
  self : WgslIrLowerer,
  function : Function,
  value : Handle,
) -> Bool {
  ignore(self)
  match function.expressions.items.get(value.index()) {
    Some(LocalVariable(handle)) =>
      match function.local_variables.items.get(handle.index()) {
        Some(local_variable) => local_variable.kind == Let
        None => false
      }
    _ => false
  }
}