Skip to content

Commit

Permalink
Add stack overflow checks without signal handlers
Browse files Browse the repository at this point in the history
Do not check for stack overflow in simulation code.
  • Loading branch information
sjoelund authored and OpenModelica-Hudson committed Sep 2, 2015
1 parent d8ff3e0 commit 3fd5375
Show file tree
Hide file tree
Showing 17 changed files with 358 additions and 87 deletions.
1 change: 1 addition & 0 deletions Compiler/FrontEnd/DAE.mo
Expand Up @@ -1580,6 +1580,7 @@ end MatchCase;

public uniontype MatchType
record MATCHCONTINUE end MATCHCONTINUE;
record TRY_STACKOVERFLOW end TRY_STACKOVERFLOW;
record MATCH
Option<tuple<Integer,Type,Integer>> switch "The index of the pattern to switch over, its type and the value to divide string hashes with";
end MATCH;
Expand Down
2 changes: 1 addition & 1 deletion Compiler/FrontEnd/InstSection.mo
Expand Up @@ -2436,7 +2436,7 @@ algorithm
DAE.CASE({}, NONE(), {}, else_branch, SOME(DAE.TUPLE({})), info, 0, info)
};

exp := DAE.MATCHEXPRESSION(DAE.MATCHCONTINUE(), {}, {}, {}, cases,
exp := DAE.MATCHEXPRESSION(if SCode.commentHasBooleanNamedAnnotation(inStatement.comment, "__OpenModelica_stackOverflowCheckpoint") then DAE.TRY_STACKOVERFLOW() else DAE.MATCHCONTINUE(), {}, {}, {}, cases,
DAE.T_NORETCALL_DEFAULT);
then
{DAE.STMT_NORETCALL(exp, source)};
Expand Down
115 changes: 71 additions & 44 deletions Compiler/Script/CevalScript.mo
Expand Up @@ -94,6 +94,7 @@ import Parser;
import Print;
import SCodeDump;
import SimCodeFunction;
import StackOverflow;
import System;
import Static;
import SCode;
Expand Down Expand Up @@ -2177,6 +2178,33 @@ end cevalCallFunction;
protected

function cevalCallFunctionEvaluateOrGenerate
"This function evaluates CALL expressions, i.e. function calls.
They are currently evaluated by generating code for the function and
then dynamicly load the function and call it."
input FCore.Cache inCache;
input FCore.Graph inEnv;
input DAE.Exp inExp;
input list<Values.Value> inValuesValueLst;
input Boolean impl;
input Option<GlobalScript.SymbolTable> inSymTab;
input Absyn.Msg inMsg;
input Boolean bIsCompleteFunction;
output FCore.Cache outCache;
output Values.Value outValue;
output Option<GlobalScript.SymbolTable> outSymTab;
algorithm
try
(outCache,outValue,outSymTab) := cevalCallFunctionEvaluateOrGenerate2(inCache,inEnv,inExp,inValuesValueLst,impl,inSymTab,inMsg,bIsCompleteFunction);
else
Error.addInternalError("Stack overflow when evaluating function call: "+ExpressionDump.printExpStr(inExp)+"...\n"+stringDelimitList(StackOverflow.getStacktraceMessages(), "\n"), match inMsg local SourceInfo info; case Absyn.MSG(info) then info; else sourceInfo(); end match);
/* Do not fail or we can loop too much */
outCache := inCache;
outSymTab := inSymTab;
outValue := Values.META_FAIL();
end try annotation(__OpenModelica_stackOverflowCheckpoint=true);
end cevalCallFunctionEvaluateOrGenerate;

function cevalCallFunctionEvaluateOrGenerate2
"This function evaluates CALL expressions, i.e. function calls.
They are currently evaluated by generating code for the function and
then dynamicly load the function and call it."
Expand Down Expand Up @@ -2262,8 +2290,8 @@ algorithm
true = bIsCompleteFunction;
true = Flags.isSet(Flags.GEN);
failure(cevalIsExternalObjectConstructor(cache,funcpath,env,msg));
if Flags.isSet(Flags.DYN_LOAD) then
Debug.traceln("[dynload]: [func from file] check if is in CF list: " + Absyn.pathString(funcpath));
if Flags.isSet(Flags.DYN_LOAD) and Flags.isSet(Flags.FAILTRACE) then
print("[dynload]: [func from file] check if is in CF list: " + Absyn.pathString(funcpath));
end if;

(true, funcHandle, buildTime, fOld) = Static.isFunctionInCflist(cflist, funcpath);
Expand All @@ -2272,13 +2300,15 @@ algorithm
false = stringEq(fNew,""); // see if the WE have a file or not!
false = Static.needToRebuild(fNew,fOld,buildTime); // we don't need to rebuild!

if Flags.isSet(Flags.DYN_LOAD) then
if Flags.isSet(Flags.DYN_LOAD) and Flags.isSet(Flags.FAILTRACE) then
print("[dynload]: [func from file] About to execute function present in CF list: " + Absyn.pathString(funcpath) + "\n");
end if;

print_debug = Flags.isSet(Flags.DYN_LOAD);
newval = DynLoad.executeFunction(funcHandle, vallst, print_debug);
//print("CALL: [func from file] CF LIST:\n\t" + stringDelimitList(List.map(cflist, Interactive.dumpCompiledFunction), "\n\t") + "\n");
if Flags.isSet(Flags.DYN_LOAD) and Flags.isSet(Flags.FAILTRACE) then
print("CALL: [func from file] CF LIST:\n\t" + stringDelimitList(List.map(cflist, Interactive.dumpCompiledFunction), "\n\t") + "\n");
end if;
then
(cache,newval,st);

Expand Down Expand Up @@ -2312,10 +2342,8 @@ algorithm

// not in CF list, we have a symbol table, generate function and update symtab
case (cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR(builtin = false))),vallst,_,
SOME(syt as GlobalScript.SYMBOLTABLE(p as Absyn.PROGRAM(),a,b,c,cf,lf)), msg, _) // yeha! we have a symboltable!
equation
true = bIsCompleteFunction;
true = Flags.isSet(Flags.GEN);
SOME(syt as GlobalScript.SYMBOLTABLE(p as Absyn.PROGRAM(),a,b,c,cf,lf)), msg, _) guard (bIsCompleteFunction and Flags.isSet(Flags.GEN)) // yeha! we have a symboltable!
algorithm
failure(cevalIsExternalObjectConstructor(cache,funcpath,env,msg));

