Beispiel #1
0
 private bool TryInvokeNamedFunction(
     ModuleInstance instance,
     string name,
     IEnumerable <SExpression> argumentExpressions,
     SExpression expression,
     out IReadOnlyList <object> results,
     bool reportExceptions = true)
 {
     if (instance.ExportedFunctions.TryGetValue(name, out FunctionDefinition def))
     {
         var args = argumentExpressions
                    .Zip(def.ParameterTypes, (expr, type) => EvaluateConstExpr(expr, type))
                    .ToArray();
         try
         {
             results = def.Invoke(args);
             return(true);
         }
         catch (Exception ex)
         {
             if (reportExceptions)
             {
                 Log.Log(
                     new LogEntry(
                         Severity.Error,
                         "unhandled exception",
                         $"function invocation threw {ex.GetType().Name}",
                         new Paragraph(ex.ToString()),
                         Assembler.Highlight(expression)));
             }
             throw;
         }
     }
     else
     {
         results = null;
         return(false);
     }
 }
Beispiel #2
0
        /// <summary>
        /// Parses a sequence of tokens as S-expressions.
        /// </summary>
        /// <param name="tokens">The tokens to parse.</param>
        /// <param name="log">A log to send errors to.</param>
        /// <param name="isNested">Tells if this parsing action is a nested rather than a top-level action.</param>
        /// <returns>A list of parsed S-expressions.</returns>
        private static IReadOnlyList <SExpression> ParseAsSExpressions(IEnumerator <Lexer.Token> tokens, ILog log, bool isNested)
        {
            var results = new List <SExpression>();

            while (tokens.MoveNext())
            {
                var token = tokens.Current;
                if (token.Kind == Lexer.TokenKind.LeftParenthesis)
                {
                    if (tokens.MoveNext())
                    {
                        var head = tokens.Current;
                        if (head.Kind != Lexer.TokenKind.Keyword)
                        {
                            log.Log(
                                new LogEntry(
                                    Severity.Error,
                                    "expected a keyword",
                                    "all S-expressions should begin with a keyword, but this one doesn't.",
                                    new HighlightedSource(new SourceRegion(head.Span))));
                        }
                        var tail = ParseAsSExpressions(tokens, log, true);
                        if (tokens.Current.Kind != Lexer.TokenKind.RightParenthesis)
                        {
                            log.Log(
                                new LogEntry(
                                    Severity.Error,
                                    "no closing parenthesis",
                                    "left parenthesis indicates the start of an S-expression, but that expression is never closed.",
                                    new HighlightedSource(new SourceRegion(token.Span))));
                        }
                        results.Add(SExpression.Create(head, tail));
                    }
                    else
                    {
                        log.Log(
                            new LogEntry(
                                Severity.Error,
                                "no closing parenthesis",
                                "left parenthesis indicates the start of an S-expression, but the file ends immediately after.",
                                new HighlightedSource(new SourceRegion(token.Span))));
                    }
                }
                else if (token.Kind == Lexer.TokenKind.RightParenthesis)
                {
                    if (!isNested)
                    {
                        log.Log(
                            new LogEntry(
                                Severity.Error,
                                "excess parenthesis",
                                "right parenthesis does not close a left parenthesis.",
                                new HighlightedSource(new SourceRegion(token.Span))));
                    }
                    break;
                }
                else
                {
                    results.Add(SExpression.Create(token));
                }
            }
            return(results);
        }
Beispiel #3
0
 private object EvaluateConstExpr(SExpression expression, Type resultType)
 {
     return(EvaluateConstExpr(expression, ValueHelpers.ToWasmValueType(resultType)));
 }
