Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Make docs consistent

svn path=/trunk/mono/; revision=76035
  • Loading branch information...
commit 2279f440996923ac66a6ea85cf101d89615aad69 1 parent 5ac7b57
Miguel de Icaza migueldeicaza authored
Showing with 727 additions and 564 deletions.
  1. +369 −298 docs/abc-removal.txt
  2. +46 −31 docs/aot-compiler.txt
  3. +312 −235 docs/exception-handling.txt
667 docs/abc-removal.txt
View
@@ -1,302 +1,373 @@
-Here "abc" stays for "array bounds check", or "array bound checks", or
-some combination of the two...
-
-------------------------------------------------------------------------------
-USAGE
-
-Simply use the "abcrem" optimization invoking mono.
-
-To see if bound checks are actually removed, use "mono -v" and grep for
-"ARRAY-ACCESS" in the output, there should be a message for each check
-that has been removed.
-
-To trace the algorithm execution, use "-v -v -v", and be prepared to be
-totally submersed by debugging messages...
-
-------------------------------------------------------------------------------
-EFFECTIVENESS
-
-The abc removal code can now always remove bound checks from "clean"
-array scans in loops, and generally anyway there are clear conditions that
-already state that the index is "safe".
+ Arrays Bounds Check Elimination (ABC)
+ in the Mono Runtime
-To be clearer, and give an idea of what the algorithm can and cannot do
-without describing it in detail... keep in mind that only "redunant" checks
-cannot be removed. By "redundant", I mean "already explicitly checked" in
-the method code.
-
-Unfortunately, analyzing complex expressions is not so easy (see below for
-details), so the capabilities of this "abc remover" are limited.
-
-These are simple guidelines:
-- Only expressions of the following kinds are handled:
- - constant
- - variable [+/- constant]
-- Only comparisons between "handled" expressions are understood.
-- "switch" statements are not yet handled.
-
-This means that code like this will be handled well:
-
-for (int i = 0; i < a.Length; i++) {
- a [i] = .....
-}
-
-The "i" variable could be declared out of the "for", the "for" could be a
-"while", and maybe even implemented with "goto", the array could be scanned
-in reverse order, and everything would still work.
-I could have a temporary variable storing the array length, and check on
-it inside the loop, and the abc removal would still occurr, like this:
-
-int i = 0;
-int l = a.Length;
-while ( i < l ) {
- a [i] ......
-}
-
-or this:
-
-int l = a.Length;
-for (int i = l; i > 0; i--) {
- a [i] = .....
-}
-
-The following two examples would work:
-
-for (int i = 0; i < (a.Length -1); i++) .....
-for (int i = 0; i < a.Length; i += 2) .....
-
-But just something like this
-
-int delta = -1;
-for (int i = 0; i < (a.Length + delta); i++) .....
-
-or like this
-
-int delta = +2;
-for (int i = 0; i < a.Length; i += delta) .....
-
-would not work, the check would stay there, sorry :-(
-(unless a combination of cfold, consprop and copyprop is used, too, which
-would make the constant value of "delta" explicit).
-
-Just to make you understand how things are tricky... this would work!
-
-int limit = a.Length - 1;
-for (int i = 0; i < limit; i++) {
- a [i] = .....
-}
-
-
-A detailed explanation of the reason why things are done like this is given
-below...
-
-------------------------------------------------------------------------------
-THE ALGORITHM
-
-This array bound check removal (abc removal) algorithm is based on
-symbolic execution concepts. I handle the removal like an "unreachable
-code elimination" (and in fact the optimization could be extended to remove
-also other unreachable sections of code, due to branches that "always go the
-same way").
-
-In symbolic execution, variables do not have actual (numeric) values, but
-instead symbolic expressions (like "a", or "x+y").
-Also, branch conditions are handled like symbolic conditions (like "i<k"),
-which state relations between variable values.
-
-The SSA representation inside mini is somewhat close to a symbolic
-representation of the execution of the compiled method.
-Particularly, the representation of variable values is exactly a symbolic
-one. It is enough to find all CEE_STIND_* instructions which store to a
-local variable, and their second argument is exactly the variable value.
-Actually, "cfg->vars [<variable-index>]->def" should contain exactly
-those store instructions, and the "get_variable_value_from_ssa_store"
-function extracts the variable value from there.
-
-On the other hand, the conditions under which each basic block is
-executed are not made fully explicit.
-
-However, it is not difficult to make them so.
-Each BB that has more than one exit BB, in practice must end either with
-a conditional branch instruction or with a switch instruction.
-In the first case, the BB has exactly two exit BBs, and their execution
-conditions are easy to get from the condition of the branch (see the
-"get_relation_from_branch_instruction" function, and expecially the end of
-"analyze_block" in abcremoval.c.
-If there is a switch, the jump condition of every exit BB is the equality
-of the switch argument with the particular index associated with its case
-(but the current implementation does not handle switch statements yet).
-
-These individual conditions are in practice associated to each arc that
-connects BBs in the CFG (with the simple case that unconditional branches
-have a "TRUE" condition, because they always happen).
-
-So, for each BB, its *proper* entry condition is the union of all the
-conditions associated to arcs that enter the BB. The "union" is like a
-logical "or", in the sense that either of the condition could be true,
-they are not necessarily all true. This means that if I can enter a BB
-in two ways, and in one case I know that "x>0", and in the other that
-"x==0", actually in the BB I know that "x>=0", which is a weaker
-condition (the union of the two).
-
-Also, the *complete* entry condition for a BB is the "intersection" of all
-the entry conditions of its dominators. This is true because each dominator
-is the only way to reach the BB, so the entry condition of each dominator
-must be true if the control flow reached the BB. This translates to the
-logical "and" of all the "proper" conditions of the BBs met walking up in the
-dominator tree. So, if one says "x>0", and another "x==1", then I know
-that "x==1", which is a stronger condition (the intersection of the two).
-Note that, if the two conditions were "x>0" and "x==0", then the block would
-be unreachable (the intersection is empty), because some branch is impossible.
-
-Another observation is that, inside each BB, every variable is subject to the
-complete entry condition of that very same BB, and not the one in which it is
-defined (with the "complete entry condition" being the thing I defined before,
-sorry if these terms "proper" and "complete" are strange, I found nothing
-better).
-This happens because the branch conditions are related to the control flow.
-I can define "i=a", and if I am in a BB where "a>0", then "i>0", but not
-otherwise.
-
-So, intuitively, if the available conditions say "i>=0", and i is used as an
-index in an array access, then the lower bound check can be omitted.
-If the condition also says "(i>=0)&&(i<array.length)", the abc removal can
-occur.
-
-So, a complete solution to the problem of abc removal would be the following:
-for each array access, build a system of equations containing:
-[1] all the symbolic variable definitions
-[2] the complete entry condition of the BB in which the array access occurs
-[3] the two "goal functions" ("index >=0" and "index < array.length")
-If the system is valid for *each possible* variable value, then the goal
-functions are always true, and the abc can be removed.
-
-All this discussion is useful to give a precise specification to the problem
-we are trying to solve.
-The trouble is that, in the general case, the resulting system of equations
-is like a predicate in first order logic, which is semi-decidable, and its
-general solution is anyway too complex to be attempted in a JIT compiler
-(which should not contain a full fledged theorem prover).
-
-Therefore, we must cut some corner.
-
-
-By the way, there is also another big problem, which is caused by "recursive"
-symbolic definitions. These definition can (and generally do) happen every
-time there is a loop. For instance, in the following piece of code
-
-for ( int i = 0; i < array.length; i++ ) {
- Console.WriteLine( "array [i] = " + array [i] );
-}
-
-one of the definitions of i is a PHI that can be either 0 or "i + 1".
-
-Now, we know that mathematically "i = i + 1" does not make sense, and in
-fact symbolic values are not "equations", they are "symbolic definitions".
-
-The actual symbolic value of i is a generic "n", where "n" is the number of
-iterations of the loop, but this is terrible to handle (and in more complex
-examples the symbolic value of i simply cannot be written, because i is
-calculated in an iterative way).
-
-However, the definition "i = i + 1" tells us something about i: it tells us
-that i "grows". So (from the PHI definition) we know that i is either 0, or
-"grows". This is enough to tell that "i>=0", which is what we want!
-It is important to note that recursive definitions can only occurr inside
-PHI definitions, because actually a variable cannot be defined *only* in terms
-of itself!
-
-
-At this point, I can explain which corners I want to cut to make the
-problem solvable. It will not remove all the abc that could theoretically
-be removed, but at least it will work.
-
-The easiest way to cut corners is to only handle expressions which are
-"reasonably simple", and ignore the rest.
-Keep in mind that ignoring an expression is not harmful in itself.
-The algorithm will be simply "less powerful", because it will ignore
-conditions that could have caused to the removal of an abc, but will
-not remove checks "by mistake" (so the resulting code will be in any case
-correct).
-
-The expressions we handle are the following (all of integer type):
-- constant
-- variable
-- variable + constant
-- constant + variable
-- variable - constant
-
-And, of course, PHI definitions.
-Any other expression causes the introduction of an "any" value in the
-evaluation, which makes all values that depend from it unknown as well.
-
-We will call these kind of definitions "summarizable" definitions.
-
-In a first attempt, we can consider only branch conditions that have the
-simplest possible form (the comparison of two summarizable expressions).
-
-We can also simplify the effect of variable definitions, keeping only
-what is relevant to know: their value range with respect to zero and with
-respect to the length of the array we are currently handling.
-
-One particular note on PHI functions: they work (obviously) like the logical
-"or" of their definitions, and therefore are equivalent to the "logical or"
-of the summarization of their definitions.
-
-About recursive definitions (which, believe me, are the worst thing in all
-this mess), we handle only "monotonic" ones. That is, we try to understand
-if the recursive definition (which, as we said above, must happen because
-of a loop) always "grows" or "gets smaller". In all other cases, we decide
-we cannot handle it.
-
-
-One critical thing, once we have defined all these data structures, is how
-the evaluation is actually performed.
-
-In a first attempt I coded a "brute force" approach, which for each BB
-tried to examine all possible conditions between all variables, filling
-a sort of "evaluation matrix". The problem was that the complexity of this
-evaluation was quadratic (or worse) on the number of variables, and that
-many wariables were examined even if they were not involved in any array
-access.
-
-Following the ABCD paper (http://citeseer.ist.psu.edu/bodik00abcd.html),
-I rewrote the algorithm in a more "sparse" way.
-Now, the main data structure is a graph of relations between variables, and
-each attempt to remove a check performs a traversal of the graph, looking
-for a path from the index to the array length that satisfies the properties
-"index >= 0" and "index < length". If such a path is found, the check is
-removed. It is true that in theory *each* traversal has a complexity which
-is exponential on the number of variables, but in practice the graph is not
-very connected, so the traversal terminates quickly.
-
-
-Then, the algorithm to optimize one method looks like this:
-
-[1] Preparation:
- [1a] Build the SSA representation.
- [1b] Prepare the evaluation graph (empty)
- [1b] Summarize each varible definition, and put the resulting relations
- in the evaluation graph
-[2] Analyze each BB, starting from the entry point and following the
- dominator tree:
- [2a] Summarize its entry condition, and put the resulting relations
- in the evaluation graph (this is the reason why the BBs are examined
- following the dominator tree, so that conditions are added to the
- graph in a "cumulative" way)
- [2b] Scan the BB instructions, and for each array access perform step [3]
- [2c] Process children BBs following the dominator tree (step [2])
- [2d] Remove from the evaluation area the conditions added at step [2a]
- (so that backtracking along the tree the area is properly cleared)
-[3] Attempt the removal:
- [3a] Summarize the index expression, to see if we can handle it; there
- are three cases: the index is either a constant, or a variable (with
- an optional delta) or cannot be handled (is a "any")
- [3b] If the index can be handled, traverse the evaluation area searching
- a path from the index variable to the array length (if the index is
- a constant, just examine the array length to see if it has some
- relation with this constant)
- [3c] Use the results of step [3b] to decide if the check is redundant
+ Massimiliano Mantione (mass@ximian.com)
+Here "abc" stays for "array bounds check", or "array bound checks", or
+some combination of the two.
+
+* Usage
+
+ Simply use the "abcrem" optimization invoking mono.
+
+ To see if bound checks are actually removed, use "mono -v" and
+ grep for "ARRAY-ACCESS" in the output, there should be a
+ message for each check that has been removed.
+
+ To trace the algorithm execution, use "-v -v -v", and be
+ prepared to be totally submersed by debugging messages...
+
+* Effectiveness
+
+ The abc removal code can now always remove bound checks from
+ "clean" array scans in loops, and generally anyway there are
+ clear conditions that already state that the index is "safe".
+
+ To be clearer, and give an idea of what the algorithm can and
+ cannot do without describing it in detail... keep in mind that
+ only "redunant" checks cannot be removed. By "redundant", I
+ mean "already explicitly checked" in the method code.
+
+ Unfortunately, analyzing complex expressions is not so easy
+ (see below for details), so the capabilities of this "abc
+ remover" are limited.
+
+ These are simple guidelines:
+
+ - Only expressions of the following kinds are handled:
+ - constant
+ - variable [+/- constant]
+ - Only comparisons between "handled" expressions are understood.
+ - "switch" statements are not yet handled.
+
+ This means that code like this will be handled well:
+
+ for (int i = 0; i < a.Length; i++) {
+ a [i] = .....
+ }
+
+ The "i" variable could be declared out of the "for", the "for"
+ could be a "while", and maybe even implemented with "goto",
+ the array could be scanned in reverse order, and everything
+ would still work.
+
+ I could have a temporary variable storing the array length,
+ and check on it inside the loop, and the abc removal would
+ still occurr, like this:
+
+ int i = 0;
+ int l = a.Length;
+ while ( i < l ) {
+ a [i] ......
+ }
+
+ or this:
+
+ int l = a.Length;
+ for (int i = l; i > 0; i--) {
+ a [i] = .....
+ }
+
+ The following two examples would work:
+
+ for (int i = 0; i < (a.Length -1); i++) .....
+ for (int i = 0; i < a.Length; i += 2) .....
+
+ But just something like this
+
+ int delta = -1;
+ for (int i = 0; i < (a.Length + delta); i++) .....
+
+ or like this
+
+ int delta = +2;
+ for (int i = 0; i < a.Length; i += delta) .....
+
+ would not work, the check would stay there. (unless
+ a combination of cfold, consprop and copyprop is used, too,
+ which would make the constant value of "delta" explicit).
+
+ Just to make you understand how things are tricky... this would work!
+
+ int limit = a.Length - 1;
+ for (int i = 0; i < limit; i++) {
+ a [i] = .....
+ }
+
+ A detailed explanation of the reason why things are done like
+ this is given below.
+
+* The Algorithm
+
+ This array bound check removal (abc removal) algorithm is
+ based on symbolic execution concepts. I handle the removal
+ like an "unreachable code elimination" (and in fact the
+ optimization could be extended to remove also other
+ unreachable sections of code, due to branches that "always go
+ the same way").
+
+ In symbolic execution, variables do not have actual (numeric)
+ values, but instead symbolic expressions (like "a", or "x+y").
+ Also, branch conditions are handled like symbolic conditions
+ (like "i<k"), which state relations between variable values.
+
+ The SSA representation inside mini is somewhat close to a
+ symbolic representation of the execution of the compiled
+ method.
+
+ Particularly, the representation of variable values is exactly
+ a symbolic one. It is enough to find all CEE_STIND_*
+ instructions which store to a local variable, and their second
+ argument is exactly the variable value. Actually, "cfg->vars
+ [<variable-index>]->def" should contain exactly those store
+ instructions, and the "get_variable_value_from_ssa_store"
+ function extracts the variable value from there.
+
+ On the other hand, the conditions under which each basic block
+ is executed are not made fully explicit.
+
+ However, it is not difficult to make them so.
+
+ Each BB that has more than one exit BB, in practice must end
+ either with a conditional branch instruction or with a switch
+ instruction.
+
+ In the first case, the BB has exactly two exit BBs, and their
+ execution conditions are easy to get from the condition of the
+ branch (see the "get_relation_from_branch_instruction"
+ function, and expecially the end of "analyze_block" in
+ abcremoval.c.
+
+ If there is a switch, the jump condition of every exit BB is
+ the equality of the switch argument with the particular index
+ associated with its case (but the current implementation does
+ not handle switch statements yet).
+
+ These individual conditions are in practice associated to each
+ arc that connects BBs in the CFG (with the simple case that
+ unconditional branches have a "TRUE" condition, because they
+ always happen).
+
+ So, for each BB, its *proper* entry condition is the union of
+ all the conditions associated to arcs that enter the BB. The
+ "union" is like a logical "or", in the sense that either of
+ the condition could be true, they are not necessarily all
+ true. This means that if I can enter a BB in two ways, and in
+ one case I know that "x>0", and in the other that "x==0",
+ actually in the BB I know that "x>=0", which is a weaker
+ condition (the union of the two).
+
+ Also, the *complete* entry condition for a BB is the
+ "intersection" of all the entry conditions of its
+ dominators. This is true because each dominator is the only
+ way to reach the BB, so the entry condition of each dominator
+ must be true if the control flow reached the BB. This
+ translates to the logical "and" of all the "proper" conditions
+ of the BBs met walking up in the dominator tree. So, if one
+ says "x>0", and another "x==1", then I know that "x==1", which
+ is a stronger condition (the intersection of the two).
+
+ Note that, if the two conditions were "x>0" and "x==0", then
+ the block would be unreachable (the intersection is empty),
+ because some branch is impossible.
+
+ Another observation is that, inside each BB, every variable is
+ subject to the complete entry condition of that very same BB,
+ and not the one in which it is defined (with the "complete
+ entry condition" being the thing I defined before, sorry if
+ these terms "proper" and "complete" are strange, I found
+ nothing better).
+
+ This happens because the branch conditions are related to the
+ control flow. I can define "i=a", and if I am in a BB where
+ "a>0", then "i>0", but not otherwise.
+
+ So, intuitively, if the available conditions say "i>=0", and i
+ is used as an index in an array access, then the lower bound
+ check can be omitted. If the condition also says
+ "(i>=0)&&(i<array.length)", the abc removal can occur.
+
+ So, a complete solution to the problem of abc removal would be
+ the following: for each array access, build a system of
+ equations containing:
+
+ [1] all the symbolic variable definitions
+
+ [2] the complete entry condition of the BB in which
+ the array access occurs
+
+ [3] the two "goal functions" ("index >=0" and "index <
+ array.length")
+
+ If the system is valid for *each possible* variable value, then the goal
+ functions are always true, and the abc can be removed.
+
+ All this discussion is useful to give a precise specification
+ to the problem we are trying to solve.
+
+ The trouble is that, in the general case, the resulting system
+ of equations is like a predicate in first order logic, which
+ is semi-decidable, and its general solution is anyway too
+ complex to be attempted in a JIT compiler (which should not
+ contain a full fledged theorem prover).
+
+ Therefore, we must cut some corner.
+
+ There is also another big problem, which is caused by
+ "recursive" symbolic definitions. These definition can (and
+ generally do) happen every time there is a loop. For instance,
+ in the following piece of code:
+
+ for ( int i = 0; i < array.length; i++ ) {
+ Console.WriteLine( "array [i] = " + array [i] );
+ }
+
+ one of the definitions of i is a PHI that can be either 0 or
+ "i + 1".
+
+ Now, we know that mathematically "i = i + 1" does not make
+ sense, and in fact symbolic values are not "equations", they
+ are "symbolic definitions".
+
+ The actual symbolic value of i is a generic "n", where "n" is
+ the number of iterations of the loop, but this is terrible to
+ handle (and in more complex examples the symbolic value of i
+ simply cannot be written, because i is calculated in an
+ iterative way).
+
+ However, the definition "i = i + 1" tells us something about
+ i: it tells us that i "grows". So (from the PHI definition) we
+ know that i is either 0, or "grows". This is enough to tell
+ that "i>=0", which is what we want! It is important to note
+ that recursive definitions can only occurr inside PHI
+ definitions, because actually a variable cannot be defined
+ *only* in terms of itself!
+
+ At this point, I can explain which corners I want to cut to
+ make the problem solvable. It will not remove all the abc that
+ could theoretically be removed, but at least it will work.
+
+ The easiest way to cut corners is to only handle expressions
+ which are "reasonably simple", and ignore the rest.
+
+ Keep in mind that ignoring an expression is not harmful in
+ itself. The algorithm will be simply "less powerful", because
+ it will ignore conditions that could have caused to the
+ removal of an abc, but will not remove checks "by mistake" (so
+ the resulting code will be in any case correct).
+
+ The expressions we handle are the following (all of integer
+ type):
+
+ - constant
+ - variable
+ - variable + constant
+ - constant + variable
+ - variable - constant
+
+ And, of course, PHI definitions.
+
+ Any other expression causes the introduction of an "any" value
+ in the evaluation, which makes all values that depend from it
+ unknown as well.
+
+ We will call these kind of definitions "summarizable"
+ definitions.
+
+ In a first attempt, we can consider only branch conditions
+ that have the simplest possible form (the comparison of two
+ summarizable expressions).
+
+ We can also simplify the effect of variable definitions,
+ keeping only what is relevant to know: their value range with
+ respect to zero and with respect to the length of the array we
+ are currently handling.
+
+ One particular note on PHI functions: they work (obviously)
+ like the logical "or" of their definitions, and therefore are
+ equivalent to the "logical or" of the summarization of their
+ definitions.
+
+ About recursive definitions (which, believe me, are the worst
+ thing in all this mess), we handle only "monotonic" ones. That
+ is, we try to understand if the recursive definition (which,
+ as we said above, must happen because of a loop) always
+ "grows" or "gets smaller". In all other cases, we decide we
+ cannot handle it.
+
+ One critical thing, once we have defined all these data
+ structures, is how the evaluation is actually performed.
+
+ In a first attempt I coded a "brute force" approach, which for
+ each BB tried to examine all possible conditions between all
+ variables, filling a sort of "evaluation matrix". The problem
+ was that the complexity of this evaluation was quadratic (or
+ worse) on the number of variables, and that many wariables
+ were examined even if they were not involved in any array
+ access.
+
+ Following the ABCD paper:
+
+ http://citeseer.ist.psu.edu/bodik00abcd.html
+
+ I rewrote the algorithm in a more "sparse" way.
+
+ Now, the main data structure is a graph of relations between
+ variables, and each attempt to remove a check performs a
+ traversal of the graph, looking for a path from the index to
+ the array length that satisfies the properties "index >= 0"
+ and "index < length". If such a path is found, the check is
+ removed. It is true that in theory *each* traversal has a
+ complexity which is exponential on the number of variables,
+ but in practice the graph is not very connected, so the
+ traversal terminates quickly.
+
+
+ Then, the algorithm to optimize one method looks like this:
+
+ [1] Preparation:
+
+ [1a] Build the SSA representation.
+
+ [1b] Prepare the evaluation graph (empty)
+
+ [1b] Summarize each varible definition, and put
+ the resulting relations in the evaluation
+ graph
+
+ [2] Analyze each BB, starting from the entry point and
+ following the dominator tree:
+
+ [2a] Summarize its entry condition, and put the resulting relations
+ in the evaluation graph (this is the reason
+ why the BBs are examined following the
+ dominator tree, so that conditions are added
+ to the graph in a "cumulative" way)
+
+ [2b] Scan the BB instructions, and for each array
+ access perform step [3]
+
+ [2c] Process children BBs following the dominator
+ tree (step [2])
+
+ [2d] Remove from the evaluation area the conditions added at step [2a]
+ (so that backtracking along the tree the area
+ is properly cleared)
+
+ [3] Attempt the removal:
+
+ [3a] Summarize the index expression, to see if we can handle it; there
+ are three cases: the index is either a
+ constant, or a variable (with an optional
+ delta) or cannot be handled (is a "any")
+
+ [3b] If the index can be handled, traverse the evaluation area searching
+ a path from the index variable to the array
+ length (if the index is a constant, just
+ examine the array length to see if it has
+ some relation with this constant)
+
+ [3c] Use the results of step [3b] to decide if the check is redundant
+
77 docs/aot-compiler.txt
View
@@ -192,40 +192,55 @@ Mono Ahead Of Time Compiler
* Performance considerations
----------------------------
-Using AOT code is a trade-off which might lead to higher or slower performance,
-depending on a lot of circumstances. Some of these are:
-
-- AOT code needs to be loaded from disk before being used, so cold startup of
- an application using AOT code MIGHT be slower than using JITed code. Warm
- startup (when the code is already in the machines cache) should be faster.
- Also, JITing code takes time, and the JIT compiler also need to load
- additional metadata for the method from the disk, so startup can be faster
- even in the cold startup case.
-- AOT code is usually compiled with all optimizations turned on, while JITted
- code is usually compiled with default optimizations, so the generated code
- in the AOT case should be faster.
-- JITted code can directly access runtime data structures and helper functions,
- while AOT code needs to go through an indirection (the GOT) to access them,
- so it will be slower and somewhat bigger as well.
-- When JITting code, the JIT compiler needs to load a lot of metadata about
- methods and types into memory.
-- JITted code has better locality, meaning that if A method calls B, then
- the native code for A and B is usually quite close in memory, leading to
- better cache behaviour thus improved performance. In contrast, the native
- code of methods inside the AOT file is in a somewhat random order.
-
+ Using AOT code is a trade-off which might lead to higher or
+ slower performance, depending on a lot of circumstances. Some
+ of these are:
+
+ - AOT code needs to be loaded from disk before being used, so
+ cold startup of an application using AOT code MIGHT be
+ slower than using JITed code. Warm startup (when the code is
+ already in the machines cache) should be faster. Also,
+ JITing code takes time, and the JIT compiler also need to
+ load additional metadata for the method from the disk, so
+ startup can be faster even in the cold startup case.
+
+ - AOT code is usually compiled with all optimizations turned
+ on, while JITted code is usually compiled with default
+ optimizations, so the generated code in the AOT case should
+ be faster.
+
+ - JITted code can directly access runtime data structures and
+ helper functions, while AOT code needs to go through an
+ indirection (the GOT) to access them, so it will be slower
+ and somewhat bigger as well.
+
+ - When JITting code, the JIT compiler needs to load a lot of
+ metadata about methods and types into memory.
+
+ - JITted code has better locality, meaning that if A method
+ calls B, then the native code for A and B is usually quite
+ close in memory, leading to better cache behaviour thus
+ improved performance. In contrast, the native code of
+ methods inside the AOT file is in a somewhat random order.
+
* Future Work
-------------
-- Currently, when an AOT module is loaded, all of its dependent assemblies are also
- loaded eagerly, and these assemblies need to be exactly the same as the ones loaded
- when the AOT module was created ('hard binding'). Non-hard binding should be allowed.
-- On x86, the generated code uses call 0, pop REG, add GOTOFFSET, REG to
- materialize the GOT address. Newer versions of gcc use a separate function
- to do this, maybe we need to do the same.
-- Currently, we get vtable addresses from the GOT. Another solution would be
- to store the data from the vtables in the .bss section, so accessing them
- would involve less indirection.
+ - Currently, when an AOT module is loaded, all of its
+ dependent assemblies are also loaded eagerly, and these
+ assemblies need to be exactly the same as the ones loaded
+ when the AOT module was created ('hard binding'). Non-hard
+ binding should be allowed.
+ - On x86, the generated code uses call 0, pop REG, add
+ GOTOFFSET, REG to materialize the GOT address. Newer
+ versions of gcc use a separate function to do this, maybe we
+ need to do the same.
+
+ - Currently, we get vtable addresses from the GOT. Another
+ solution would be to store the data from the vtables in the
+ .bss section, so accessing them would involve less
+ indirection.
+
547 docs/exception-handling.txt
View
@@ -2,252 +2,329 @@
Exception Handling In the Mono Runtime
--------------------------------------
-Introduction
-------------
-
- There are many types of exceptions which the runtime needs to handle. These
-are:
-- exceptions thrown from managed code using the 'throw' or 'rethrow' CIL
- instructions.
-- exceptions thrown by some IL instructions like InvalidCastException thrown
- by the 'castclass' CIL instruction.
-- exceptions thrown by runtime code
-- synchronous signals received while in managed code
-- synchronous signals received while in native code
-- asynchronous signals
-
-Since exception handling is very arch dependent, parts of the exception
-handling code reside in the arch specific exceptions-<ARCH>.c files. The
-architecture independent parts are in mini-exceptions.c. The different
-exception types listed above are generated in different parts of the runtime,
-but ultimately, they all end up in the mono_handle_exception () function in
-mini-exceptions.c.
-
-Exceptions throw programmatically from managed code
----------------------------------------------------
-
-These exceptions are thrown from managed code using 'throw' or 'rethrow' CIL
-instructions. The JIT compiler will translate them to a call to a helper
-function called 'mono_arch_throw/rethrow_exception'. These helper functions do
-not exist at compile time, they are created dynamically at run time by the
-code in the exceptions-<ARCH>.c files. They perform various stack
-manipulation magic, then call a helper function usually named throw_exception (), which
-does further processing in C code, then calls mono_handle_exception () to do the rest.
-
-Exceptions thrown implicitly from managed code
-----------------------------------------------
-
-These exceptions are thrown by some IL instructions when something goes wrong.
-When the JIT needs to throw such an exception, it emits a forward conditional
-branch and remembers its position, along with the exception which needs to
-be emitted. This is usually done in macros named EMIT_COND_SYSTEM_EXCEPTION in
-the mini-<ARCH>.c files. After the machine code for the method is emitted, the
-JIT calls the arch dependent mono_arch_emit_exceptions () function which will
-add the exception throwing code to the end of the method, and patches up the
-previous forward branches so they will point to this code. This has the
-advantage that the rarely-executed exception throwing code is kept separate
-from the method body, leading to better icache performance.
-The exception throwing code braches to the dynamically generated
-mono_arch_throw_corlib_exception helper function, which will create the
-proper exception object, does some stack manipulation, then calls
-throw_exception ().
-
-Exceptions thrown by runtime code
----------------------------------
-
-These exceptions are usually thrown by the implementations of InternalCalls
-(icalls). First an appropriate exception object is created with the help of
-various helper functions in metadata/exception.c, which has a separate helper
-function for allocating each kind of exception object used by the runtime code.
-Then the mono_raise_exception () function is called to actually throw the
-exception. That function never returns.
-
-An example:
- if (something_is_wrong)
- mono_raise_exception (mono_get_exception_index_out_of_range ());
-
-mono_raise_exception () simply passes the exception to the JIT side through
-an API, where it will be received by helper created by mono_arch_throw_exception (). From now on, it is treated as an exception thrown from managed code.
-
-Synchronous signals
--------------------
-
-For performance reasons, the runtime does not do same checks required by the
-CLI spec. Instead, it relies on the CPU to do them. The two main checks which
-are omitted are null-pointer checks, and arithmetic checks. When a null
-pointer is dereferenced by JITted code, the CPU will notify the kernel through
-an interrupt, and the kernel will send a SIGSEGV signal to the process. The
-runtime installs a signal handler for SIGSEGV, which is
-sigsegv_signal_handler () in mini.c. The signal handler creates the appropriate
-exception object and calls mono_handle_exception () with it. Arithmetic
-exceptions like division by zero are handled similarly.
-
-Synchronous signals in native code
-----------------------------------
-
-Receiving a signal such as SIGSEGV while in native code means something very
-bad has happened. Because of this, the runtime will abort after trying to print a
-managed plus a native stack trace. The logic is in the mono_handle_native_sigsegv ()
-function.
-Note that there are two kinds of native code which can be the source of the signal:
-- code inside the runtime
-- code inside a native library loaded by an application, ie. libgtk+
-
-Stack overflow checking
------------------------
-
- Stack overflow exceptions need special handling. When a thread overflows its
-stack, the kernel sends it a normal SIGSEGV signal, but the signal handler
-tries to execute on the same as the thread leading to a further SIGSEGV which
-will terminate the thread. A solution is to use an alternative signal stack
-supported by UNIX operating systems through the sigaltstack (2) system call.
-When a thread starts up, the runtime will install an altstack using the
-mono_setup_altstack () function in mini-exceptions.c. When a SIGSEGV is
-received, the signal handler checks whenever the fault address is near the
-bottom of the threads normal stack. If it is, a StackOverflowException is
-created instead of a NullPointerException. This exception is handled like
-any other exception, with some minor differences.
- Working sigaltstack support is very much os/kernel/libc dependent, so it is
-disabled by default.
-
-Asynchronous signals
---------------------
-
- Async signals are used by the runtime to notify a thread that it needs to
-change its state somehow. Currently, it is used for implementing
-thread abort/suspend/resume.
-
- Handling async signals correctly is a very hard problem, since the receiving
-thread can be in basically any state upon receipt of the signal. It can
-execute managed code, native code, it can hold various managed/native locks, or
-it can be in a process of acquiring them, it can be starting up, shutting down
-etc. Most of the C APIs used by the runtime are not asynch-signal safe,
-meaning it is not safe to call them from an async signal handler. In
-particular, the pthread locking functions are not async-safe, so if a
-signal handler interrupted code which was in the process of acquiring a lock,
-and the signal handler tries to acquire a lock, the thread will deadlock.
-Unfortunately, the current signal handling code does acquire locks, so
-sometimes it does deadlock.
-
-When receiving an async signal, the signal handler first tries to determine
-whenever the thread was executing managed code when it was interrupted. If
-it did, then it is safe to interrupt it, so a ThreadAbortException is
-constructed and thrown. If the thread was executing native code, then it is
-generally not safe to interrupt it. In this case, the runtime sets a flag
-then returns from the signal handler. That flag is checked every time the
-runtime returns from native code to managed code, and the exception is thrown
-then. Also, a platform specific mechanism is used to cause the thread to
-interrupt any blocking operation it might be doing.
-
-The async signal handler is in sigusr1_signal_handler () in mini.c, while
-the logic which determines whenever an exception is safe to be thrown is in
-mono_thread_request_interruption ().
-
-Stack unwinding during exception handling
------------------------------------------
-
-The execution state of a thread during exception handling is stored in an
-arch-specific structure called MonoContext. This structure contains the values
-of all the CPU registers relevant during exception handling, which
-usually means:
-- IP (instruction pointer)
-- SP (stack pointer)
-- FP (frame pointer)
-- callee saved registers
-
-Callee saved registers are the registers which are required by any procedure
-to be saved/restored before/after using them. They are usually defined by
-each platforms ABI (Application Binary Interface). For example, on x86, they
-are EBX, ESI and EDI.
-
-The code which calls mono_handle_exception () is required to construct the
-initial MonoContext. How this is done depends on the caller. For exceptions
-thrown from managed code, the mono_arch_throw_exception helper function
-saves the values of the required registers and passes them to throw_exception (), which will save them in the MonoContext structure. For exceptions thrown from
-signal handlers, the MonoContext stucture is initialized from the signal info
-received from the kernel.
-
-During exception handling, the runtime needs to 'unwind' the stack, i.e.
-given the state of the thread at a stack frame, construct the state at its
-callers. Since this is platform specific, it is done by a platform specific
-function called mono_arch_find_jit_info ().
-
-Two kinds of stack frames need handling:
-- Managed frames are easier. The JIT will store some information about each
- managed method, like which callee-saved registers it uses. Based on this
- information, mono_arch_find_jit_info () can find the values of the registers
- on the thread stack, and restore them.
-- Native frames are problematic, since we have no information about how to
- unwind through them. Some compilers generate unwind information for code,
- some don't. Also, there is no general purpose library to obtain and decode
- this unwind information. So the runtime uses a different solution. When
- managed code needs to call into native code, it does through a
- managed->native wrapper function, which is generated by the JIT. This
- function is responsible for saving the machine state into a per-thread
- structure called MonoLMF (Last Managed Frame). These LMF structures are
- stored on the threads stack, and are linked together using one of their
- fields. When the unwinder encounters a native frame, it simply pops
- one entry of the LMF 'stack', and uses it to restore the frame state to the
- moment before control passed to native code. In effect, all successive native
- frames are skipped together.
+* Introduction
+--------------
+
+ There are many types of exceptions which the runtime needs to
+ handle. These are:
+
+ - exceptions thrown from managed code using the 'throw' or 'rethrow' CIL
+ instructions.
+
+ - exceptions thrown by some IL instructions like InvalidCastException thrown
+ by the 'castclass' CIL instruction.
+
+ - exceptions thrown by runtime code
+
+ - synchronous signals received while in managed code
+
+ - synchronous signals received while in native code
+
+ - asynchronous signals
+
+ Since exception handling is very arch dependent, parts of the
+ exception handling code reside in the arch specific
+ exceptions-<ARCH>.c files. The architecture independent parts
+ are in mini-exceptions.c. The different exception types listed
+ above are generated in different parts of the runtime, but
+ ultimately, they all end up in the mono_handle_exception ()
+ function in mini-exceptions.c.
+
+* Exceptions throw programmatically from managed code
+-----------------------------------------------------
+
+ These exceptions are thrown from managed code using 'throw' or
+ 'rethrow' CIL instructions. The JIT compiler will translate
+ them to a call to a helper function called
+ 'mono_arch_throw/rethrow_exception'.
+
+ These helper functions do not exist at compile time, they are
+ created dynamically at run time by the code in the
+ exceptions-<ARCH>.c files.
+
+ They perform various stack manipulation magic, then call a
+ helper function usually named throw_exception (), which does
+ further processing in C code, then calls
+ mono_handle_exception() to do the rest.
+
+* Exceptions thrown implicitly from managed code
+------------------------------------------------
+
+ These exceptions are thrown by some IL instructions when
+ something goes wrong. When the JIT needs to throw such an
+ exception, it emits a forward conditional branch and remembers
+ its position, along with the exception which needs to be
+ emitted. This is usually done in macros named
+ EMIT_COND_SYSTEM_EXCEPTION in the mini-<ARCH>.c files.
+
+ After the machine code for the method is emitted, the JIT
+ calls the arch dependent mono_arch_emit_exceptions () function
+ which will add the exception throwing code to the end of the
+ method, and patches up the previous forward branches so they
+ will point to this code.
+
+ This has the advantage that the rarely-executed exception
+ throwing code is kept separate from the method body, leading
+ to better icache performance.
+
+ The exception throwing code braches to the dynamically
+ generated mono_arch_throw_corlib_exception helper function,
+ which will create the proper exception object, does some stack
+ manipulation, then calls throw_exception ().
+
+* Exceptions thrown by runtime code
+-----------------------------------
+
+ These exceptions are usually thrown by the implementations of
+ InternalCalls (icalls). First an appropriate exception object
+ is created with the help of various helper functions in
+ metadata/exception.c, which has a separate helper function for
+ allocating each kind of exception object used by the runtime
+ code. Then the mono_raise_exception () function is called to
+ actually throw the exception. That function never returns.
+
+ An example:
+
+ if (something_is_wrong)
+ mono_raise_exception (mono_get_exception_index_out_of_range ());
+
+ mono_raise_exception () simply passes the exception to the JIT
+ side through an API, where it will be received by helper
+ created by mono_arch_throw_exception (). From now on, it is
+ treated as an exception thrown from managed code.
+
+* Synchronous signals
+---------------------
+
+ For performance reasons, the runtime does not do same checks
+ required by the CLI spec. Instead, it relies on the CPU to do
+ them. The two main checks which are omitted are null-pointer
+ checks, and arithmetic checks. When a null pointer is
+ dereferenced by JITted code, the CPU will notify the kernel
+ through an interrupt, and the kernel will send a SIGSEGV
+ signal to the process. The runtime installs a signal handler
+ for SIGSEGV, which is sigsegv_signal_handler () in mini.c. The
+ signal handler creates the appropriate exception object and
+ calls mono_handle_exception () with it. Arithmetic exceptions
+ like division by zero are handled similarly.
+
+* Synchronous signals in native code
+------------------------------------
+
+ Receiving a signal such as SIGSEGV while in native code means
+ something very bad has happened. Because of this, the runtime
+ will abort after trying to print a managed plus a native stack
+ trace. The logic is in the mono_handle_native_sigsegv ()
+ function.
+
+ Note that there are two kinds of native code which can be the
+ source of the signal:
+
+ - code inside the runtime
+ - code inside a native library loaded by an application, ie. libgtk+
+
+* Stack overflow checking
+-------------------------
+
+ Stack overflow exceptions need special handling. When a thread
+ overflows its stack, the kernel sends it a normal SIGSEGV
+ signal, but the signal handler tries to execute on the same as
+ the thread leading to a further SIGSEGV which will terminate
+ the thread. A solution is to use an alternative signal stack
+ supported by UNIX operating systems through the sigaltstack
+ (2) system call. When a thread starts up, the runtime will
+ install an altstack using the mono_setup_altstack () function
+ in mini-exceptions.c. When a SIGSEGV is received, the signal
+ handler checks whenever the fault address is near the bottom
+ of the threads normal stack. If it is, a
+ StackOverflowException is created instead of a
+ NullPointerException. This exception is handled like any other
+ exception, with some minor differences.
+
+ There are two reasons why sigaltstack is disabled by default:
+
+ * The main problem with sigaltstack() is that the stack
+ employed by it is not visible to the GC and it is possible
+ that the GC will miss it.
+
+ * Working sigaltstack support is very much os/kernel/libc
+ dependent, so it is disabled by default.
+
+
+* Asynchronous signals
+----------------------
+ Async signals are used by the runtime to notify a thread that
+ it needs to change its state somehow. Currently, it is used
+ for implementing thread abort/suspend/resume.
+
+ Handling async signals correctly is a very hard problem,
+ since the receiving thread can be in basically any state upon
+ receipt of the signal. It can execute managed code, native
+ code, it can hold various managed/native locks, or it can be
+ in a process of acquiring them, it can be starting up,
+ shutting down etc. Most of the C APIs used by the runtime are
+ not asynch-signal safe, meaning it is not safe to call them
+ from an async signal handler. In particular, the pthread
+ locking functions are not async-safe, so if a signal handler
+ interrupted code which was in the process of acquiring a lock,
+ and the signal handler tries to acquire a lock, the thread
+ will deadlock. Unfortunately, the current signal handling
+ code does acquire locks, so sometimes it does deadlock.
+
+ When receiving an async signal, the signal handler first tries
+ to determine whenever the thread was executing managed code
+ when it was interrupted. If it did, then it is safe to
+ interrupt it, so a ThreadAbortException is constructed and
+ thrown. If the thread was executing native code, then it is
+ generally not safe to interrupt it. In this case, the runtime
+ sets a flag then returns from the signal handler. That flag is
+ checked every time the runtime returns from native code to
+ managed code, and the exception is thrown then. Also, a
+ platform specific mechanism is used to cause the thread to
+ interrupt any blocking operation it might be doing.
+
+ The async signal handler is in sigusr1_signal_handler () in
+ mini.c, while the logic which determines whenever an exception
+ is safe to be thrown is in mono_thread_request_interruption
+ ().
+
+* Stack unwinding during exception handling
+-------------------------------------------
+
+ The execution state of a thread during exception handling is
+ stored in an arch-specific structure called MonoContext. This
+ structure contains the values of all the CPU registers
+ relevant during exception handling, which usually means:
+
+ - IP (instruction pointer)
+ - SP (stack pointer)
+ - FP (frame pointer)
+ - callee saved registers
+
+ Callee saved registers are the registers which are required by
+ any procedure to be saved/restored before/after using
+ them. They are usually defined by each platforms ABI
+ (Application Binary Interface). For example, on x86, they are
+ EBX, ESI and EDI.
+
+ The code which calls mono_handle_exception () is required to
+ construct the initial MonoContext. How this is done depends on
+ the caller. For exceptions thrown from managed code, the
+ mono_arch_throw_exception helper function saves the values of
+ the required registers and passes them to throw_exception (),
+ which will save them in the MonoContext structure. For
+ exceptions thrown from signal handlers, the MonoContext
+ stucture is initialized from the signal info received from the
+ kernel.
+
+ During exception handling, the runtime needs to 'unwind' the
+ stack, i.e. given the state of the thread at a stack frame,
+ construct the state at its callers. Since this is platform
+ specific, it is done by a platform specific function called
+ mono_arch_find_jit_info ().
+
+ Two kinds of stack frames need handling:
+
+ - Managed frames are easier. The JIT will store some
+ information about each managed method, like which
+ callee-saved registers it uses. Based on this information,
+ mono_arch_find_jit_info () can find the values of the
+ registers on the thread stack, and restore them.
+
+ - Native frames are problematic, since we have no information
+ about how to unwind through them. Some compilers generate
+ unwind information for code, some don't. Also, there is no
+ general purpose library to obtain and decode this unwind
+ information. So the runtime uses a different solution. When
+ managed code needs to call into native code, it does through
+ a managed->native wrapper function, which is generated by
+ the JIT. This function is responsible for saving the machine
+ state into a per-thread structure called MonoLMF (Last
+ Managed Frame). These LMF structures are stored on the
+ threads stack, and are linked together using one of their
+ fields. When the unwinder encounters a native frame, it
+ simply pops one entry of the LMF 'stack', and uses it to
+ restore the frame state to the moment before control passed
+ to native code. In effect, all successive native frames are
+ skipped together.
+
Problems/future work
--------------------
1. Async signal safety
----------------------
-The current async signal handling code is not async safe, so it can and does
-deadlock in practice. It needs to be rewritten to avoid taking locks at least
-until it can determine that it was interrupting managed code.
-
-Another problem is the managed stack frame unwinding code. It blindly assumes
-that if the IP points into a managed frame, then all the callee saved
-registers + the stack pointer are saved on the stack. This is not true if
-the thread was interrupted while executing the method prolog/epilog.
-
+ The current async signal handling code is not async safe, so
+ it can and does deadlock in practice. It needs to be rewritten
+ to avoid taking locks at least until it can determine that it
+ was interrupting managed code.
+
+ Another problem is the managed stack frame unwinding code. It
+ blindly assumes that if the IP points into a managed frame,
+ then all the callee saved registers + the stack pointer are
+ saved on the stack. This is not true if the thread was
+ interrupted while executing the method prolog/epilog.
+
2. Raising exceptions from native code
--------------------------------------
-Currently, exceptions are raised by calling mono_raise_exception () in
-the middle of runtime code. This has two problems:
-- No cleanup is done, ie. if the caller of the function which throws an
- exception has taken locks, or allocated memory, that is not cleaned up. For
- this reason, it is only safe to call mono_raise_exception () 'very close' to
- managed code, ie. in the icall functions themselves.
-- To allow mono_raise_exception () to unwind through native code, we need to
- save the LMF structures which can add a lot of overhead even in the common
- case when no exception is thrown. So this is not zero-cost exception handling.
-
- An alternative might be to use a JNI style set-pending-exception API.
-Runtime code could call mono_set_pending_exception (), then return to its
-caller with an error indication allowing the caller to clean up. When execution
-returns to managed code, then managed->native wrapper could check whenever
-there is a pending exception and throw it if neccesary. Since we already check
-for pending thread interruption, this would have no overhead, allowing us
-to drop the LMF saving/restoring code, or significant parts of it.
-
+ Currently, exceptions are raised by calling
+ mono_raise_exception () in the middle of runtime code. This
+ has two problems:
+
+ - No cleanup is done, ie. if the caller of the function which
+ throws an exception has taken locks, or allocated memory,
+ that is not cleaned up. For this reason, it is only safe to
+ call mono_raise_exception () 'very close' to managed code,
+ ie. in the icall functions themselves.
+
+ - To allow mono_raise_exception () to unwind through native
+ code, we need to save the LMF structures which can add a lot
+ of overhead even in the common case when no exception is
+ thrown. So this is not zero-cost exception handling.
+
+ An alternative might be to use a JNI style
+ set-pending-exception API. Runtime code could call
+ mono_set_pending_exception (), then return to its caller with
+ an error indication allowing the caller to clean up. When
+ execution returns to managed code, then managed->native
+ wrapper could check whenever there is a pending exception and
+ throw it if neccesary. Since we already check for pending
+ thread interruption, this would have no overhead, allowing us
+ to drop the LMF saving/restoring code, or significant parts of
+ it.
+
4. libunwind
------------
-There is an OSS project called libunwind which is a standalone stack unwinding
-library. It is currently in development, but it is used by default by gcc on
-ia64 for its stack unwinding. The mono runtime also uses it on ia64. It has
-several advantages in relation to our current unwinding code:
-- it has a platform independent API, i.e. the same unwinding code can be used
- on multiple platforms.
-- it can generate unwind tables which are correct at every instruction, i.e.
- can be used for unwinding from async signals.
-- given sufficient unwind info generated by a C compiler, it can unwind through
- C code.
-- most of its API is async-safe
-- it implements the gcc C++ exception handling API, so in theory it can
- be used to implement mixed-language exception handling (i.e. C++ exception
- caught in mono, mono exception caught in C++).
-- it is MIT licensed
-
-The biggest problem with libuwind is its platform support. ia64 support is
-complete/well tested, while support for other platforms is missing/incomplete.
-
-http://www.hpl.hp.com/research/linux/libunwind/
+ There is an OSS project called libunwind which is a standalone
+ stack unwinding library. It is currently in development, but
+ it is used by default by gcc on ia64 for its stack
+ unwinding. The mono runtime also uses it on ia64. It has
+ several advantages in relation to our current unwinding code:
+
+ - it has a platform independent API, i.e. the same unwinding
+ code can be used on multiple platforms.
+
+ - it can generate unwind tables which are correct at every
+ instruction, i.e. can be used for unwinding from async
+ signals.
+
+ - given sufficient unwind info generated by a C compiler, it
+ can unwind through C code.
+
+ - most of its API is async-safe
+
+ - it implements the gcc C++ exception handling API, so in
+ theory it can be used to implement mixed-language exception
+ handling (i.e. C++ exception caught in mono, mono exception
+ caught in C++).
+ - it is MIT licensed
+
+ The biggest problem with libuwind is its platform support. ia64 support is
+ complete/well tested, while support for other platforms is missing/incomplete.
+
+ http://www.hpl.hp.com/research/linux/libunwind/
+
Please sign in to comment.
Something went wrong with that request. Please try again.