if Flags.isSet(Flags.DYN_LOAD) then
Expand All @@ -2324,9 +2352,9 @@ algorithm

// remove it and all its dependencies as it might be there with an older build time.
// get dependencies!
(_, functionDependencies, _) = getFunctionDependencies(cache, funcpath);
(_, functionDependencies, _) := getFunctionDependencies(cache, funcpath);
//print("\nFunctions before:\n\t" + stringDelimitList(List.map(cf, Interactive.dumpCompiledFunction), "\n\t") + "\n");
newCF = Interactive.removeCfAndDependencies(cf, funcpath::functionDependencies);
newCF := Interactive.removeCfAndDependencies(cf, funcpath::functionDependencies);
//print("\nFunctions after remove:\n\t" + stringDelimitList(List.map(newCF, Interactive.dumpCompiledFunction), "\n\t") + "\n");

if Flags.isSet(Flags.DYN_LOAD) then
Expand All @@ -2336,28 +2364,29 @@ algorithm
//print("\nfunctions in SYMTAB: " + Interactive.dumpCompiledFunctions(syt)

// now is safe to generate code
(cache, funcstr, fileName) = cevalGenerateFunction(cache, env, p, funcpath);
print_debug = Flags.isSet(Flags.DYN_LOAD);
libHandle = System.loadLibrary(fileName, print_debug);
funcHandle = System.lookupFunction(libHandle, stringAppend("in_", funcstr));
newval = DynLoad.executeFunction(funcHandle, vallst, print_debug);
(cache, funcstr, fileName) := cevalGenerateFunction(cache, env, p, funcpath);
print_debug := Flags.isSet(Flags.DYN_LOAD);
libHandle := System.loadLibrary(fileName, print_debug);
funcHandle := System.lookupFunction(libHandle, stringAppend("in_", funcstr));
newval := DynLoad.executeFunction(funcHandle, vallst, print_debug);

System.freeLibrary(libHandle, print_debug);
buildTime = System.getCurrentTime();
buildTime := System.getCurrentTime();
// update the build time in the class!
Absyn.CLASS(_,_,_,_,Absyn.R_FUNCTION(_),_,info) = Interactive.getPathedClassInProgram(funcpath, p);
Absyn.CLASS(_,_,_,_,Absyn.R_FUNCTION(_),_,info) := Interactive.getPathedClassInProgram(funcpath, p);

/* info = Absyn.setBuildTimeInInfo(buildTime,info);
ts = Absyn.setTimeStampBuild(ts, buildTime); */
w = Interactive.buildWithin(funcpath);
w := Interactive.buildWithin(funcpath);

if Flags.isSet(Flags.DYN_LOAD) then
print("[dynload]: Updating build time for function path: " + Absyn.pathString(funcpath) + " within: " + Dump.unparseWithin(w) + "\n");
end if;

