openscad-cpp-evaluator 0.5.1__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/bytecode.hpp +78 -9
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/bytecode_compiler.cpp +74 -42
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/bytecode_vm.cpp +57 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/user_calls.cpp +26 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_bytecode_compiler.cpp +131 -21
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_tail_calls.cpp +29 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/.gitignore +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/.gitmodules +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/LICENSE +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/README.md +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/stb_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/builtins.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/modifiers.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/registry.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/csg_resolve.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/text/stb_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_booleans.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tools/cli/main.cpp +0 -0
|
@@ -100,16 +100,47 @@ enum class Op {
|
|
|
100
100
|
// a = index into CompiledChunk::upvalues. At runtime, searches the live
|
|
101
101
|
// call stack (Evaluator::findUpvalue) for a still-active call of the
|
|
102
102
|
// upvalue's own target declaration and reads that call's own slot --
|
|
103
|
-
// undef if that call already returned
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
103
|
+
// undef if that call already returned. Never needs a STORE counterpart:
|
|
104
|
+
// a captured variable is always a parameter/let-binding of some
|
|
105
|
+
// ENCLOSING, already-executing call, and nothing this phase compiles
|
|
106
|
+
// ever writes back into an enclosing call's own slot (let/ListCompLet
|
|
107
|
+
// assignments always target a freshly-opened scope of their OWN, never
|
|
108
|
+
// a parent's).
|
|
109
|
+
//
|
|
110
|
+
// NOTE: as of Op::MakeClosure (below), no CompiledChunk that's ever
|
|
111
|
+
// actually registered (chunkCache_/literalChunkCache_) has a non-empty
|
|
112
|
+
// `upvalues` list any more -- a FunctionLiteral with any real capture
|
|
113
|
+
// need (direct or transitively bubbled up from a nested literal, see
|
|
114
|
+
// bytecode_compiler.cpp's FunctionLiteral case) is now handled via
|
|
115
|
+
// Op::MakeClosure instead, and always runs interpreted when invoked
|
|
116
|
+
// (Evaluator::lookupCompiledLiteralChunk finds no entry for it). This
|
|
117
|
+
// opcode and Evaluator::findUpvalue/CallStackFrame::upvalueParent are
|
|
118
|
+
// therefore unreachable dead code today, left in place rather than
|
|
119
|
+
// removed in the same change that made them unreachable -- deleting
|
|
120
|
+
// them is a distinct, lower-risk cleanup, not bundled in here.
|
|
111
121
|
LoadUpvalue,
|
|
112
122
|
|
|
123
|
+
// Escaping-closure support. a = index into CompiledChunk::closureSites.
|
|
124
|
+
// Builds a FRESH captured environment every time this instruction
|
|
125
|
+
// actually runs (never a compile-time constant -- a loop creating one
|
|
126
|
+
// closure per iteration must capture THAT iteration's own values, not
|
|
127
|
+
// share one snapshot) by reading each of the site's own captures
|
|
128
|
+
// straight out of the CURRENTLY EXECUTING frame's own `slots` (see
|
|
129
|
+
// ClosureSite's own doc comment for why every capture, however deeply
|
|
130
|
+
// the literal was originally nested, always resolves against the one
|
|
131
|
+
// chunk actually running Op::MakeClosure), and constructs
|
|
132
|
+
// Closure{node, capturedTrail} -- reusing Closure::capturedLet (value.hpp)
|
|
133
|
+
// and callCtxFor's existing capturedLet-rooting (user_calls.cpp)
|
|
134
|
+
// UNCHANGED: invoking this closure later (always via the interpreter,
|
|
135
|
+
// see ClosureSite's own doc comment for why its body is never itself
|
|
136
|
+
// compiled) works exactly like any interpreter-created escaping closure
|
|
137
|
+
// always has, no new invocation-side code needed at all. Replaces
|
|
138
|
+
// Op::PushConst's frozen `Closure{&n, nullptr}` specifically for a
|
|
139
|
+
// literal whose (transitively merged) capture set is non-empty; a
|
|
140
|
+
// literal that closes over nothing at all -- even transitively -- still
|
|
141
|
+
// takes the cheaper PushConst path, unchanged.
|
|
142
|
+
MakeClosure,
|
|
143
|
+
|
|
113
144
|
// A call whose callee isn't statically resolvable to a builtin or a
|
|
114
145
|
// named FunctionDeclaration (see CallFn) -- the callee expression is
|
|
115
146
|
// compiled to push its own Value first, THEN each argument (so at
|
|
@@ -225,10 +256,47 @@ struct CompiledChunk {
|
|
|
225
256
|
// inside AT COMPILE TIME (matched at runtime via Evaluator::
|
|
226
257
|
// findUpvalue's exact-identity search over the live call stack, the
|
|
227
258
|
// same identity CallStackFrame::declNode is stamped with), `slot` is
|
|
228
|
-
// that declaration's own slot number for the captured variable.
|
|
259
|
+
// that declaration's own slot number for the captured variable. `name`
|
|
260
|
+
// is that same variable's source name -- unused by Op::LoadUpvalue
|
|
261
|
+
// itself, needed by Op::MakeClosure's ClosureSite (below), which builds
|
|
262
|
+
// a NAME-keyed captured environment (Closure::capturedLet is a
|
|
263
|
+
// TrailView<Value>, looked up by name at invocation time, exactly like
|
|
264
|
+
// every other escaping closure) rather than a slot-indexed one.
|
|
229
265
|
struct UpvalueRef {
|
|
230
266
|
const oscad::ASTNode* targetDecl = nullptr;
|
|
231
267
|
int slot = 0;
|
|
268
|
+
std::string name;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// One FunctionLiteral's own capture list for Op::MakeClosure, computed
|
|
272
|
+
// at compile time (bytecode_compiler.cpp's FunctionLiteral case) as the
|
|
273
|
+
// TRANSITIVE union of: every free variable `node` itself references
|
|
274
|
+
// from outside its own body, PLUS -- for each FunctionLiteral nested
|
|
275
|
+
// anywhere within `node` (however deep) -- whatever THAT nested
|
|
276
|
+
// literal's own ClosureSite still needed from beyond `node`'s own scope
|
|
277
|
+
// (i.e. any capture whose resolution target isn't `node` itself). This
|
|
278
|
+
// "bubbling" is what makes a closure nested inside another compiled-
|
|
279
|
+
// creating closure resolve correctly even though the inner one's body
|
|
280
|
+
// is never itself compiled (see this struct's own body-compilation
|
|
281
|
+
// note below): by the time a real, running CompiledChunk executes
|
|
282
|
+
// Op::MakeClosure for `node`, every one of `captures`'s entries is
|
|
283
|
+
// guaranteed to resolve against THAT SAME running chunk's own `slots`
|
|
284
|
+
// array, regardless of how many literal-within-literal levels `node`
|
|
285
|
+
// was originally nested through in the source.
|
|
286
|
+
//
|
|
287
|
+
// `node`'s own BODY is deliberately never itself compiled/registered
|
|
288
|
+
// once it has any capture need (even an empty one bubbled up from
|
|
289
|
+
// something it contains) -- it always runs via the ordinary AST
|
|
290
|
+
// interpreter when invoked (Evaluator::lookupCompiledLiteralChunk finds
|
|
291
|
+
// no cache entry for it), using Closure::capturedLet exactly like any
|
|
292
|
+
// other escaping closure the interpreter creates directly. This keeps
|
|
293
|
+
// the whole feature to "make closure CREATION safe to compile"; a
|
|
294
|
+
// closure's own body additionally getting compiled too (its captures
|
|
295
|
+
// would need to read from this snapshot instead of a live-stack walk)
|
|
296
|
+
// is a natural follow-on, not attempted here.
|
|
297
|
+
struct ClosureSite {
|
|
298
|
+
const oscad::FunctionLiteral* node = nullptr;
|
|
299
|
+
std::vector<UpvalueRef> captures;
|
|
232
300
|
};
|
|
233
301
|
|
|
234
302
|
// One echo() expression form's own argument names, in source order --
|
|
@@ -246,6 +314,7 @@ struct CompiledChunk {
|
|
|
246
314
|
std::vector<std::string> names;
|
|
247
315
|
std::vector<CallSite> callSites;
|
|
248
316
|
std::vector<UpvalueRef> upvalues;
|
|
317
|
+
std::vector<ClosureSite> closureSites;
|
|
249
318
|
std::vector<EchoSite> echoSites;
|
|
250
319
|
int numSlots = 0;
|
|
251
320
|
// Count of distinct ListCompFor-assignment iterLists allocated across
|
|
@@ -40,6 +40,33 @@ bool isListCompClauseKind(oscad::NodeKind kind) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Merges `site`'s own captures into `out` for every entry that escapes
|
|
44
|
+
// `owner` itself (targetDecl != owner) -- see ClosureSite's own doc comment
|
|
45
|
+
// (bytecode.hpp) for why this "bubbling" is necessary: a literal nested
|
|
46
|
+
// inside `owner` may need something from further out than `owner`'s own
|
|
47
|
+
// scope, and since that inner literal's body is never itself compiled
|
|
48
|
+
// (only ever invoked via the interpreter once it has any capture need),
|
|
49
|
+
// `owner`'s OWN Op::MakeClosure must snapshot that name too -- otherwise
|
|
50
|
+
// the inner literal's interpreter-resolved capturedLet ancestry, rooted at
|
|
51
|
+
// whatever `owner` itself captured, would simply never contain it.
|
|
52
|
+
// Dedupes by (targetDecl, slot): the same enclosing binding reached through
|
|
53
|
+
// two different nested literals (or the same literal referencing it twice)
|
|
54
|
+
// is still one capture to make.
|
|
55
|
+
void bubbleEscapingCaptures(const CompiledChunk::ClosureSite& site, const oscad::ASTNode* owner,
|
|
56
|
+
std::vector<CompiledChunk::UpvalueRef>& out) {
|
|
57
|
+
for (const auto& cap : site.captures) {
|
|
58
|
+
if (cap.targetDecl == owner) continue;
|
|
59
|
+
bool already = false;
|
|
60
|
+
for (const auto& existing : out) {
|
|
61
|
+
if (existing.targetDecl == cap.targetDecl && existing.slot == cap.slot) {
|
|
62
|
+
already = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!already) out.push_back(cap);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
// Compile-time name -> slot resolution, mirroring the LetOp/nested-scope
|
|
44
71
|
// shadowing rules callCtx()/letChildCtx() apply at runtime: one frame per
|
|
45
72
|
// LetOp (pushed/popped around its own assignments+body), innermost frame
|
|
@@ -149,7 +176,7 @@ public:
|
|
|
149
176
|
// naturally resolve to the innermost active binding too.
|
|
150
177
|
std::optional<CompiledChunk::UpvalueRef> resolveEnclosing(const std::string& name) const {
|
|
151
178
|
for (auto it = enclosing_.rbegin(); it != enclosing_.rend(); ++it) {
|
|
152
|
-
if (auto slot = it->scope->resolve(name)) return CompiledChunk::UpvalueRef{it->decl, *slot};
|
|
179
|
+
if (auto slot = it->scope->resolve(name)) return CompiledChunk::UpvalueRef{it->decl, *slot, name};
|
|
153
180
|
}
|
|
154
181
|
return std::nullopt;
|
|
155
182
|
}
|
|
@@ -253,20 +280,14 @@ public:
|
|
|
253
280
|
// other point where its own free-variable references could be
|
|
254
281
|
// resolved against this enclosing chain (AST nodes have no
|
|
255
282
|
// parent pointers to discover it from later, see this file's
|
|
256
|
-
// own module notes). If the literal itself fails to compile
|
|
257
|
-
// (contains
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// ctx.let_) locals otherwise. On success, the literal's own
|
|
265
|
-
// chunk is stashed in chunk_.nestedLiterals for
|
|
266
|
-
// Evaluator::lookupOrCompileChunk to flatten into
|
|
267
|
-
// literalChunkCache_; the emitted instruction just pushes the
|
|
268
|
-
// AST pointer as a Value, exactly like the interpreter's own
|
|
269
|
-
// `Value{&node}` (evalExpr's own FunctionLiteral case).
|
|
283
|
+
// own module notes). If the literal itself fails to compile as
|
|
284
|
+
// bytecode at all (contains an echo/assert/unsupported
|
|
285
|
+
// construct), the WHOLE containing declaration must also bail
|
|
286
|
+
// -- propagated by simply rethrowing -- since there's no lighter
|
|
287
|
+
// way left to find out what it captures (see
|
|
288
|
+
// bubbleEscapingCaptures' own doc comment for why a literal
|
|
289
|
+
// that DOES compile as bytecode no longer needs this bail just
|
|
290
|
+
// for referencing enclosing state).
|
|
270
291
|
case NodeKind::FunctionLiteral: {
|
|
271
292
|
auto& n = static_cast<const oscad::FunctionLiteral&>(node);
|
|
272
293
|
std::vector<EnclosingLevel> childEnclosing = enclosing_;
|
|
@@ -276,33 +297,44 @@ public:
|
|
|
276
297
|
*n.body)) {
|
|
277
298
|
throw NotCompilable{};
|
|
278
299
|
}
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
// Effective capture set: `n`'s own direct free-variable
|
|
301
|
+
// references, plus -- transitively -- whatever any literal
|
|
302
|
+
// nested inside `n` still needs from beyond `n`'s own scope
|
|
303
|
+
// (see bubbleEscapingCaptures' own doc comment, above, and
|
|
304
|
+
// ClosureSite's, bytecode.hpp).
|
|
305
|
+
std::vector<CompiledChunk::UpvalueRef> effectiveCaptures = literalChunk.upvalues;
|
|
306
|
+
for (const auto& nestedSite : literalChunk.closureSites) {
|
|
307
|
+
bubbleEscapingCaptures(nestedSite, &n, effectiveCaptures);
|
|
308
|
+
}
|
|
309
|
+
if (effectiveCaptures.empty()) {
|
|
310
|
+
// Closes over nothing at all, even transitively -- the
|
|
311
|
+
// existing fast path: a single frozen constant works
|
|
312
|
+
// for every invocation, and this literal's own compiled
|
|
313
|
+
// chunk (possibly containing its OWN MakeClosure sites
|
|
314
|
+
// for anything nested inside IT that closes only over
|
|
315
|
+
// ITS OWN scope) is genuinely usable, registered as
|
|
316
|
+
// before.
|
|
317
|
+
chunk_.nestedLiterals.emplace_back(&n, std::move(literalChunk));
|
|
318
|
+
out.push_back(
|
|
319
|
+
{Op::PushConst, internConst(Value{std::make_shared<const Closure>(Closure{&n, nullptr})}), 0,
|
|
320
|
+
nullptr});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Escaping-closure support (Op::MakeClosure, bytecode.hpp).
|
|
324
|
+
// `literalChunk`'s own bytecode is discarded here -- its own
|
|
325
|
+
// upvalue reads would resolve via the live-call-stack walk
|
|
326
|
+
// (Op::LoadUpvalue), which is wrong for a closure that
|
|
327
|
+
// outlives its creator -- so this literal always runs
|
|
328
|
+
// interpreted when invoked instead (Evaluator::
|
|
329
|
+
// lookupCompiledLiteralChunk finds no cache entry for it),
|
|
330
|
+
// using the exact same Closure::capturedLet machinery every
|
|
331
|
+
// other escaping closure the interpreter creates directly
|
|
332
|
+
// already relies on. Only `effectiveCaptures` (the names/
|
|
333
|
+
// slots to snapshot right now) survives from the discarded
|
|
334
|
+
// compile attempt.
|
|
335
|
+
int siteIdx = static_cast<int>(chunk_.closureSites.size());
|
|
336
|
+
chunk_.closureSites.push_back(CompiledChunk::ClosureSite{&n, std::move(effectiveCaptures)});
|
|
337
|
+
out.push_back({Op::MakeClosure, siteIdx, 0, nullptr});
|
|
306
338
|
return;
|
|
307
339
|
}
|
|
308
340
|
case NodeKind::RangeLiteral: {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
5
5
|
#include "openscad_cpp_evaluator/function_builtins.hpp"
|
|
6
6
|
#include "openscad_cpp_evaluator/import_builtin.hpp"
|
|
7
|
+
#include "openscad_cpp_evaluator/scope_trail.hpp"
|
|
7
8
|
|
|
8
9
|
#include "openscad_cpp_parser/ast/declarations.hpp"
|
|
9
10
|
|
|
@@ -132,6 +133,33 @@ Value runChunk(Evaluator& ev, const CompiledChunk& chunk, const std::vector<Inst
|
|
|
132
133
|
++pc;
|
|
133
134
|
break;
|
|
134
135
|
}
|
|
136
|
+
case Op::MakeClosure: {
|
|
137
|
+
// Builds a FRESH captured environment every time this
|
|
138
|
+
// instruction actually runs (never a compile-time constant
|
|
139
|
+
// -- a loop creating one closure per iteration must capture
|
|
140
|
+
// THAT iteration's own values, not share one snapshot) by
|
|
141
|
+
// reading each of the site's own captures straight out of
|
|
142
|
+
// THIS frame's own `slots` -- see ClosureSite's own doc
|
|
143
|
+
// comment (bytecode.hpp) for why every capture, however
|
|
144
|
+
// deeply the literal was originally nested, is guaranteed
|
|
145
|
+
// to resolve against this SAME currently-running chunk.
|
|
146
|
+
// The resulting Closure::capturedLet is a real, standalone
|
|
147
|
+
// TrailView (isolated root -- nothing else shares it, and
|
|
148
|
+
// it owes nothing to `ctx.let_`, which a compiled call
|
|
149
|
+
// never populates -- see bindCompiledArgs' own doc
|
|
150
|
+
// comment), so invoking this closure later works through
|
|
151
|
+
// the EXACT same callCtxFor/capturedLetTrail machinery
|
|
152
|
+
// every interpreter-created escaping closure already uses,
|
|
153
|
+
// unchanged.
|
|
154
|
+
const CompiledChunk::ClosureSite& site = chunk.closureSites[static_cast<size_t>(ins.a)];
|
|
155
|
+
auto capturedTrail = TrailView<Value>::makeRoot();
|
|
156
|
+
for (const auto& cap : site.captures) {
|
|
157
|
+
capturedTrail->set(cap.name, slots[static_cast<size_t>(cap.slot)]);
|
|
158
|
+
}
|
|
159
|
+
stack.push_back(Value{std::make_shared<const Closure>(Closure{site.node, std::move(capturedTrail)})});
|
|
160
|
+
++pc;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
135
163
|
case Op::Range: {
|
|
136
164
|
Value step = std::move(stack.back());
|
|
137
165
|
stack.pop_back();
|
|
@@ -636,10 +664,38 @@ Value runCompiledFunctionFromBound(Evaluator& ev, const CompiledChunk& chunk, co
|
|
|
636
664
|
// *stack* growth (what this trampoline exists to eliminate) for heap
|
|
637
665
|
// growth instead -- O(iteration count) EvalContext objects, bounded by
|
|
638
666
|
// available RAM rather than a ~few-MB thread stack.
|
|
667
|
+
namespace {
|
|
668
|
+
// Tears a trampoline's own `chain` down back-to-front on EVERY exit path --
|
|
669
|
+
// normal return OR exception unwind (recordTailCallHop's own recursion-guard
|
|
670
|
+
// error is the exact case that matters: an infinite tail-recursive function
|
|
671
|
+
// with no base case throws out of the loop entirely, bypassing any teardown
|
|
672
|
+
// code placed after it). Relying on `chain` simply falling out of scope
|
|
673
|
+
// instead is NOT equivalent: std::vector<T>'s own element-destruction order
|
|
674
|
+
// is unspecified by the standard -- libc++ destroys back-to-front (matching
|
|
675
|
+
// ScopeTrailStorage::popLevel's own "scan from the back" doc comment, which
|
|
676
|
+
// assumes the level being popped is usually the most recently pushed one --
|
|
677
|
+
// O(1) per pop that way), but libstdc++ destroys front-to-back, the
|
|
678
|
+
// adversarial order for that same scan -- each pop then walks almost the
|
|
679
|
+
// entire remaining vector, turning an intended O(N) teardown into a real,
|
|
680
|
+
// measured O(N^2) (see issue #50: an N-scaling experiment plus a minimal
|
|
681
|
+
// repro proving libc++/libstdc++ disagree on std::vector<T>::clear()'s
|
|
682
|
+
// destruction order for identical source). Popping back-to-front explicitly,
|
|
683
|
+
// via a destructor that always runs before `chain`'s own, is correct AND
|
|
684
|
+
// O(N) on every standard library and every exit path, not just the ones
|
|
685
|
+
// that happen to agree with libc++ and return normally.
|
|
686
|
+
struct ChainTeardown {
|
|
687
|
+
std::vector<EvalContext>& chain;
|
|
688
|
+
~ChainTeardown() {
|
|
689
|
+
while (!chain.empty()) chain.pop_back();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
} // namespace
|
|
693
|
+
|
|
639
694
|
Value runCompiledFunctionTrampoline(Evaluator& ev, const CompiledChunk& chunk,
|
|
640
695
|
const std::vector<std::unique_ptr<oscad::Argument>>& arguments,
|
|
641
696
|
EvalContext& callerCtx, EvalContext& childCtx) {
|
|
642
697
|
std::vector<EvalContext> chain{childCtx};
|
|
698
|
+
ChainTeardown teardown{chain};
|
|
643
699
|
TailCallRequest req;
|
|
644
700
|
Value result = runCompiledFunction(ev, chunk, arguments, callerCtx, chain.back(), &req);
|
|
645
701
|
unsigned recursionGuard = 0;
|
|
@@ -661,6 +717,7 @@ Value runCompiledFunctionTrampoline(Evaluator& ev, const CompiledChunk& chunk,
|
|
|
661
717
|
Value runCompiledFunctionFromBoundTrampoline(Evaluator& ev, const CompiledChunk& chunk, const BoundArgs& bound,
|
|
662
718
|
EvalContext& childCtx) {
|
|
663
719
|
std::vector<EvalContext> chain{childCtx};
|
|
720
|
+
ChainTeardown teardown{chain};
|
|
664
721
|
TailCallRequest req;
|
|
665
722
|
Value result = runCompiledFunctionFromBound(ev, chunk, bound, chain.back(), &req);
|
|
666
723
|
unsigned recursionGuard = 0;
|
|
@@ -443,6 +443,32 @@ Value Evaluator::evalFunctionBodyTrampoline(const oscad::Expression& bodyExpr, E
|
|
|
443
443
|
// dropped there) if memory ever measurably matters more than this
|
|
444
444
|
// simpler all-or-nothing approach.
|
|
445
445
|
std::vector<EvalContext> chain{ctx};
|
|
446
|
+
// Tears `chain` down back-to-front on EVERY exit path -- normal return
|
|
447
|
+
// OR exception unwind (recordTailCallHop's own recursion-guard error,
|
|
448
|
+
// below, is the exact case that matters: an infinite tail-recursive
|
|
449
|
+
// function with no base case throws out of this loop entirely,
|
|
450
|
+
// bypassing any teardown code placed after it). Relying on `chain`
|
|
451
|
+
// simply falling out of scope instead is NOT equivalent: std::vector<T>'s
|
|
452
|
+
// own element-destruction order is unspecified by the standard --
|
|
453
|
+
// libc++ destroys back-to-front (matching ScopeTrailStorage::popLevel's
|
|
454
|
+
// own "scan from the back" doc comment, which assumes the level being
|
|
455
|
+
// popped is usually the most recently pushed one -- O(1) per pop that
|
|
456
|
+
// way), but libstdc++ destroys front-to-back, the adversarial order for
|
|
457
|
+
// that same scan -- each pop then walks almost the entire remaining
|
|
458
|
+
// vector, turning an intended O(N) teardown into a real, measured
|
|
459
|
+
// O(N^2) (see issue #50: an N-scaling experiment plus a minimal repro
|
|
460
|
+
// proving libc++/libstdc++ disagree on std::vector<T>::clear()'s
|
|
461
|
+
// destruction order for identical source). Popping back-to-front
|
|
462
|
+
// explicitly, via a destructor that always runs before `chain`'s own,
|
|
463
|
+
// is correct AND O(N) on every standard library and every exit path,
|
|
464
|
+
// not just the ones that happen to agree with libc++ and return
|
|
465
|
+
// normally.
|
|
466
|
+
struct ChainTeardown {
|
|
467
|
+
std::vector<EvalContext>& chain;
|
|
468
|
+
~ChainTeardown() {
|
|
469
|
+
while (!chain.empty()) chain.pop_back();
|
|
470
|
+
}
|
|
471
|
+
} teardown{chain};
|
|
446
472
|
unsigned recursionGuard = 0;
|
|
447
473
|
while (true) {
|
|
448
474
|
auto result = simplifyTailStep(*expr, ctx);
|
{openscad_cpp_evaluator-0.5.1 → openscad_cpp_evaluator-0.6.1}/tests/test_bytecode_compiler.cpp
RENAMED
|
@@ -380,14 +380,27 @@ TEST(BytecodeCompiler, VmOffAndVmOnAgreeOnListComprehensionCases) {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
// -- Phase 2: closures/upvalues --------------------------------------------
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
383
|
+
// The natural, most common shape is calling a locally-held function value
|
|
384
|
+
// directly (`g(5)` where `g` is a let/param bound to a FunctionLiteral),
|
|
385
|
+
// which needed its own dynamic-dispatch opcode (CALL_DYNAMIC) alongside
|
|
386
|
+
// LOAD_UPVALUE to be reachable at all; see bytecode_compiler.cpp's
|
|
387
|
+
// PrimaryCall case and bytecode_vm.cpp's CallDynamic handler.
|
|
388
|
+
//
|
|
389
|
+
// Phase 2b (Op::MakeClosure) then made the CONTAINING function compilable
|
|
390
|
+
// even when it creates a closure that escapes (returned, stored, passed on)
|
|
391
|
+
// -- previously ANY FunctionLiteral referencing enclosing state bailed the
|
|
392
|
+
// whole container, unconditionally, since Op::LOAD_UPVALUE's live-call-
|
|
393
|
+
// stack walk (findUpvalue) can only resolve a still-active call frame,
|
|
394
|
+
// never survive past it. Op::MakeClosure instead snapshots the exact
|
|
395
|
+
// values a literal needs into a real TrailView<Value> at the moment of
|
|
396
|
+
// creation (see bytecode.hpp's own doc comment), reusing Closure::
|
|
397
|
+
// capturedLet/callCtxFor's existing capture-rooting unchanged -- so the
|
|
398
|
+
// closure's own BODY still always runs interpreted when invoked (never
|
|
399
|
+
// itself compiled), but the function that CREATES it no longer has to.
|
|
400
|
+
// Several tests below predate Phase 2b and were written to describe the
|
|
401
|
+
// OLD bail-and-fall-back behavior; their own comments now note what
|
|
402
|
+
// actually happens post-Phase-2b instead of describing stale behavior as
|
|
403
|
+
// if it were still current.
|
|
391
404
|
|
|
392
405
|
TEST(BytecodeCompiler, ClosureCapturesParameterAndIsCalledDirectly) {
|
|
393
406
|
ScopedVm vm(true);
|
|
@@ -457,13 +470,12 @@ TEST(BytecodeCompiler, ClosureOverNonActiveEnclosingCallResolvesCorrectly) {
|
|
|
457
470
|
// (no escaping-closure support) as if it were the intended contract.
|
|
458
471
|
// The fix: Value's FunctionLiteral alternative became a real Closure
|
|
459
472
|
// (node + capturedLet, a shared_ptr<TrailView<Value>> snapshot of
|
|
460
|
-
// ctx.let_ at creation time, see value.hpp),
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
// capturedLet directly.
|
|
473
|
+
// ctx.let_ at creation time, see value.hpp). Post-Phase-2b, `outer`
|
|
474
|
+
// itself now compiles too (Op::MakeClosure builds an equivalent
|
|
475
|
+
// snapshot from compiled code, see bytecode.hpp) -- only the returned
|
|
476
|
+
// closure's own body (`function(y) y + n`) still always runs
|
|
477
|
+
// interpreted when invoked, reading its capturedLet directly via the
|
|
478
|
+
// same callCtxFor path an interpreter-created closure always has.
|
|
467
479
|
EXPECT_EQ(runCapturingEcho("function outer(n) = function(y) y + n;\n"
|
|
468
480
|
"stored = outer(5);\n"
|
|
469
481
|
"echo(stored(3));"),
|
|
@@ -490,6 +502,100 @@ TEST(BytecodeCompiler, VmOffAndVmOnAgreeOnClosureCases) {
|
|
|
490
502
|
EXPECT_EQ(offResult, onResult);
|
|
491
503
|
}
|
|
492
504
|
|
|
505
|
+
TEST(BytecodeCompiler, MakeClosureLetsContainerCompileDespiteNonEscapingClosure) {
|
|
506
|
+
ScopedVm vm(true);
|
|
507
|
+
// `make` creates AND immediately calls `g` -- before Op::MakeClosure
|
|
508
|
+
// this bailed `make` entirely (any non-empty upvalues did, escaping or
|
|
509
|
+
// not). A debug hook alone forces the interpreter regardless of
|
|
510
|
+
// compilability (see DebugAttachedWithoutFastContinueAlwaysInterprets
|
|
511
|
+
// above), so fast-continue must be enabled (an empty breakpoint map --
|
|
512
|
+
// fast-continue "on", nothing set) to actually observe whether `make`
|
|
513
|
+
// runs compiled here.
|
|
514
|
+
const int stops = countDebugHookStops("function make(x) = let(g = function(y) y + x) g(5);\n"
|
|
515
|
+
"echo(make(10));",
|
|
516
|
+
std::unordered_map<std::string, std::set<int>>{});
|
|
517
|
+
EXPECT_EQ(stops, 4);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
TEST(BytecodeCompiler, MakeClosureLetsContainerCompileDespiteEscapingClosure) {
|
|
521
|
+
ScopedVm vm(true);
|
|
522
|
+
// `outer` returns a closure that escapes past its own call entirely
|
|
523
|
+
// (called later, from a separate top-level statement, see
|
|
524
|
+
// ClosureOverNonActiveEnclosingCallResolvesCorrectly above for the
|
|
525
|
+
// correctness side of this same shape) -- `outer` itself now compiles
|
|
526
|
+
// too (fast-continue enabled for the same reason as the non-escaping
|
|
527
|
+
// case above). The ternary is needed here purely to make compiled-vs-
|
|
528
|
+
// interpreted OBSERVABLE via stop count: a body that's nothing but
|
|
529
|
+
// "return a closure" has no sub-expression checkpoints in the
|
|
530
|
+
// interpreter either way (a FunctionLiteral's own creation isn't itself
|
|
531
|
+
// a checkpoint), so it produces the SAME stop count whether `outer`
|
|
532
|
+
// compiled or not -- confirmed empirically, not assumed, by diffing
|
|
533
|
+
// this test's own result against the pre-Op::MakeClosure compiler.
|
|
534
|
+
const int stops = countDebugHookStops(
|
|
535
|
+
"function outer(n) = n > 0 ? function(y) y + n : function(y) y - n;\n"
|
|
536
|
+
"stored = outer(5);\n"
|
|
537
|
+
"echo(stored(3));",
|
|
538
|
+
std::unordered_map<std::string, std::set<int>>{});
|
|
539
|
+
EXPECT_EQ(stops, 6);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
TEST(BytecodeCompiler, MakeClosureCapturesFreshValuePerInvocation) {
|
|
543
|
+
ScopedVm vm(true);
|
|
544
|
+
// Op::MakeClosure runs fresh every time the instruction is actually
|
|
545
|
+
// reached (never a compile-time constant) -- a closure built once per
|
|
546
|
+
// list-comprehension iteration must capture THAT iteration's own `i`,
|
|
547
|
+
// not all share whatever `i` ended at. `make_adders(3)` returns
|
|
548
|
+
// [function(y) y+0, function(y) y+1, function(y) y+2]; calling each
|
|
549
|
+
// with 10 and summing proves each closed over its OWN value.
|
|
550
|
+
EXPECT_EQ(runCapturingEcho("function make_adders(n) = [for (i = [0:1:n-1]) function(y) y + i];\n"
|
|
551
|
+
"fns = make_adders(3);\n"
|
|
552
|
+
"echo(fns[0](10) + fns[1](10) + fns[2](10));"),
|
|
553
|
+
"ECHO: 33");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
TEST(BytecodeCompiler, MakeClosureBubblesCaptureThroughAnIntermediateClosureLevel) {
|
|
557
|
+
ScopedVm vm(true);
|
|
558
|
+
// `inner` (nested inside `mid`, nested inside `outer`) references
|
|
559
|
+
// `outer`'s own `x` DIRECTLY, skipping `mid` entirely -- exercising
|
|
560
|
+
// bubbleEscapingCaptures' own transitive merge (bytecode_compiler.cpp):
|
|
561
|
+
// `mid`'s own ClosureSite must bubble this capture up so `outer`'s own
|
|
562
|
+
// Op::MakeClosure (for `mid`) snapshots `x` too, even though `mid`
|
|
563
|
+
// itself never reads `x`. Same values/shape as
|
|
564
|
+
// NestedClosureCapturesBothEnclosingLevels above, but this time
|
|
565
|
+
// `mid` escapes `outer` (returned, called from a separate statement)
|
|
566
|
+
// rather than being called back immediately -- so `outer` itself must
|
|
567
|
+
// now compile, and the correctness depends on the bubbled capture
|
|
568
|
+
// actually reaching `mid`'s own snapshot correctly.
|
|
569
|
+
const std::string script = "function outer(x) = let(mid = function(y) let(inner = function(z) x + y + z) "
|
|
570
|
+
"inner(1)) mid;\n"
|
|
571
|
+
"stored = outer(100);\n"
|
|
572
|
+
"echo(stored(10));";
|
|
573
|
+
EXPECT_EQ(runCapturingEcho(script), "ECHO: 111");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
TEST(BytecodeCompiler, ClosureWithDollarParameterStillBailsContainer) {
|
|
577
|
+
ScopedVm vm(true);
|
|
578
|
+
// Residual, deliberate limitation: a FunctionLiteral with its OWN
|
|
579
|
+
// dollar-prefixed parameter still fails compileFunctionLike outright
|
|
580
|
+
// (same $-prefixed-parameter rule as a plain named function, see
|
|
581
|
+
// DollarPrefixedParameterBailsCompilationAndStillWorks above) -- there's
|
|
582
|
+
// no lighter way left to discover what it captures in that case, so the
|
|
583
|
+
// WHOLE containing declaration still bails (throw NotCompilable), same
|
|
584
|
+
// as before Op::MakeClosure. Value must still be correct via the
|
|
585
|
+
// interpreter fallback.
|
|
586
|
+
const int stops = countDebugHookStops(
|
|
587
|
+
"function outer(x) = let(g = function(y, $fn) y + x + $fn) g(5, $fn=2);\n"
|
|
588
|
+
"echo(outer(10));",
|
|
589
|
+
std::nullopt);
|
|
590
|
+
// `outer` never compiles at all here (more than 1 stop for its own
|
|
591
|
+
// call), unlike MakeClosureLetsContainerCompileDespiteNonEscapingClosure
|
|
592
|
+
// above where the analogous ($-free) container compiles to 1.
|
|
593
|
+
EXPECT_GT(stops, 2);
|
|
594
|
+
EXPECT_EQ(runCapturingEcho("function outer(x) = let(g = function(y, $fn) y + x + $fn) g(5, $fn=2);\n"
|
|
595
|
+
"echo(outer(10));"),
|
|
596
|
+
"ECHO: 17");
|
|
597
|
+
}
|
|
598
|
+
|
|
493
599
|
// -- Tail-call optimization, VM path (Phase B) -----------------------------
|
|
494
600
|
//
|
|
495
601
|
// Mirrors tests/test_tail_calls.cpp's own Phase A (interpreter) suite, but
|
|
@@ -537,13 +643,17 @@ TEST(BytecodeCompiler, MixedCompiledInterpretedTailChainStaysCorrect) {
|
|
|
537
643
|
|
|
538
644
|
TEST(BytecodeCompiler, ClosureNestedTailCallUnderVmFallsBackAndStillResolves) {
|
|
539
645
|
ScopedVm vm(true);
|
|
540
|
-
//
|
|
646
|
+
// `g` (a closure over `make`'s own `x`) is called in tail position from
|
|
647
|
+
// `make`'s own compiled body -- but `g`'s own body is never itself
|
|
648
|
+
// compiled post-Phase-2b (Op::MakeClosure's whole point is to let
|
|
649
|
+
// `make` compile despite creating `g`; `g` still always runs
|
|
650
|
+
// interpreted when invoked, see Op::MakeClosure's own doc comment,
|
|
651
|
+
// bytecode.hpp). CallDynamicTail's handler therefore finds no compiled
|
|
652
|
+
// chunk for `g` (Evaluator::lookupCompiledLiteralChunk returns nullptr)
|
|
653
|
+
// and falls back to a real evalFunctionLiteralFromBound call instead of
|
|
654
|
+
// trampolining, exactly like the interpreter path's own
|
|
541
655
|
// ClosureNestedTailCallFallsBackToRealRecursionAndStillResolves
|
|
542
|
-
// (test_tail_calls.cpp)
|
|
543
|
-
// make's body (a closure over `x`, read via Op::LoadUpvalue), so
|
|
544
|
-
// isolatedCallCtxFor must return nullopt for it, and CallDynamicTail's
|
|
545
|
-
// handler must fall back to a real evalFunctionLiteralFromBound call
|
|
546
|
-
// instead of trampolining.
|
|
656
|
+
// (test_tail_calls.cpp).
|
|
547
657
|
const std::string script = "function make(x) = let(g = function(y) y + x) g(5);\necho(make(10));";
|
|
548
658
|
EXPECT_EQ(runCapturingEcho(script), "ECHO: 15");
|
|
549
659
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
#include "test_helpers.hpp"
|
|
21
21
|
|
|
22
|
+
#include <chrono>
|
|
22
23
|
#include <gtest/gtest.h>
|
|
23
24
|
|
|
24
25
|
using namespace oscadeval;
|
|
@@ -153,6 +154,34 @@ TEST(TailCalls, InfiniteTailRecursionErrorMentionsTheFunctionName) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
TEST(TailCalls, InfiniteTailRecursionHitsTheCapInBoundedWallTime) {
|
|
158
|
+
// Regression tripwire for issue #50: evalFunctionBodyTrampoline's own
|
|
159
|
+
// `chain` (every hop's EvalContext, kept alive for $-var ancestry --
|
|
160
|
+
// see its own doc comment) used to rely on std::vector<EvalContext>'s
|
|
161
|
+
// OWN destructor to tear itself down, whose element-destruction order
|
|
162
|
+
// is unspecified by the standard. libstdc++ (GCC) and, per CI evidence,
|
|
163
|
+
// MSVC destroy front-to-back -- the adversarial order for
|
|
164
|
+
// ScopeTrailStorage::popLevel's "scan from the back" optimization
|
|
165
|
+
// (its own doc comment), turning an intended O(N) teardown into a
|
|
166
|
+
// real, measured O(N^2): the exact same 1,000,000-iteration script
|
|
167
|
+
// below took 25-50+ minutes on GCC/MSVC CI runners before the fix
|
|
168
|
+
// (an explicit back-to-front teardown, immune to the underlying
|
|
169
|
+
// vector's own unspecified order), vs ~1s on Clang/libc++ throughout.
|
|
170
|
+
// ponytail: generous fixed ceiling, not a strict perf target -- trips
|
|
171
|
+
// only on a real regression (this exact O(N^2) reintroduced), not
|
|
172
|
+
// machine noise; raise if a slower CI runner ever needs it.
|
|
173
|
+
ScopedVm vm(false);
|
|
174
|
+
Evaluator ev;
|
|
175
|
+
auto ast = parseSrc("function loop(n) = loop(n + 1);\nresult = loop(0);");
|
|
176
|
+
auto scope = oscad::buildScopes(ast);
|
|
177
|
+
EvalContext ctx = EvalContext::makeRoot(scope.get());
|
|
178
|
+
const auto start = std::chrono::steady_clock::now();
|
|
179
|
+
EXPECT_THROW(ev.resolveTree(ast, ctx), EvalError);
|
|
180
|
+
const double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
181
|
+
constexpr double kCeilingMs = 30000.0;
|
|
182
|
+
EXPECT_LT(ms, kCeilingMs) << "took " << ms << "ms -- possible O(N^2) regression, see issue #50";
|
|
183
|
+
}
|
|
184
|
+
|
|
156
185
|
TEST(TailCalls, ErrorThrownDeepInATailChainProducesABoundedTrace) {
|
|
157
186
|
ScopedVm vm(false);
|
|
158
187
|
Evaluator ev;
|
|
File without changes
|