Beispiel #4
0
        private IReadOnlyList <object> RunAction(SExpression expression, bool reportExceptions = true)
        {
            if (expression.IsCallTo("invoke"))
            {
                var tail     = expression.Tail;
                var moduleId = Assembler.AssembleLabelOrNull(ref tail);
                var name     = Assembler.AssembleString(tail[0], Log);
                var args     = tail.Skip(1);

                if (moduleId == null)
                {
                    foreach (var inst in Enumerable.Reverse(moduleInstances))
                    {
                        if (TryInvokeNamedFunction(inst, name, args, expression, out IReadOnlyList <object> results, reportExceptions))
                        {
                            return(results);
                        }
                    }

                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "undefined function",
                            Quotation.QuoteEvenInBold(
                                "no function named ",
                                name,
                                " is defined here."),
                            Assembler.Highlight(expression)));
                    return(Array.Empty <object>());
                }
                else
                {
                    if (moduleInstancesByName.TryGetValue(moduleId, out ModuleInstance inst))
                    {
                        if (TryInvokeNamedFunction(inst, name, args, expression, out IReadOnlyList <object> results, reportExceptions))
                        {
                            return(results);
                        }
                        else
                        {
                            Log.Log(
                                new LogEntry(
                                    Severity.Error,
                                    "undefined function",
                                    Quotation.QuoteEvenInBold(
                                        "no function named ",
                                        name,
                                        " is defined in module ",
                                        moduleId,
                                        "."),
                                    Assembler.Highlight(expression)));
                            return(Array.Empty <object>());
                        }
                    }
                    else
                    {
                        Log.Log(
                            new LogEntry(
                                Severity.Error,
                                "undefined module",
                                Quotation.QuoteEvenInBold(
                                    "no module named ",
                                    moduleId,
                                    " is defined here."),
                                Assembler.Highlight(expression)));
                        return(Array.Empty <object>());
                    }
                }
            }
            else if (expression.IsCallTo("get"))
            {
                var tail     = expression.Tail;
                var moduleId = Assembler.AssembleLabelOrNull(ref tail);
                var name     = Assembler.AssembleString(tail[0], Log);
                if (moduleId == null)
                {
                    foreach (var inst in moduleInstances)
                    {
                        if (inst.ExportedGlobals.TryGetValue(name, out Variable def))
                        {
                            return(new[] { def.Get <object>() });
                        }
                    }

                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "undefined global",
                            Quotation.QuoteEvenInBold(
                                "no global named ",
                                name,
                                " is defined here."),
                            Assembler.Highlight(expression)));
                    return(Array.Empty <object>());
                }
                else
                {
                    if (moduleInstancesByName.TryGetValue(moduleId, out ModuleInstance inst))
                    {
                        if (inst.ExportedGlobals.TryGetValue(name, out Variable def))
                        {
                            return(new[] { def.Get <object>() });
                        }
                        else
                        {
                            Log.Log(
                                new LogEntry(
                                    Severity.Error,
                                    "undefined global",
                                    Quotation.QuoteEvenInBold(
                                        "no global named ",
                                        name,
                                        " is defined in module ",
                                        moduleId,
                                        "."),
                                    Assembler.Highlight(expression)));
                            return(Array.Empty <object>());
                        }
                    }
                    else
                    {
                        Log.Log(
                            new LogEntry(
                                Severity.Error,
                                "undefined module",
                                Quotation.QuoteEvenInBold(
                                    "no module named ",
                                    moduleId,
                                    " is defined here."),
                                Assembler.Highlight(expression)));
                        return(Array.Empty <object>());
                    }
                }
            }
            else
            {
                Log.Log(
                    new LogEntry(
                        Severity.Error,
                        "unknown action",
                        Quotation.QuoteEvenInBold(
                            "expression ",
                            expression.Head.Span.Text,
                            " was not recognized as a known script action."),
                        Assembler.Highlight(expression)));
                return(Array.Empty <object>());
            }
        }