// p = Interactive.updateProgram(Absyn.PROGRAM({Absyn.CLASS(name,ppref,fpref,epref,Absyn.R_FUNCTION(funcRest),body,info)},w,ts), p);
f = Absyn.getFileNameFromInfo(info);
f := Absyn.getFileNameFromInfo(info);

syt = GlobalScript.SYMBOLTABLE(
syt := GlobalScript.SYMBOLTABLE(
p, a, b, c,
GlobalScript.CFunction(funcpath,DAE.T_UNKNOWN({funcpath}),funcHandle,buildTime,f)::newCF,
lf);
Expand All @@ -2371,48 +2400,46 @@ algorithm
(cache,newval,SOME(syt));

// no symtab, WE SHOULD NOT EVALUATE! but we do anyway with suppressed error messages!
case (cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR(builtin = false))),vallst,_,NONE(), msg, _) // crap! we have no symboltable!
equation
true = bIsCompleteFunction;
true = Flags.isSet(Flags.GEN);
case (cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR(builtin = false))),vallst,_,NONE(), msg, _) guard (bIsCompleteFunction and Flags.isSet(Flags.GEN)) // crap! we have no symboltable!
algorithm
failure(cevalIsExternalObjectConstructor(cache,funcpath,env,msg));
ErrorExt.setCheckpoint("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB");

if Flags.isSet(Flags.DYN_LOAD) then
print("[dynload]: [NO SYMTAB] not in in CF list: " + Absyn.pathString(funcpath) + "\n");
end if;

// we might actually have a function loaded here already!
// we need to unload all functions to not get conflicts!
p = FCore.getProgramFromCache(cache);
(cache,funcstr,fileName) = cevalGenerateFunction(cache, env, p, funcpath);
p := FCore.getProgramFromCache(cache);

ErrorExt.setCheckpoint("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB");

try
(cache,funcstr,fileName) := cevalGenerateFunction(cache, env, p, funcpath);
ErrorExt.rollBack("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB"); // Should be delete?
else
ErrorExt.rollBack("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB");
fail();
end try;

// generate a uniquely named dll!
if Flags.isSet(Flags.DYN_LOAD) then
print("[dynload]: cevalCallFunction: about to execute " + funcstr + "\n");
end if;
print_debug = Flags.isSet(Flags.DYN_LOAD);
libHandle = System.loadLibrary(fileName, print_debug);
funcHandle = System.lookupFunction(libHandle, stringAppend("in_", funcstr));
newval = DynLoad.executeFunction(funcHandle, vallst, print_debug);
print_debug := Flags.isSet(Flags.DYN_LOAD);
libHandle := System.loadLibrary(fileName, print_debug);
funcHandle := System.lookupFunction(libHandle, stringAppend("in_", funcstr));
newval := DynLoad.executeFunction(funcHandle, vallst, print_debug);

System.freeFunction(funcHandle, print_debug);
System.freeLibrary(libHandle, print_debug);

if Flags.isSet(Flags.DYN_LOAD) then
Debug.traceln("CALL: [NO SYMTAB] not in in CF list [finished]: " + Absyn.pathString(funcpath));
print("CALL: [NO SYMTAB] not in in CF list [finished]: " + Absyn.pathString(funcpath));
end if;
ErrorExt.rollBack("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB");
then
(cache,newval,NONE());

// cleanup the case below when we failed. we should delete generated files too
case (cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR(builtin = false))),_,_,NONE(),msg, _) // crap! we have no symboltable!
equation
true = bIsCompleteFunction;
true = Flags.isSet(Flags.GEN);
failure(cevalIsExternalObjectConstructor(cache,funcpath,env,msg));
ErrorExt.rollBack("cevalCallFunctionEvaluateOrGenerate_NO_SYMTAB");
then
fail();
(cache,newval,NONE());

case (_,_,(DAE.CALL(path = funcpath)),_,_,_, _, _)
equation
Expand All @@ -2428,7 +2455,7 @@ algorithm
fail();

end matchcontinue;
end cevalCallFunctionEvaluateOrGenerate;
end cevalCallFunctionEvaluateOrGenerate2;

