Skip to content

Commit

Permalink
Simplify typing of builtin functions (#8329)
Browse files Browse the repository at this point in the history
- Implement some simple resolving of polymorphic return types when
  typing function calls, to replace the old hack of manually having to
  patch the return type of many builtin functions.
- Remove special handling of `diagonal` and `previous`, they can now be
  typed based on their definitions in ModelicaBuiltin.
- Simplify handling of `cardinality`, it can now be typed normally and
  only needs some checks that it's used in the correct context.
- Use a custom polymorphic type `__Any` to reduce the need for unboxing
  builtin functions.
- Add tests for some builtin functions that were lacking tests.
  • Loading branch information
perost committed Dec 15, 2021
1 parent b28ce89 commit d6cdbc5
Show file tree
Hide file tree
Showing 20 changed files with 368 additions and 204 deletions.
124 changes: 15 additions & 109 deletions OMCompiler/Compiler/NFFrontEnd/NFBuiltinCall.mo
Expand Up @@ -118,12 +118,10 @@ public
case "change" then typeChangeCall(call, next_context, info);
case "Clock" guard Config.synchronousFeaturesAllowed() then typeClockCall(call, next_context, info);
case "der" then typeDerCall(call, next_context, info);
case "diagonal" then typeDiagonalCall(call, next_context, info);
case "DynamicSelect" then typeDynamicSelectCall("DynamicSelect", call, next_context, info);
case "edge" then typeEdgeCall(call, next_context, info);
case "fill" then typeFillCall(call, next_context, info);
case "getInstanceName" then typeGetInstanceName(call);
//case "hold" guard Config.synchronousFeaturesAllowed() then typeHoldCall(call, next_context, info);
//case "initialState" guard Config.synchronousFeaturesAllowed() then typeInitialStateCall(call, next_context, info);
case "initial" then typeDiscreteCall(call, next_context, info);
case "inStream" then typeActualInStreamCall("inStream", call, next_context, info);
Expand All @@ -132,12 +130,10 @@ public
case "max" then typeMinMaxCall("max", call, next_context, info);
case "min" then typeMinMaxCall("min", call, next_context, info);
case "ndims" then typeNdimsCall(call, next_context, info);
//case "noClock" guard Config.synchronousFeaturesAllowed() then typeNoClockCall(call, next_context, info);
case "noEvent" then typeNoEventCall(call, next_context, info);
case "ones" then typeZerosOnesCall("ones", call, next_context, info);
case "potentialRoot" then typePotentialRootCall(call, next_context, info);
case "pre" then typePreCall(call, next_context, info);
case "previous" then typePreviousCall(call, next_context, info);
case "product" then typeProductCall(call, next_context, info);
case "promote" then typePromoteCall(call, next_context, info);
case "pure" then typePureCall(call, next_context, info);
Expand Down Expand Up @@ -638,49 +634,6 @@ protected
callExp := Expression.CALL(Call.makeTypedCall(fn, {arg}, variability, purity, ty));
end typeDerCall;

function typeDiagonalCall
input Call call;
input InstContext.Type context;
input SourceInfo info;
output Expression callExp;
output Type ty;
output Variability variability;
output Purity purity;
protected
ComponentRef fn_ref;
list<Expression> args;
list<NamedArg> named_args;
Expression arg;
Dimension dim;
Function fn;
algorithm
Call.UNTYPED_CALL(ref = fn_ref, arguments = args, named_args = named_args) := call;
assertNoNamedParams("diagonal", named_args, info);

if listLength(args) <> 1 then
Error.addSourceMessageAndFail(Error.NO_MATCHING_FUNCTION_FOUND_NFINST,
{Call.toString(call), "diagonal(Any[n]) => Any[n, n]"}, info);
end if;

(arg, ty, variability, purity) := Typing.typeExp(listHead(args), context, info);

ty := match ty
case Type.ARRAY(dimensions = {dim})
then Type.ARRAY(ty.elementType, {dim, dim});

else
algorithm
Error.addSourceMessage(Error.ARG_TYPE_MISMATCH,
{"1", ComponentRef.toString(fn_ref), "", Expression.toString(arg),
Type.toString(ty), "Any[:]"}, info);
then
fail();
end match;

{fn} := Function.typeRefCache(fn_ref);
callExp := Expression.CALL(Call.makeTypedCall(fn, {arg}, variability, purity, ty));
end typeDiagonalCall;

function typeEdgeCall
input Call call;
input InstContext.Type context;
Expand Down Expand Up @@ -1434,39 +1387,12 @@ protected
Error.addSourceMessageAndFail(Error.INVALID_CARDINALITY_CONTEXT, {}, info);
end if;

Call.UNTYPED_CALL(ref = fn_ref, arguments = args, named_args = named_args) := call;
assertNoNamedParams("cardinality", named_args, info);

if listLength(args) <> 1 then
Error.addSourceMessageAndFail(Error.NO_MATCHING_FUNCTION_FOUND_NFINST,
{Call.toString(call), ComponentRef.toString(fn_ref) + "(Connector) => Integer"}, info);
end if;

if InstContext.inFunction(context) then
Error.addSourceMessageAndFail(Error.EXP_INVALID_IN_FUNCTION,
{ComponentRef.toString(fn_ref)}, info);
{AbsynUtil.pathString(Call.functionName(call))}, info);
end if;

