Are arbitrary expressions allowed as input arguments in Compile?

Here's what I think is happening. If we look at the CompilePrint for both:

CompilePrint[cf2]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        Result = R1

1   R1 = MainEvaluate[ Function[{Times$796906}, a][ R0]]
2   Return
"
CompilePrint[cf1]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        R1 = 1.
        Result = R1

1   Return
"

This tells us that the argument which is assume to be Real, is just absorbed into R0. Then we see that Times$796906 there, which comes from the Head wrapping the argument.

We can see what happens with a different Head:

cf3 = Compile[{Hold[{a, _Integer}, m, {b, _Integer}]}, a];

CompilePrint[cf3]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        Result = R1

1   R1 = MainEvaluate[ Function[{Hold$802124}, a][ R0]]
2   Return
"

It seems Mathematica is interpreting this construct like

Compile[{ singleArgument }, expr]

since that singleArgument doesn't fit into the form of a "regular" variable, Compile takes its Head and tries to force the function that will be sent to MainEvaluate to be side-effect free using that. This can be made clear by looking at


cf4 = Compile[{{a}}, b];
CompilePrint[cf4]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        Result = R1

1   R1 = MainEvaluate[ Function[{a}, b][ R0]]
2   Return
"

Same compiled form as for the other functions, but in this case since we just had a symbolic argument Global`a, we've got no issues.

We get interesting behavior if we use

cf5 = Compile[{a[1]}, b];
CompilePrint[cf5]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        Result = R1

1   R1 = MainEvaluate[ Function[{a$809738}, b][ R0]]
2   Return
"

where it's clear that Compile is doing some localization of (for some reason) just the Head of its argument.

My usual tricks and hacks aren't having the usual effect, so I can't determine if exactly that Function argument is just directly fed to the main loop, but I think it is. Maybe someone else can find a way around the internal implementation of Unique or whatever they're using. Here's what I've tried for that

cf6 =
  With[{m = $ModuleNumber},
   With[{b = ToExpression["a$" <> ToString[m]]},
Internal`InheritedBlock[
 {Unique},
 Block[{$ModuleNumber = m - 1},
      Unprotect[Unique];
      Unique[a] := b;
      Compile[
       {a[1]},
       b
       ]
      ]
     ]
    ]
   ];
CompilePrint[cf6]

"
        1 argument
        2 Real registers
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        R0 = A1
        Result = R1

1   R1 = MainEvaluate[ Function[{a$809750}, a$809751][ R0]]
2   Return
"

Well to be honest Compile works in very strange ways (in more then one regard). I do not think that this is intended and I have not seen it docummented or in any code I came across.

From some experiments I think the following is happening: Dropping the type specifier automatically assumes _Real and variable "names" can be (as OP discovered) rather exotic. The probable reason for this rather loose behavior is that arguments inside the CompileFunction are refereed to as A1 to AN (for a CompileFunction with N). The argument names specified by the user are not used in the final function. If one drops the outer curly bracket comma separated expression get treated as real scalar arguments. So

Needs["CompiledFunctionTools`"];
Compile[{Sin[x^2]},(Sin[x^2])^2];
%
%//CompilePrint

results in

Compiled function

with Compile[{{x, _Real}}, (x)^2]; as an equivalent conventional input form.

Somewhat scary in this context is this Compile[{x, _Real}, x + _Real]; which is equivalent to Compile[{{x, _Real},{y,_Real}}, x + y];. I do not know how robust this is and on first glance it seems rather useless to input functions/arguments this way but one advantage I see is the possibility to use strings, sup-/superscripted values and more for argument names which allows for names which are normally impossible in Mathematica. E.g.:

Compile[{{"A_1", _Real}, {"A_2", _Real}}, ("A_1")^2 + "A_2"];
Compile[{{Subscript[A, 1], _Real}, {Subscript[A,2], _Real}}, (Subscript[A, 1])^2 + Subscript[A, 2]]

work as one would expect. This might be a use case for this curious find.

I would call the whole scenario a "feature" in the sense that this seems to be rather robust behavior related to the input parser of compile. That being said there is no guarantee that the current behavior will persist across different versions of the software.

I always use CompilePrint to check the outputted CompiledFunction for obvious errors, unevaluated expressions (e.g. If[2==2,...]) and especially MainEvaluate[...] since in my experience (using compiled functions inside NDSolve) just one MainEvaluate[...] completely eliminates any performance benefit.

Tags:

Syntax

Compile