work vacancies trabajos trabajo stackoverflow español bolsa badges ask .net compiler-construction f# stack-overflow tail-recursion

.net - vacancies - trabajos stackoverflow



Prevención de StackOverflow en intérpretes de idiomas (1)

F # como lenguaje es ideal para escribir intérpretes o compiladores de lenguaje, sin embargo, una cosa sigue pegándonos donde no lo queremos: la excepción StackOverflowException.

Es bien sabido que una excepción SO no se puede capturar y no se puede recuperar. Una técnica obvia para evitar tal excepción es contar la profundidad de la pila a medida que avanza. Gastos generales, sí, pero factibles y quizás no necesarios en todas las funciones.

Sin embargo, con F #, esta técnica no trae mucho beneficio. Hacemos mucho uso de las técnicas de optimización de llamadas de cola en las expresiones generadas sobre la marcha del intérprete. Los problemas que enfrentamos con SO-excepciones son:

  • ¿Cómo podemos informar al usuario de ellos, en lugar de bloquear todo el dominio de aplicación actual?
  • Si vamos a contar la profundidad de la pila, ¿cómo sabemos si una función tiene TCO ''o está en línea para que no tengamos que contar?
  • Si optamos por otro enfoque (como inspeccionar la pila a intervalos de profundidad dados), ¿hay alguna forma (conocida) de hacerlo sin obstaculizar seriamente el rendimiento?

No basta con aumentar el tamaño de la pila. Queremos dar al usuario un error de inicio de sesión, preferiblemente capturable por la aplicación que realiza la llamada. Para eso necesitamos poder tirar la excepción a mano, lo que la hace capturable. ¿Pero cómo determinamos el momento adecuado?

Actualización :
Hans Passant sugiere correctamente la previsibilidad aquí. Sin embargo, los programadores que usan este DSL esperan que (ciertas) llamadas reciban TCO, por lo que no quieren un límite de pila fuerte. Ellos saben lo que están haciendo. Aún así, sus programas deben poder morir con gracia, al menos en la medida en que no se dañe ninguna aplicación de llamada (es decir, un programa C # que utiliza nuestras bibliotecas).


No estoy familiarizado con F #, pero sí escribí un intérprete ECMAScript-262 v.5 en C # para poder relacionarme con algunos de sus problemas. Como saben, la excepción Exception no puede ser detectada en aplicaciones .NET desde v2.0. Sin embargo, hay una solución bastante confiable y es rápida.

Si declara una variable en la pila, por ejemplo un int, la dirección de esa variable representa la parte superior de la pila y le permite saber cuánto espacio queda. Por lo tanto, si registra esa variable al inicio mientras la pila está básicamente vacía, puede hacer referencia a ella cada vez que ingrese un nuevo contexto de ejecución.

Aquí hay algunos extractos de mi intérprete que tratan este problema.

DO#:

Estas son variables estáticas declaradas en la clase principal de intérprete.

private static int TopOfStack; private const int STACK_SIZE = 1000000;

Este es el constructor estático de la clase principal de intérprete.

static Interpreter() { InitializeGlobalEnvironment(); //--------------------------------------------------- // Get the address of a new variable allocated on the stack // to represent the amount of memory available. Record // the address. //--------------------------------------------------- int stackVariable; TopOfStack = (int)&stackVariable; }

Este código se llama antes de que se interprete una función ECMAScript. Si la dirección de una nueva variable asignada de pila es menos corta. Máx., Lanzo la excepción detectable. Debe dejar algo de espacio para que se desenrolle la pila de llamadas.

internal static ExecutionContext EnterFunctionContext(IValue thisArg, LIST args, FUNCTION function) { ... LexicalEnvironment localEnv = ECMA.NewDeclarativeEnvironment(function.Scope); ExecutionContext context = new ExecutionContext() { Strict = function.IsStrict, VariableEnvironment = localEnv, LexicalEnvironment = localEnv }; int remainingStackSpace; if (STACK_SIZE - (TopOfStack - (int)&remainingStackSpace) < short.MaxValue) throw new ECMARuntimeException("", RuntimeErrorType.RangeError); CallStack.Push(context); LexicalEnvironment env = CurrentContext.VariableEnvironment; ... }

Cuando se interpreta el siguiente código, la excepción se produce alrededor de la iteración 1200.

Actualización: En la versión de lanzamiento es alrededor de 4100 iteraciones.

ECMAScript:

RecursiveCall(0); function RecursiveCall(counter){ return RecursiveCall(++counter); }

Salida: RangeError:

Puede aumentar el tamaño de pila en el subproceso utilizando el constructor de Thread(ParameterizedThreadStart, Int32) . Simplemente no sentí la necesidad.

Buena suerte con tu proyecto. Espero que esto ayude.