function cevalIsExternalObjectConstructor
input FCore.Cache cache;
Expand Down
35 changes: 32 additions & 3 deletions Compiler/Template/CodegenCFunctions.tpl
Expand Up @@ -981,7 +981,7 @@ case FUNCTION(__) then
<%functionPrototype(fname, functionArguments, outVars, false, visibility, isSimulation)%>
{
<%varDecls%>
_tailrecursive: OMC_LABEL_UNUSED
<% if boolNot(isSimulation) then 'MMC_SO();<%\n%>'%>_tailrecursive: OMC_LABEL_UNUSED
<%varInits%>
<%bodyPart%>
_return: OMC_LABEL_UNUSED
Expand Down Expand Up @@ -6349,7 +6349,9 @@ template daeExpMatch2(Exp exp, list<Exp> tupleAssignExps, Text res, Text startIn
::=
match exp
case exp as MATCHEXPRESSION(__) then
let () = codegenPushTryThrowIndex(System.tmpTick())
let mydummy = match exp.matchType
case TRY_STACKOVERFLOW() then ""
else codegenPushTryThrowIndex(System.tmpTick())
let goto = 'goto_<%codegenPeekTryThrowIndex()%>'
let &preExpInner = buffer ""
let &preExpRes = buffer ""
Expand Down Expand Up @@ -6386,6 +6388,33 @@ case exp as MATCHEXPRESSION(__) then
else tempDecl('volatile mmc_switch_type', &varDeclsInner)
let done = tempDecl('int', &varDeclsInner)
let &preExp +=
match exp.matchType
case TRY_STACKOVERFLOW() then
let &varDeclsInner = buffer ""
(match exp.cases
case {try as CASE(__),else as CASE(__)} then
let tryb = (try.body |> stmt => algStatement(stmt, context, &varDeclsInner, &auxFunction); separator="\n")
let elseb = (else.body |> stmt => algStatement(stmt, context, &varDeclsInner, &auxFunction); separator="\n")
<<
<%endModelicaLine()%>
{ /* stack overflow check */
<%varDeclsInput%>
<%preExpInput%>
<%expInput%>
{
<%varDeclsInner%>
<%preExpInner%>
MMC_TRY_STACK()
<%tryb%>
MMC_ELSE_STACK()
<%elseb%>
MMC_CATCH_STACK()
}
}
>>
else error(sourceInfo(), 'Got stack overflow block with more than 2 cases')
)
else
<<
<%endModelicaLine()%>
{ /* <% match exp.matchType case MATCHCONTINUE(__) then "matchcontinue expression" case MATCH(__) then "match expression" %> */
Expand All @@ -6402,7 +6431,7 @@ case exp as MATCHEXPRESSION(__) then
<%ix%> = 0;
<%done%> = 0;
<% match exp.matchType case MATCHCONTINUE(__) then
/* One additional MMC_TRY_INTERNAL() for each caught exception
/* One additional MMC_TRY_INTERNAL() for each caught exceptionOne additional MMC_TRY_INTERNAL() for each caught exception
* You would expect you could do the setjmp only once, but some counters I guess are stored in registers and would need to become volatile
* This is still a lot faster than doing MMC_TRY_INTERNAL() inside the for-loop
*/
Expand Down
3 changes: 2 additions & 1 deletion Compiler/Template/SimCodeTV.mo
Expand Up @@ -2064,7 +2064,8 @@ package DAE
end Subscript;

uniontype MatchType
record MATCHCONTINUE end MATCHCONTINUE;
record MATCHCONTINUE end MATCHCONTINUE;
record TRY_STACKOVERFLOW end TRY_STACKOVERFLOW;
record MATCH
Option<tuple<Integer,Type,Integer>> switch;
end MATCH;
Expand Down
33 changes: 28 additions & 5 deletions Compiler/Util/DynLoad.mo
Expand Up @@ -39,15 +39,38 @@ encapsulated package DynLoad

public import Values;

public function executeFunction "
protected

import Error;
import StackOverflow;

public

function executeFunction "
Executes a function given a handle (from System.lookupFunction) and a list
of values."
input Integer inFuncHandle;
input list<Values.Value> inValLst;
input Boolean inPrintDebug;
input Integer handle;
input list<Values.Value> values;
input Boolean debug;
output Values.Value outVal;
protected

function executeFunction_internal
input Integer handle;
input list<Values.Value> values;
input Boolean debug;
output Values.Value outVal;
external "C" outVal=DynLoad_executeFunction(OpenModelica.threadData(),handle,values,debug) annotation(Library = "omcruntime");
end executeFunction_internal;

external "C" outVal=DynLoad_executeFunction(OpenModelica.threadData(),inFuncHandle,inValLst,inPrintDebug) annotation(Library = "omcruntime");
algorithm
StackOverflow.clearStacktraceMessages();
// executeFunction returns Values.META_FAIL on stack overflow...
outVal := executeFunction_internal(handle, values, debug);
if StackOverflow.hasStacktraceMessages() then
Error.addInternalError("Stack overflow when evaluating function:\n"+ stringDelimitList(StackOverflow.readableStacktraceMessages(), "\n"), sourceInfo());
// fail(); // Causes seg.fault for some reason.
end if;
end executeFunction;

annotation(__OpenModelica_Interface="frontend");
Expand Down

0 comments on commit 3fd5375

Please sign in to comment.