Beispiel #5
0
        /// <summary>
        /// Runs a single expression in the script.
        /// </summary>
        /// <param name="expression">The expression to run.</param>
        public TestStatistics Run(SExpression expression)
        {
            if (expression.IsCallTo("module"))
            {
                var module   = Assembler.AssembleModule(expression, out string moduleId);
                var instance = Wasm.Interpret.ModuleInstance.Instantiate(
                    module,
                    importer,
                    policy: ExecutionPolicy.Create(maxMemorySize: 0x1000),
                    compiler: Compiler);

                moduleInstances.Add(instance);
                if (moduleId != null)
                {
                    moduleInstancesByName[moduleId] = instance;
                }
                if (module.StartFunctionIndex.HasValue)
                {
                    instance.Functions[(int)module.StartFunctionIndex.Value].Invoke(Array.Empty <object>());
                }
                return(TestStatistics.Empty);
            }
            else if (expression.IsCallTo("register"))
            {
                var tail = expression.Tail;
                var name = Assembler.AssembleString(tail[0], Log);
                tail = tail.Skip(1).ToArray();
                var moduleId = Assembler.AssembleLabelOrNull(ref tail);
                if (moduleId == null)
                {
                    importer.RegisterImporter(name, new ModuleExportsImporter(moduleInstances[moduleInstances.Count - 1]));
                }
                else
                {
                    importer.RegisterImporter(name, new ModuleExportsImporter(moduleInstancesByName[moduleId]));
                }
                return(TestStatistics.Empty);
            }
            else if (expression.IsCallTo("invoke") || expression.IsCallTo("get"))
            {
                RunAction(expression);
                return(TestStatistics.SingleSuccess);
            }
            else if (expression.IsCallTo("assert_return"))
            {
                var results  = RunAction(expression.Tail[0]);
                var expected = expression.Tail
                               .Skip(1)
                               .Zip(results, (expr, val) => EvaluateConstExpr(expr, val.GetType()))
                               .ToArray();

                if (expected.Length != results.Count)
                {
                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "assertion failed",
                            "action produced result ",
                            string.Join(", ", results),
                            "; expected ",
                            string.Join(", ", expected),
                            ".",
                            Assembler.Highlight(expression)));
                    return(TestStatistics.SingleFailure);
                }

                bool failures = false;
                for (int i = 0; i < expected.Length; i++)
                {
                    if (!object.Equals(results[i], expected[i]))
                    {
                        if (AlmostEquals(results[i], expected[i]))
                        {
                            Log.Log(
                                new LogEntry(
                                    Severity.Warning,
                                    "rounding error",
                                    "action produced result ",
                                    results[i].ToString(),
                                    "; expected ",
                                    expected[i].ToString(),
                                    ".",
                                    Assembler.Highlight(expression)));
                        }
                        else
                        {
                            Log.Log(
                                new LogEntry(
                                    Severity.Error,
                                    "assertion failed",
                                    "action produced result ",
                                    results[i].ToString(),
                                    "; expected ",
                                    expected[i].ToString(),
                                    ".",
                                    Assembler.Highlight(expression)));
                            failures = true;
                        }
                    }
                }
                return(failures ? TestStatistics.SingleFailure : TestStatistics.SingleSuccess);
            }
            else if (expression.IsCallTo("assert_trap") || expression.IsCallTo("assert_exhaustion"))
            {
                var       expected  = Assembler.AssembleString(expression.Tail[1], Log);
                bool      caught    = false;
                Exception exception = null;
                try
                {
                    if (expression.Tail[0].IsCallTo("module"))
                    {
                        Run(expression.Tail[0]);
                    }
                    else
                    {
                        RunAction(expression.Tail[0], false);
                    }
                }
                catch (TrapException ex)
                {
                    caught    = ex.SpecMessage == expected;
                    exception = ex;
                }
                catch (PixieException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    caught    = false;
                    exception = ex;
                }

                if (caught)
                {
                    return(TestStatistics.SingleSuccess);
                }
                else
                {
                    if (exception == null)
                    {
                        Log.Log(
                            new LogEntry(
                                Severity.Error,
                                "assertion failed",
                                "action should have trapped, but didn't.",
                                Assembler.Highlight(expression)));
                    }
                    else
                    {
                        Log.Log(
                            new LogEntry(
                                Severity.Error,
                                "assertion failed",
                                "action trapped as expected, but with an unexpected exception. ",
                                exception.ToString(),
                                Assembler.Highlight(expression)));
                    }
                    return(TestStatistics.SingleFailure);
                }
            }
            else if (expression.IsCallTo("assert_return_canonical_nan"))
            {
                var  results = RunAction(expression.Tail[0]);
                bool isCanonicalNaN;
                if (results.Count != 1)
                {
                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "assertion failed",
                            "action produced ",
                            results.Count.ToString(),
                            " results (",
                            string.Join(", ", results),
                            "); expected a single canonical NaN.",
                            Assembler.Highlight(expression)));
                    return(TestStatistics.SingleFailure);
                }
                else if (results[0] is double)
                {
                    var val = Interpret.ValueHelpers.ReinterpretAsInt64((double)results[0]);
                    isCanonicalNaN = val == Interpret.ValueHelpers.ReinterpretAsInt64((double)FloatLiteral.NaN(false)) ||
                                     val == Interpret.ValueHelpers.ReinterpretAsInt64((double)FloatLiteral.NaN(true));
                }
                else if (results[0] is float)
                {
                    var val = Interpret.ValueHelpers.ReinterpretAsInt32((float)results[0]);
                    isCanonicalNaN = val == Interpret.ValueHelpers.ReinterpretAsInt32((float)FloatLiteral.NaN(false)) ||
                                     val == Interpret.ValueHelpers.ReinterpretAsInt32((float)FloatLiteral.NaN(true));
                }
                else
                {
                    isCanonicalNaN = false;
                }
                if (isCanonicalNaN)
                {
                    return(TestStatistics.SingleSuccess);
                }
                else
                {
                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "assertion failed",
                            "action produced ",
                            results[0].ToString(),
                            "; expected a single canonical NaN.",
                            Assembler.Highlight(expression)));
                    return(TestStatistics.SingleFailure);
                }
            }
            else if (expression.IsCallTo("assert_return_arithmetic_nan"))
            {
                var  results = RunAction(expression.Tail[0]);
                bool isNaN;
                if (results.Count != 1)
                {
                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "assertion failed",
                            "action produced ",
                            results.Count.ToString(),
                            " results (",
                            string.Join(", ", results),
                            "); expected a single NaN.",
                            Assembler.Highlight(expression)));
                    return(TestStatistics.SingleFailure);
                }
                else if (results[0] is double)
                {
                    isNaN = double.IsNaN((double)results[0]);
                }
                else if (results[0] is float)
                {
                    isNaN = float.IsNaN((float)results[0]);
                }
                else
                {
                    isNaN = false;
                }
                if (isNaN)
                {
                    return(TestStatistics.SingleSuccess);
                }
                else
                {
                    Log.Log(
                        new LogEntry(
                            Severity.Error,
                            "assertion failed",
                            "action produced ",
                            results[0].ToString(),
                            "; expected a single NaN.",
                            Assembler.Highlight(expression)));
                    return(TestStatistics.SingleFailure);
                }
            }
            else
            {
                Log.Log(
                    new LogEntry(
                        Severity.Warning,
                        "unknown script command",
                        Quotation.QuoteEvenInBold(
                            "expression ",
                            expression.Head.Span.Text,
                            " was not recognized as a known script command."),
                        Assembler.Highlight(expression)));
                return(TestStatistics.SingleUnknown);
            }
        }