(arg, ty) := Typing.typeExp(listHead(args), context, info);

if not Expression.isCref(arg) then
Error.addSourceMessageAndFail(Error.ARGUMENT_MUST_BE_VARIABLE,
{"First", ComponentRef.toString(fn_ref), "<REMOVE ME>"}, info);
end if;

node := ComponentRef.node(Expression.toCref(arg));

if not (Type.isScalar(ty) and InstNode.isComponent(node) and Component.isConnector(InstNode.component(node))) then
Error.addSourceMessageAndFail(Error.ARG_TYPE_MISMATCH,
{"1", ComponentRef.toString(fn_ref), "",
Expression.toString(arg), Type.toString(ty), "connector"}, info);
end if;

{fn} := Function.typeRefCache(fn_ref);
ty := Type.INTEGER();
callExp := Expression.CALL(Call.makeTypedCall(fn, {arg}, var, purity, ty));
// TODO: Check cardinality restrictions, 3.7.2.3.

(callExp, ty, _, _) := typeBuiltinCallExp(call, context, info, vectorize = false);
System.setUsesCardinality(true);
end typeCardinalityCall;

Expand Down Expand Up @@ -2244,7 +2170,7 @@ protected
Call.typeMatchNormalCall(call, context, info, vectorize = false);
Structural.markExp(counter);
Structural.markExp(resolution);
callExp := Expression.CALL(Call.unboxArgs(ty_call));
callExp := Expression.CALL(ty_call);
end typeBackSampleCall;

function typeShiftSampleCall
Expand All @@ -2263,7 +2189,7 @@ protected
Call.typeMatchNormalCall(call, context, info, vectorize = false);
Structural.markExp(counter);
Structural.markExp(resolution);
callExp := Expression.CALL(Call.unboxArgs(ty_call));
callExp := Expression.CALL(ty_call);
end typeShiftSampleCall;

function typeSubSampleCall
Expand All @@ -2281,7 +2207,7 @@ protected
ty_call as Call.TYPED_CALL(arguments = {_, factor}, ty = ty, var = var) :=
Call.typeMatchNormalCall(call, context, info, vectorize = false);
Structural.markExp(factor);
callExp := Expression.CALL(Call.unboxArgs(ty_call));
callExp := Expression.CALL(ty_call);
end typeSubSampleCall;

function typeSuperSampleCall
Expand All @@ -2299,7 +2225,7 @@ protected
ty_call as Call.TYPED_CALL(arguments = {_, factor}, ty = ty, var = var) :=
Call.typeMatchNormalCall(call, context, info, vectorize = false);
Structural.markExp(factor);
callExp := Expression.CALL(Call.unboxArgs(ty_call));
callExp := Expression.CALL(ty_call);
end typeSuperSampleCall;

function typePureCall
Expand Down Expand Up @@ -2334,55 +2260,35 @@ protected
end match;
end typePureCall;

function typePreviousCall
function typeBuiltinCallExp
input Call call;
input InstContext.Type context;
input SourceInfo info;
output Expression callExp;
input Boolean vectorize = true;
output Expression outExp;
output Type ty;
output Variability var;
output Purity purity = Purity.PURE;
output Purity pur;
protected
list<Expression> args;
Expression arg;
Call c;
algorithm
args := Call.arguments(call);

if listLength(args) == 1 then
arg := listHead(args);

() := match arg
case Expression.CREF()
guard ComponentRef.isCref(arg.cref) and
InstNode.isComponent(ComponentRef.node(arg.cref))
then ();

else
algorithm
Error.addSourceMessage(Error.FUNCTION_ARGUMENT_MUST_BE,
{"previous", Gettext.translateContent(Error.COMPONENT_EXPRESSION)}, info);
then
fail();
end match;
end if;

(c, ty, var, purity) := typeBuiltinCall(call, context, info);
callExp := Expression.CALL(c);
end typePreviousCall;
(c, ty, var, pur) := typeBuiltinCall(call, context, info, vectorize);
outExp := Expression.CALL(c);
end typeBuiltinCallExp;

function typeBuiltinCall
input Call call;
input InstContext.Type context;
input SourceInfo info;
input Boolean vectorize = true;
output Call outCall;
output Type ty;
output Variability var;
output Purity pur;
protected
Call c;
algorithm
outCall := Call.typeMatchNormalCall(call, context, info);
outCall := Call.typeMatchNormalCall(call, context, info, vectorize);
ty := Call.typeOf(outCall);
var := Call.variability(outCall);
pur := Call.purity(outCall);
Expand Down
81 changes: 40 additions & 41 deletions OMCompiler/Compiler/NFFrontEnd/NFCall.mo
Expand Up @@ -372,11 +372,7 @@ public
args := listReverseInPlace(args);

