// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
priv struct CallArgumentPlanner {
lowerer : WgslIrLowerer
callee_function : Function
function : Function
scope : WgslIrFunctionScope
arguments : Array[@ast.WgslExprNode]
prelowered : Array[Handle?]
}
///|
fn WgslIrLowerer::call_argument_planner(
self : WgslIrLowerer,
arguments : Array[@ast.WgslExprNode],
callee_function : Function,
function : Function,
scope : WgslIrFunctionScope,
) -> CallArgumentPlanner {
let prelowered : Array[Handle?] = []
for _ in 0.. Array[Handle] raise WgslIrLowerError {
self.prelower_dependencies()
let handles : Array[Handle] = []
let mut index = 0
while index < self.arguments.length() {
if self.prelowered[index] == None &&
self.argument_is_deferred_const_like(self.arguments[index]) {
self.lower_later_source_let_arithmetic_before_direct(index)
ignore(self.lower_next_reference_run(index + 1))
} else if self.prelowered[index] == None &&
self.argument_uses_reference_predeclare(index) {
ignore(self.lower_reference_run(index))
}
if self.prelowered[index] == None &&
wgsl_ir_expr_node_is_function_argument_value(
self.arguments[index],
self.scope,
) {
self.lower_later_reference_before_direct(index)
}
let argument = match self.prelowered[index] {
Some(handle) => handle
None => self.lower_argument_at(index)
}
handles.push(argument)
index += 1
}
handles
}
///|
fn CallArgumentPlanner::prelower_dependencies(
self : CallArgumentPlanner,
) -> Unit raise WgslIrLowerError {
for index in 0.. Unit raise WgslIrLowerError {
guard self.has_later_pointer_argument(direct_index) else { return }
let mut lookahead = direct_index + 1
while lookahead < self.arguments.length() {
if self.prelowered[lookahead] != None {
lookahead += 1
continue
}
if self.lowerer.callee_parameter_is_pointer(self.callee_function, lookahead) {
return
}
if wgsl_ir_expr_node_is_call_value(self.arguments[lookahead]) ||
self.lowerer.expression_node_contains_user_function_call(
self.arguments[lookahead],
) {
return
}
if self.argument_is_pure_binary_value_expression(lookahead) {
self.prelowered[lookahead] = Some(self.lower_argument_at(lookahead))
return
}
if self.argument_is_existing_alias_reference(self.arguments[lookahead]) ||
self.lowerer.expression_node_is_inline_constant_reference(
self.arguments[lookahead],
self.scope,
) ||
wgsl_ir_expr_node_is_function_argument_value(
self.arguments[lookahead],
self.scope,
) {
lookahead += 1
continue
}
return
}
}
///|
fn CallArgumentPlanner::has_later_pointer_argument(
self : CallArgumentPlanner,
direct_index : Int32,
) -> Bool {
let mut index = direct_index + 1
while index < self.arguments.length() {
if self.lowerer.callee_parameter_is_pointer(self.callee_function, index) {
return true
}
index += 1
}
false
}
///|
fn CallArgumentPlanner::argument_is_pure_binary_value_expression(
self : CallArgumentPlanner,
index : Int32,
) -> Bool {
if self.argument_uses_reference_predeclare(index) {
return false
}
if self.lowerer.expression_node_contains_user_function_call(
self.arguments[index],
) {
return false
}
match wgsl_ir_expr_node_without_groups(self.arguments[index]).kind() {
Binary(_, _, _) => true
_ => false
}
}
///|
fn CallArgumentPlanner::argument_is_deferred_const_like(
self : CallArgumentPlanner,
argument : @ast.WgslExprNode,
) -> Bool {
wgsl_ir_expr_node_is_literal_like(argument) ||
wgsl_ir_expr_node_is_deferred_select_value(argument) ||
self.lowerer.expression_node_is_inline_constant_reference(
argument,
self.scope,
)
}
///|
fn CallArgumentPlanner::argument_uses_reference_predeclare(
self : CallArgumentPlanner,
index : Int32,
) -> Bool {
if self.lowerer.callee_parameter_is_pointer(self.callee_function, index) {
return false
}
self.argument_is_reference_access_chain(self.arguments[index])
}
///|
fn CallArgumentPlanner::value_argument_lowers_before_prior_direct(
self : CallArgumentPlanner,
index : Int32,
) -> Bool {
guard wgsl_ir_expr_node_is_call_value(self.arguments[index]) else {
return false
}
guard self.lowerer.expression_node_contains_writer_reference_value(
self.arguments[index],
self.function,
self.scope,
) else {
return false
}
for prior in 0.. Unit raise WgslIrLowerError {
let mut lookahead = direct_index + 1
while lookahead < self.arguments.length() {
if self.prelowered[lookahead] != None {
lookahead += 1
continue
}
if wgsl_ir_expr_node_is_function_argument_value(
self.arguments[lookahead],
self.scope,
) {
lookahead += 1
continue
}
if wgsl_ir_expr_node_is_call_value(self.arguments[lookahead]) {
return
}
if self.argument_is_pending_store_pointer_deref(lookahead) {
self.prelowered[lookahead] = Some(self.lower_argument_at(lookahead))
return
}
if self.argument_uses_reference_predeclare(lookahead) {
self.prelowered[lookahead] = Some(self.lower_argument_at(lookahead))
return
}
if self.argument_is_mutable_local_value(lookahead) {
self.prelowered[lookahead] = Some(self.lower_argument_at(lookahead))
return
}
if self.argument_is_existing_alias_reference(self.arguments[lookahead]) {
lookahead += 1
continue
}
if self.lowerer.expression_node_is_inline_constant_reference(
self.arguments[lookahead],
self.scope,
) {
lookahead += 1
continue
}
return
}
}
///|
fn CallArgumentPlanner::argument_is_pending_store_pointer_deref(
self : CallArgumentPlanner,
index : Int32,
) -> Bool {
if self.lowerer.callee_parameter_is_pointer(self.callee_function, index) {
return false
}
match self.scope.pending_store_pointer_argument_root {
Some(name) =>
wgsl_ir_expr_node_is_pointer_deref_of_argument_name(
self.arguments[index],
name,
)
None => false
}
}
///|
fn CallArgumentPlanner::lower_next_reference_run(
self : CallArgumentPlanner,
start : Int32,
) -> Int32 raise WgslIrLowerError {
let mut run_start = start
while run_start < self.arguments.length() &&
self.prelowered[run_start] == None &&
self.argument_is_existing_alias(self.arguments[run_start]) {
run_start += 1
}
if run_start < self.arguments.length() &&
self.prelowered[run_start] == None &&
self.argument_uses_reference_predeclare(run_start) {
self.lower_reference_run(run_start)
} else {
run_start
}
}
///|
fn CallArgumentPlanner::lower_reference_run(
self : CallArgumentPlanner,
start : Int32,
) -> Int32 raise WgslIrLowerError {
let mut index = start
while index < self.arguments.length() &&
self.prelowered[index] == None &&
self.argument_uses_reference_predeclare(index) {
index += 1
}
for run_index in start.. Bool {
match argument.kind() {
Group(inner) => self.argument_is_existing_alias(inner)
Identifier(name) =>
match self.scope.local_constants.get(name.name()) {
Some({ value: ExpressionAlias(_, _), .. }) => true
_ => false
}
_ => false
}
}
///|
fn CallArgumentPlanner::argument_is_mutable_local_value(
self : CallArgumentPlanner,
index : Int32,
) -> Bool {
if self.lowerer.callee_parameter_is_pointer(self.callee_function, index) {
return false
}
match self.arguments[index].kind() {
Group(_) => self.argument_is_mutable_local_value_node(self.arguments[index])
_ => self.argument_is_mutable_local_value_node(self.arguments[index])
}
}
///|
fn CallArgumentPlanner::argument_is_mutable_local_value_node(
self : CallArgumentPlanner,
argument : @ast.WgslExprNode,
) -> Bool {
match argument.kind() {
Group(inner) => self.argument_is_mutable_local_value_node(inner)
Identifier(name) =>
match self.scope.locals.get(name.name()) {
Some(handle) =>
match self.function.local_variables.items.get(handle.index()) {
Some(local_var) =>
local_var.kind == Var && !local_var.generated_temporary
None => false
}
None => false
}
_ => false
}
}
///|
fn CallArgumentPlanner::argument_is_existing_alias_reference(
self : CallArgumentPlanner,
argument : @ast.WgslExprNode,
) -> Bool {
match argument.kind() {
Group(inner) => self.argument_is_existing_alias_reference(inner)
Member(base, _) | Index(base, _) =>
self.argument_is_existing_alias_reference(base)
_ => self.argument_is_existing_alias(argument)
}
}
///|
fn CallArgumentPlanner::reference_has_dynamic_indexed_root(
self : CallArgumentPlanner,
node : @ast.WgslExprNode,
) -> Bool {
match node.kind() {
Group(inner) => self.reference_has_dynamic_indexed_root(inner)
Member(base, _) | Index(base, _) =>
self.reference_access_base_has_dynamic_index(base)
_ => false
}
}
///|
fn CallArgumentPlanner::reference_access_base_has_dynamic_index(
self : CallArgumentPlanner,
node : @ast.WgslExprNode,
) -> Bool {
match node.kind() {
Group(inner) => self.reference_access_base_has_dynamic_index(inner)
Member(base, _) => self.reference_access_base_has_dynamic_index(base)
Index(base, index) =>
!wgsl_ir_expr_node_is_static_literal_index(index) ||
self.reference_access_base_has_dynamic_index(base)
_ => false
}
}
///|
fn CallArgumentPlanner::argument_is_reference_access_chain(
self : CallArgumentPlanner,
node : @ast.WgslExprNode,
) -> Bool {
match node.kind() {
Group(inner) => self.argument_is_reference_access_chain(inner)
Member(base, _) | Index(base, _) =>
self.argument_access_root_is_reference(base)
_ => false
}
}
///|
fn CallArgumentPlanner::argument_access_root_is_reference(
self : CallArgumentPlanner,
node : @ast.WgslExprNode,
) -> Bool {
match node.kind() {
Group(inner) => self.argument_access_root_is_reference(inner)
Unary(op, inner) =>
op == Multiply && self.argument_access_root_is_reference(inner)
Member(base, _) | Index(base, _) =>
self.argument_access_root_is_reference(base)
Identifier(name) =>
self.scope.arguments.contains(name.name()) ||
self.argument_is_existing_alias(node) ||
self.lowerer.global_names.contains(name.name())
_ => false
}
}
///|
fn CallArgumentPlanner::lower_argument_at(
self : CallArgumentPlanner,
index : Int32,
) -> Handle raise WgslIrLowerError {
self.lowerer.lower_user_function_call_argument_at(
self.arguments[index],
self.callee_function,
index,
self.function,
self.scope,
)
}