ty := Function.returnType(func);

// Hack to fix return type of some builtin functions.
if Type.isPolymorphic(ty) then
ty := getSpecialReturnType(func, args);
end if;
ty := resolvePolymorphicReturnType(func, typed_args, ty);

if var == Variability.PARAMETER and Function.isExternal(func) then
// Mark external functions with parameter expressions as non-structural,
Expand Down Expand Up @@ -2662,46 +2658,49 @@ protected
end match;
end evaluateCallTypeDimExp;

function getSpecialReturnType
function resolvePolymorphicReturnType
"Resolves a polymorphic type to the actual type based on the inputs of a function."
input Function fn;
input list<Expression> args;
output Type ty;
algorithm
ty := match fn.path
case Absyn.IDENT("min")
then Type.arrayElementType(Expression.typeOf(Expression.unbox(listHead(args))));
case Absyn.IDENT("max")
then Type.arrayElementType(Expression.typeOf(Expression.unbox(listHead(args))));
case Absyn.IDENT("sum")
then Type.arrayElementType(Expression.typeOf(Expression.unbox(listHead(args))));
case Absyn.IDENT("product")
then Type.arrayElementType(Expression.typeOf(Expression.unbox(listHead(args))));
case Absyn.IDENT("previous")
then Type.arrayElementType(Expression.typeOf(listHead(args)));
case Absyn.IDENT("shiftSample")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("backSample")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("hold")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("superSample")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("subSample")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("noClock")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("DynamicSelect")
then Expression.typeOf(Expression.unbox(listHead(args)));
case Absyn.IDENT("pure")
then Expression.typeOf(Expression.unbox(listHead(args)));
else
algorithm
Error.assertion(false, getInstanceName() + ": unhandled case for " +
AbsynUtil.pathString(fn.path), sourceInfo());
input list<TypedArg> args;
input Type ty;
output Type outType;
protected
String name;
Type input_ty;
TypedArg arg;
list<TypedArg> rest_args = args;
algorithm
outType := match ty
case Type.POLYMORPHIC(name = name)
algorithm
// Go through the inputs until we find one with the same polymorphic
// type as the one we're looking for.
for i in fn.inputs loop
arg :: rest_args := rest_args;
input_ty := InstNode.getType(i);

if Type.isPolymorphicNamed(Type.arrayElementType(input_ty), name) then
// Replace the type with the corresponding argument type, but
// remove as many dimensions from it as the input has.
// For example: T[:] and Real[2, 3] gives T = Real[3]
outType := Type.unliftArrayN(Type.dimensionCount(input_ty), arg.ty);
return;
end if;
end for;
then
fail();

case Type.ARRAY(elementType = Type.POLYMORPHIC())
algorithm
// For an array of polymorphic types, only resolve the polymorphic
// type itself and keep the dimensions.
ty.elementType := resolvePolymorphicReturnType(fn, args, ty.elementType);
then
ty;

else ty;
end match;
end getSpecialReturnType;
end resolvePolymorphicReturnType;

annotation(__OpenModelica_Interface="frontend");
end NFCall;
34 changes: 34 additions & 0 deletions OMCompiler/Compiler/NFFrontEnd/NFExpression.mo
Expand Up @@ -5017,5 +5017,39 @@ public
end match;
end isFunctionPointer;

function isConnector
"Returns true if the expression is a component reference that refers to a
connector, otherwise false."
input Expression exp;
output Boolean res;
protected
InstNode node;
algorithm
res := match exp
case Expression.CREF()
algorithm
node := ComponentRef.node(exp.cref);
then
InstNode.isComponent(node) and InstNode.isConnector(node);

else false;
end match;
end isConnector;

function isComponentExpression
"Returns true if the expression is a component reference that refers to an
actual component (and not e.g. a function), otherwise false"
input Expression exp;
output Boolean res;
algorithm
res := match exp
case Expression.CREF()
then ComponentRef.isCref(exp.cref) and
InstNode.isComponent(ComponentRef.node(exp.cref));

else false;
end match;
end isComponentExpression;

annotation(__OpenModelica_Interface="frontend");
end NFExpression;
2 changes: 0 additions & 2 deletions OMCompiler/Compiler/NFFrontEnd/NFFunction.mo
Expand Up @@ -1619,7 +1619,6 @@ uniontype Function
// argument should be a cref?
case "change" then true;
case "der" then true;
case "diagonal" then true;
// Function should not be used in function context.
case "edge" then true;
// can have variable number of arguments
Expand Down Expand Up @@ -1649,7 +1648,6 @@ uniontype Function
// Function should not be used in function context.
// argument should be a cref?
case "pre" then true;
case "previous" then true;
// needs unboxing and return type fix.
case "product" then true;
case "promote" then true;
Expand Down

0 comments on commit d6cdbc5

Please sign in to comment.