From d1a6b7139d1712fdd673d4adaad5485dbb59d8bd Mon Sep 17 00:00:00 2001 From: gvozdvmozgu Date: Mon, 16 Mar 2026 04:39:11 -0700 Subject: [PATCH] chore: delete legacy codebase --- .github/workflows/dependencies.yml | 106 - .github/workflows/rust.yml | 94 - AGENTS.md | 51 - Cargo.lock | 4001 ------------- Cargo.toml | 72 - LICENSE | 21 - README.md | 3 - crates/mitki-abi-lower/Cargo.toml | 23 - crates/mitki-abi-lower/src/abi.rs | 1198 ---- crates/mitki-abi-lower/src/abi_v2.rs | 854 --- crates/mitki-abi-lower/src/lib.rs | 15 - crates/mitki-abi-lower/src/transport.rs | 98 - crates/mitki-abi/Cargo.toml | 19 - crates/mitki-abi/src/canonical.rs | 730 --- crates/mitki-abi/src/codec.rs | 150 - crates/mitki-abi/src/contract.rs | 992 ---- crates/mitki-abi/src/lib.rs | 39 - crates/mitki-abi/src/metadata.rs | 1650 ------ crates/mitki-analysis/Cargo.toml | 29 - crates/mitki-analysis/src/lib.rs | 945 --- crates/mitki-analysis/src/match_analysis.rs | 887 --- crates/mitki-analysis/src/ownership.rs | 154 - crates/mitki-analysis/src/semantics.rs | 739 --- .../src/wasm_boundary_legality.rs | 351 -- crates/mitki-backend-wasm/Cargo.toml | 44 - crates/mitki-backend-wasm/src/abi.rs | 44 - crates/mitki-backend-wasm/src/api.rs | 154 - crates/mitki-backend-wasm/src/backend.rs | 647 -- crates/mitki-backend-wasm/src/boundary.rs | 643 -- crates/mitki-backend-wasm/src/capability.rs | 9 - crates/mitki-backend-wasm/src/comptime.rs | 30 - .../mitki-backend-wasm/src/emit/boundary.rs | 4785 --------------- .../mitki-backend-wasm/src/emit/function.rs | 4605 --------------- crates/mitki-backend-wasm/src/emit/mod.rs | 11 - crates/mitki-backend-wasm/src/emit/module.rs | 5230 ----------------- crates/mitki-backend-wasm/src/layout.rs | 12 - crates/mitki-backend-wasm/src/lib.rs | 44 - .../src/lowering/function_codegen_ir.rs | 2503 -------- .../src/lowering/function_kernel.rs | 669 --- .../src/lowering/function_legalize.rs | 490 -- .../src/lowering/function_ownership.rs | 241 - .../src/lowering/function_wasm_ir.rs | 898 --- crates/mitki-backend-wasm/src/lowering/mod.rs | 8 - .../src/lowering/wrapper_mir.rs | 2505 -------- crates/mitki-backend-wasm/src/model.rs | 243 - crates/mitki-backend-wasm/src/obligations.rs | 283 - crates/mitki-backend-wasm/src/planning.rs | 2381 -------- crates/mitki-backend-wasm/src/reachability.rs | 2465 -------- .../src/reachability_graph.rs | 381 -- crates/mitki-backend-wasm/src/registry.rs | 657 --- crates/mitki-backend-wasm/src/storage.rs | 645 -- crates/mitki-backend-wasm/src/target.rs | 1008 ---- .../src/validation/capability.rs | 273 - .../src/validation/legality.rs | 107 - .../mitki-backend-wasm/src/validation/mod.rs | 9 - .../mitki-backend-wasm/src/validation/plan.rs | 1499 ----- .../mitki-backend-wasm/tests/abi_surface.rs | 394 -- crates/mitki-backend-wasm/tests/common/mod.rs | 331 -- .../tests/comptime_behavior.rs | 86 - .../tests/normalized_wat.rs | 184 - .../tests/runtime_behavior.rs | 694 --- crates/mitki-benchmark/Cargo.toml | 31 - .../benches/goto_definition.rs | 160 - crates/mitki-benchmark/benches/parser.rs | 61 - crates/mitki-benchmark/benches/tokenizer.rs | 69 - crates/mitki-benchmark/src/lib.rs | 1 - crates/mitki-codegen-core/Cargo.toml | 19 - crates/mitki-codegen-core/src/capability.rs | 165 - crates/mitki-codegen-core/src/classify.rs | 600 -- crates/mitki-codegen-core/src/descriptor.rs | 52 - crates/mitki-codegen-core/src/layout.rs | 180 - crates/mitki-codegen-core/src/lib.rs | 8 - crates/mitki-codegen-core/src/runtime_rep.rs | 810 --- .../mitki-codegen-core/src/stage_intrinsic.rs | 66 - crates/mitki-comptime-wasm/Cargo.toml | 30 - crates/mitki-comptime-wasm/src/lib.rs | 9 - crates/mitki-comptime-wasm/src/stage.rs | 121 - .../mitki-comptime-wasm/src/stage_runtime.rs | 620 -- crates/mitki-db/Cargo.toml | 21 - crates/mitki-db/src/lib.rs | 12 - crates/mitki-db/tests/modules.rs | 434 -- crates/mitki-db/tests/typeck.rs | 2022 ------- crates/mitki-errors/Cargo.toml | 16 - crates/mitki-errors/src/lib.rs | 59 - crates/mitki-hir-macros/Cargo.toml | 16 - crates/mitki-hir-macros/src/lib.rs | 769 --- crates/mitki-hir/Cargo.toml | 20 - crates/mitki-hir/src/arena.rs | 164 - crates/mitki-hir/src/hir.rs | 8 - crates/mitki-hir/src/hir/function.rs | 96 - crates/mitki-hir/src/hir/id.rs | 47 - crates/mitki-hir/src/hir/schema.rs | 156 - crates/mitki-hir/src/hir/store.rs | 62 - crates/mitki-hir/src/lib.rs | 3 - crates/mitki-hir/src/ty.rs | 209 - crates/mitki-hir/tests/hir_roundtrip.rs | 113 - crates/mitki-hir/tests/trybuild.rs | 5 - crates/mitki-hir/tests/trybuild/id_mixup.rs | 20 - .../mitki-hir/tests/trybuild/id_mixup.stderr | 40 - crates/mitki-ide/Cargo.toml | 25 - crates/mitki-ide/src/analysis.rs | 24 - .../mitki-ide/src/analysis/goto_definition.rs | 460 -- crates/mitki-ide/src/analysis/hover.rs | 469 -- crates/mitki-ide/src/analysis/inlay_hints.rs | 632 -- .../mitki-ide/src/analysis/semantic_tokens.rs | 140 - crates/mitki-ide/src/lib.rs | 50 - crates/mitki-inputs/Cargo.toml | 16 - crates/mitki-inputs/src/lib.rs | 28 - crates/mitki-lower/Cargo.toml | 23 - crates/mitki-lower/src/ast_map.rs | 75 - crates/mitki-lower/src/hir.rs | 62 - crates/mitki-lower/src/hir/function.rs | 729 --- crates/mitki-lower/src/item.rs | 4 - crates/mitki-lower/src/item/package.rs | 233 - crates/mitki-lower/src/item/scope.rs | 1415 ----- crates/mitki-lower/src/item/stdlib.rs | 27 - crates/mitki-lower/src/item/tree.rs | 227 - crates/mitki-lower/src/lib.rs | 8 - crates/mitki-lsp-server/Cargo.toml | 20 - crates/mitki-lsp-server/src/api.rs | 326 - crates/mitki-lsp-server/src/lib.rs | 158 - crates/mitki-lsp-server/src/notifications.rs | 48 - crates/mitki-lsp-server/src/requests.rs | 55 - crates/mitki-parse/Cargo.toml | 23 - crates/mitki-parse/src/grammar.rs | 57 - crates/mitki-parse/src/grammar/exprs.rs | 521 -- crates/mitki-parse/src/grammar/items.rs | 499 -- crates/mitki-parse/src/grammar/patterns.rs | 250 - crates/mitki-parse/src/grammar/types.rs | 238 - crates/mitki-parse/src/lib.rs | 58 - crates/mitki-parse/src/parser.rs | 345 -- crates/mitki-parse/src/tests.rs | 91 - .../mitki-parse/test_data/anon_record_expr.ir | 30 - .../test_data/anon_record_expr.mitki | 3 - .../test_data/anon_record_single_field.ir | 23 - .../test_data/anon_record_single_field.mitki | 3 - crates/mitki-parse/test_data/array_expr.ir | 38 - crates/mitki-parse/test_data/array_expr.mitki | 6 - crates/mitki-parse/test_data/call_expr.ir | 43 - crates/mitki-parse/test_data/call_expr.mitki | 4 - crates/mitki-parse/test_data/char_literal.ir | 15 - .../mitki-parse/test_data/char_literal.mitki | 3 - .../mitki-parse/test_data/closure_params.ir | 39 - .../test_data/closure_params.mitki | 3 - .../test_data/comptime_function.ir | 28 - .../test_data/comptime_function.mitki | 3 - .../mitki-parse/test_data/curly_in_params.ir | 15 - .../test_data/curly_in_params.mitki | 1 - .../test_data/destructor_member.ir | 85 - .../test_data/destructor_member.mitki | 15 - crates/mitki-parse/test_data/empty.ir | 3 - crates/mitki-parse/test_data/empty.mitki | 0 .../test_data/empty_braces_closure.ir | 17 - .../test_data/empty_braces_closure.mitki | 3 - crates/mitki-parse/test_data/enum_def.ir | 22 - crates/mitki-parse/test_data/enum_def.mitki | 5 - .../mitki-parse/test_data/enum_with_data.ir | 23 - .../test_data/enum_with_data.mitki | 4 - crates/mitki-parse/test_data/field_expr.ir | 27 - crates/mitki-parse/test_data/field_expr.mitki | 3 - crates/mitki-parse/test_data/function.ir | 15 - crates/mitki-parse/test_data/function.mitki | 3 - .../test_data/function_ret_type.ir | 27 - .../test_data/function_ret_type.mitki | 2 - .../test_data/generic_param_list.ir | 44 - .../test_data/generic_param_list.mitki | 3 - .../test_data/generic_type_path.ir | 58 - .../test_data/generic_type_path.mitki | 7 - .../test_data/if_block_after_path.ir | 31 - .../test_data/if_block_after_path.mitki | 3 - crates/mitki-parse/test_data/if_expr.ir | 50 - crates/mitki-parse/test_data/if_expr.mitki | 5 - crates/mitki-parse/test_data/instance_item.ir | 25 - .../mitki-parse/test_data/instance_item.mitki | 2 - crates/mitki-parse/test_data/loop_control.ir | 33 - .../mitki-parse/test_data/loop_control.mitki | 9 - crates/mitki-parse/test_data/loop_expr.ir | 18 - crates/mitki-parse/test_data/loop_expr.mitki | 3 - crates/mitki-parse/test_data/match_expr.ir | 185 - crates/mitki-parse/test_data/match_expr.mitki | 20 - .../mitki-parse/test_data/match_malformed.ir | 49 - .../test_data/match_malformed.mitki | 6 - .../test_data/match_typed_pattern.ir | 119 - .../test_data/match_typed_pattern.mitki | 10 - .../test_data/missing_fn_param_type.ir | 38 - .../test_data/missing_fn_param_type.mitki | 1 - crates/mitki-parse/test_data/module_item.ir | 8 - .../mitki-parse/test_data/module_item.mitki | 1 - crates/mitki-parse/test_data/module_syntax.ir | 33 - .../mitki-parse/test_data/module_syntax.mitki | 3 - .../test_data/mutable_assignment.ir | 50 - .../test_data/mutable_assignment.mitki | 3 - crates/mitki-parse/test_data/mutable_local.ir | 24 - .../mitki-parse/test_data/mutable_local.mitki | 4 - .../test_data/named_struct_empty_fields.ir | 20 - .../test_data/named_struct_empty_fields.mitki | 3 - crates/mitki-parse/test_data/param_list.ir | 48 - crates/mitki-parse/test_data/param_list.mitki | 3 - .../mitki-parse/test_data/pattern_params.ir | 140 - .../test_data/pattern_params.mitki | 9 - .../mitki-parse/test_data/qualified_path.ir | 43 - .../test_data/qualified_path.mitki | 3 - crates/mitki-parse/test_data/return.ir | 17 - crates/mitki-parse/test_data/return.mitki | 3 - .../mitki-parse/test_data/std_alloc_module.ir | 137 - .../test_data/std_alloc_module.mitki | 12 - .../mitki-parse/test_data/string_literal.ir | 15 - .../test_data/string_literal.mitki | 3 - crates/mitki-parse/test_data/struct_def.ir | 24 - crates/mitki-parse/test_data/struct_def.mitki | 4 - crates/mitki-parse/test_data/struct_expr.ir | 33 - .../mitki-parse/test_data/struct_expr.mitki | 3 - .../test_data/struct_expr_shorthand.ir | 38 - .../test_data/struct_expr_shorthand.mitki | 4 - crates/mitki-parse/test_data/tuple_expr.ir | 32 - crates/mitki-parse/test_data/tuple_expr.mitki | 6 - crates/mitki-parse/test_data/unary_expr.ir | 61 - crates/mitki-parse/test_data/unary_expr.mitki | 7 - crates/mitki-parse/test_data/val_stmt.ir | 32 - crates/mitki-parse/test_data/val_stmt.mitki | 4 - .../test_data/wasm_import_export_function.ir | 41 - .../wasm_import_export_function.mitki | 5 - crates/mitki-resolve/Cargo.toml | 19 - .../mitki-resolve/src/compiler_intrinsics.rs | 93 - crates/mitki-resolve/src/lib.rs | 18 - crates/mitki-resolve/src/resolver.rs | 517 -- crates/mitki-resolve/src/runtime.rs | 151 - crates/mitki-resolve/src/scope.rs | 384 -- crates/mitki-resolve/src/signature.rs | 168 - crates/mitki-span/Cargo.toml | 14 - crates/mitki-span/src/lib.rs | 3 - crates/mitki-span/src/symbol.rs | 19 - crates/mitki-tokenizer/Cargo.toml | 17 - crates/mitki-tokenizer/src/cursor.rs | 263 - crates/mitki-tokenizer/src/lib.rs | 937 --- crates/mitki-typeck/Cargo.toml | 19 - crates/mitki-typeck/src/infer.rs | 4481 -------------- crates/mitki-typeck/src/lib.rs | 2 - crates/mitki-wasm-runtime/Cargo.toml | 24 - .../examples/inspect_and_invoke.rs | 51 - .../mitki-wasm-runtime/inspect_and_invoke.rs | 51 - crates/mitki-wasm-runtime/src/lib.rs | 11 - crates/mitki-wasm-runtime/src/runtime.rs | 1663 ------ crates/mitki-wasm-runtime/src/runtime_v2.rs | 1360 ----- crates/mitki-yellow/Cargo.toml | 15 - crates/mitki-yellow/src/ast.rs | 1965 ------- crates/mitki-yellow/src/builder.rs | 416 -- crates/mitki-yellow/src/lib.rs | 27 - crates/mitki-yellow/src/maybe_dangling.rs | 59 - crates/mitki-yellow/src/nodes.rs | 9 - crates/mitki-yellow/src/nodes/node.rs | 252 - crates/mitki-yellow/src/nodes/token.rs | 239 - crates/mitki-yellow/src/nodes/tree.rs | 15 - crates/mitki-yellow/src/syntax.rs | 943 --- crates/mitki-yellow/src/syntax_kind.rs | 183 - crates/mitki-yellow/src/syntax_set.rs | 72 - crates/mitki-yellow/src/trivia.rs | 25 - crates/mitki/Cargo.toml | 34 - crates/mitki/src/main.rs | 526 -- crates/mitki/tests/cli_wasm.rs | 1381 ----- deny.toml | 14 - rust-toolchain.toml | 2 - rustfmt.toml | 9 - stdlib/src/alloc.mitki | 23 - stdlib/src/alloc/int.mitki | 27 - stdlib/src/env.mitki | 102 - stdlib/src/io.mitki | 53 - stdlib/src/lexer.mitki | 603 -- stdlib/src/lib.mitki | 6 - stdlib/src/str.mitki | 3 - stdlib/src/vec.mitki | 1 - stdlib/src/vec/int.mitki | 106 - 272 files changed, 82544 deletions(-) delete mode 100644 .github/workflows/dependencies.yml delete mode 100644 .github/workflows/rust.yml delete mode 100644 AGENTS.md delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 crates/mitki-abi-lower/Cargo.toml delete mode 100644 crates/mitki-abi-lower/src/abi.rs delete mode 100644 crates/mitki-abi-lower/src/abi_v2.rs delete mode 100644 crates/mitki-abi-lower/src/lib.rs delete mode 100644 crates/mitki-abi-lower/src/transport.rs delete mode 100644 crates/mitki-abi/Cargo.toml delete mode 100644 crates/mitki-abi/src/canonical.rs delete mode 100644 crates/mitki-abi/src/codec.rs delete mode 100644 crates/mitki-abi/src/contract.rs delete mode 100644 crates/mitki-abi/src/lib.rs delete mode 100644 crates/mitki-abi/src/metadata.rs delete mode 100644 crates/mitki-analysis/Cargo.toml delete mode 100644 crates/mitki-analysis/src/lib.rs delete mode 100644 crates/mitki-analysis/src/match_analysis.rs delete mode 100644 crates/mitki-analysis/src/ownership.rs delete mode 100644 crates/mitki-analysis/src/semantics.rs delete mode 100644 crates/mitki-analysis/src/wasm_boundary_legality.rs delete mode 100644 crates/mitki-backend-wasm/Cargo.toml delete mode 100644 crates/mitki-backend-wasm/src/abi.rs delete mode 100644 crates/mitki-backend-wasm/src/api.rs delete mode 100644 crates/mitki-backend-wasm/src/backend.rs delete mode 100644 crates/mitki-backend-wasm/src/boundary.rs delete mode 100644 crates/mitki-backend-wasm/src/capability.rs delete mode 100644 crates/mitki-backend-wasm/src/comptime.rs delete mode 100644 crates/mitki-backend-wasm/src/emit/boundary.rs delete mode 100644 crates/mitki-backend-wasm/src/emit/function.rs delete mode 100644 crates/mitki-backend-wasm/src/emit/mod.rs delete mode 100644 crates/mitki-backend-wasm/src/emit/module.rs delete mode 100644 crates/mitki-backend-wasm/src/layout.rs delete mode 100644 crates/mitki-backend-wasm/src/lib.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/function_codegen_ir.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/function_kernel.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/function_legalize.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/function_ownership.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/function_wasm_ir.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/mod.rs delete mode 100644 crates/mitki-backend-wasm/src/lowering/wrapper_mir.rs delete mode 100644 crates/mitki-backend-wasm/src/model.rs delete mode 100644 crates/mitki-backend-wasm/src/obligations.rs delete mode 100644 crates/mitki-backend-wasm/src/planning.rs delete mode 100644 crates/mitki-backend-wasm/src/reachability.rs delete mode 100644 crates/mitki-backend-wasm/src/reachability_graph.rs delete mode 100644 crates/mitki-backend-wasm/src/registry.rs delete mode 100644 crates/mitki-backend-wasm/src/storage.rs delete mode 100644 crates/mitki-backend-wasm/src/target.rs delete mode 100644 crates/mitki-backend-wasm/src/validation/capability.rs delete mode 100644 crates/mitki-backend-wasm/src/validation/legality.rs delete mode 100644 crates/mitki-backend-wasm/src/validation/mod.rs delete mode 100644 crates/mitki-backend-wasm/src/validation/plan.rs delete mode 100644 crates/mitki-backend-wasm/tests/abi_surface.rs delete mode 100644 crates/mitki-backend-wasm/tests/common/mod.rs delete mode 100644 crates/mitki-backend-wasm/tests/comptime_behavior.rs delete mode 100644 crates/mitki-backend-wasm/tests/normalized_wat.rs delete mode 100644 crates/mitki-backend-wasm/tests/runtime_behavior.rs delete mode 100644 crates/mitki-benchmark/Cargo.toml delete mode 100644 crates/mitki-benchmark/benches/goto_definition.rs delete mode 100644 crates/mitki-benchmark/benches/parser.rs delete mode 100644 crates/mitki-benchmark/benches/tokenizer.rs delete mode 100644 crates/mitki-benchmark/src/lib.rs delete mode 100644 crates/mitki-codegen-core/Cargo.toml delete mode 100644 crates/mitki-codegen-core/src/capability.rs delete mode 100644 crates/mitki-codegen-core/src/classify.rs delete mode 100644 crates/mitki-codegen-core/src/descriptor.rs delete mode 100644 crates/mitki-codegen-core/src/layout.rs delete mode 100644 crates/mitki-codegen-core/src/lib.rs delete mode 100644 crates/mitki-codegen-core/src/runtime_rep.rs delete mode 100644 crates/mitki-codegen-core/src/stage_intrinsic.rs delete mode 100644 crates/mitki-comptime-wasm/Cargo.toml delete mode 100644 crates/mitki-comptime-wasm/src/lib.rs delete mode 100644 crates/mitki-comptime-wasm/src/stage.rs delete mode 100644 crates/mitki-comptime-wasm/src/stage_runtime.rs delete mode 100644 crates/mitki-db/Cargo.toml delete mode 100644 crates/mitki-db/src/lib.rs delete mode 100644 crates/mitki-db/tests/modules.rs delete mode 100644 crates/mitki-db/tests/typeck.rs delete mode 100644 crates/mitki-errors/Cargo.toml delete mode 100644 crates/mitki-errors/src/lib.rs delete mode 100644 crates/mitki-hir-macros/Cargo.toml delete mode 100644 crates/mitki-hir-macros/src/lib.rs delete mode 100644 crates/mitki-hir/Cargo.toml delete mode 100644 crates/mitki-hir/src/arena.rs delete mode 100644 crates/mitki-hir/src/hir.rs delete mode 100644 crates/mitki-hir/src/hir/function.rs delete mode 100644 crates/mitki-hir/src/hir/id.rs delete mode 100644 crates/mitki-hir/src/hir/schema.rs delete mode 100644 crates/mitki-hir/src/hir/store.rs delete mode 100644 crates/mitki-hir/src/lib.rs delete mode 100644 crates/mitki-hir/src/ty.rs delete mode 100644 crates/mitki-hir/tests/hir_roundtrip.rs delete mode 100644 crates/mitki-hir/tests/trybuild.rs delete mode 100644 crates/mitki-hir/tests/trybuild/id_mixup.rs delete mode 100644 crates/mitki-hir/tests/trybuild/id_mixup.stderr delete mode 100644 crates/mitki-ide/Cargo.toml delete mode 100644 crates/mitki-ide/src/analysis.rs delete mode 100644 crates/mitki-ide/src/analysis/goto_definition.rs delete mode 100644 crates/mitki-ide/src/analysis/hover.rs delete mode 100644 crates/mitki-ide/src/analysis/inlay_hints.rs delete mode 100644 crates/mitki-ide/src/analysis/semantic_tokens.rs delete mode 100644 crates/mitki-ide/src/lib.rs delete mode 100644 crates/mitki-inputs/Cargo.toml delete mode 100644 crates/mitki-inputs/src/lib.rs delete mode 100644 crates/mitki-lower/Cargo.toml delete mode 100644 crates/mitki-lower/src/ast_map.rs delete mode 100644 crates/mitki-lower/src/hir.rs delete mode 100644 crates/mitki-lower/src/hir/function.rs delete mode 100644 crates/mitki-lower/src/item.rs delete mode 100644 crates/mitki-lower/src/item/package.rs delete mode 100644 crates/mitki-lower/src/item/scope.rs delete mode 100644 crates/mitki-lower/src/item/stdlib.rs delete mode 100644 crates/mitki-lower/src/item/tree.rs delete mode 100644 crates/mitki-lower/src/lib.rs delete mode 100644 crates/mitki-lsp-server/Cargo.toml delete mode 100644 crates/mitki-lsp-server/src/api.rs delete mode 100644 crates/mitki-lsp-server/src/lib.rs delete mode 100644 crates/mitki-lsp-server/src/notifications.rs delete mode 100644 crates/mitki-lsp-server/src/requests.rs delete mode 100644 crates/mitki-parse/Cargo.toml delete mode 100644 crates/mitki-parse/src/grammar.rs delete mode 100644 crates/mitki-parse/src/grammar/exprs.rs delete mode 100644 crates/mitki-parse/src/grammar/items.rs delete mode 100644 crates/mitki-parse/src/grammar/patterns.rs delete mode 100644 crates/mitki-parse/src/grammar/types.rs delete mode 100644 crates/mitki-parse/src/lib.rs delete mode 100644 crates/mitki-parse/src/parser.rs delete mode 100644 crates/mitki-parse/src/tests.rs delete mode 100644 crates/mitki-parse/test_data/anon_record_expr.ir delete mode 100644 crates/mitki-parse/test_data/anon_record_expr.mitki delete mode 100644 crates/mitki-parse/test_data/anon_record_single_field.ir delete mode 100644 crates/mitki-parse/test_data/anon_record_single_field.mitki delete mode 100644 crates/mitki-parse/test_data/array_expr.ir delete mode 100644 crates/mitki-parse/test_data/array_expr.mitki delete mode 100644 crates/mitki-parse/test_data/call_expr.ir delete mode 100644 crates/mitki-parse/test_data/call_expr.mitki delete mode 100644 crates/mitki-parse/test_data/char_literal.ir delete mode 100644 crates/mitki-parse/test_data/char_literal.mitki delete mode 100644 crates/mitki-parse/test_data/closure_params.ir delete mode 100644 crates/mitki-parse/test_data/closure_params.mitki delete mode 100644 crates/mitki-parse/test_data/comptime_function.ir delete mode 100644 crates/mitki-parse/test_data/comptime_function.mitki delete mode 100644 crates/mitki-parse/test_data/curly_in_params.ir delete mode 100644 crates/mitki-parse/test_data/curly_in_params.mitki delete mode 100644 crates/mitki-parse/test_data/destructor_member.ir delete mode 100644 crates/mitki-parse/test_data/destructor_member.mitki delete mode 100644 crates/mitki-parse/test_data/empty.ir delete mode 100644 crates/mitki-parse/test_data/empty.mitki delete mode 100644 crates/mitki-parse/test_data/empty_braces_closure.ir delete mode 100644 crates/mitki-parse/test_data/empty_braces_closure.mitki delete mode 100644 crates/mitki-parse/test_data/enum_def.ir delete mode 100644 crates/mitki-parse/test_data/enum_def.mitki delete mode 100644 crates/mitki-parse/test_data/enum_with_data.ir delete mode 100644 crates/mitki-parse/test_data/enum_with_data.mitki delete mode 100644 crates/mitki-parse/test_data/field_expr.ir delete mode 100644 crates/mitki-parse/test_data/field_expr.mitki delete mode 100644 crates/mitki-parse/test_data/function.ir delete mode 100644 crates/mitki-parse/test_data/function.mitki delete mode 100644 crates/mitki-parse/test_data/function_ret_type.ir delete mode 100644 crates/mitki-parse/test_data/function_ret_type.mitki delete mode 100644 crates/mitki-parse/test_data/generic_param_list.ir delete mode 100644 crates/mitki-parse/test_data/generic_param_list.mitki delete mode 100644 crates/mitki-parse/test_data/generic_type_path.ir delete mode 100644 crates/mitki-parse/test_data/generic_type_path.mitki delete mode 100644 crates/mitki-parse/test_data/if_block_after_path.ir delete mode 100644 crates/mitki-parse/test_data/if_block_after_path.mitki delete mode 100644 crates/mitki-parse/test_data/if_expr.ir delete mode 100644 crates/mitki-parse/test_data/if_expr.mitki delete mode 100644 crates/mitki-parse/test_data/instance_item.ir delete mode 100644 crates/mitki-parse/test_data/instance_item.mitki delete mode 100644 crates/mitki-parse/test_data/loop_control.ir delete mode 100644 crates/mitki-parse/test_data/loop_control.mitki delete mode 100644 crates/mitki-parse/test_data/loop_expr.ir delete mode 100644 crates/mitki-parse/test_data/loop_expr.mitki delete mode 100644 crates/mitki-parse/test_data/match_expr.ir delete mode 100644 crates/mitki-parse/test_data/match_expr.mitki delete mode 100644 crates/mitki-parse/test_data/match_malformed.ir delete mode 100644 crates/mitki-parse/test_data/match_malformed.mitki delete mode 100644 crates/mitki-parse/test_data/match_typed_pattern.ir delete mode 100644 crates/mitki-parse/test_data/match_typed_pattern.mitki delete mode 100644 crates/mitki-parse/test_data/missing_fn_param_type.ir delete mode 100644 crates/mitki-parse/test_data/missing_fn_param_type.mitki delete mode 100644 crates/mitki-parse/test_data/module_item.ir delete mode 100644 crates/mitki-parse/test_data/module_item.mitki delete mode 100644 crates/mitki-parse/test_data/module_syntax.ir delete mode 100644 crates/mitki-parse/test_data/module_syntax.mitki delete mode 100644 crates/mitki-parse/test_data/mutable_assignment.ir delete mode 100644 crates/mitki-parse/test_data/mutable_assignment.mitki delete mode 100644 crates/mitki-parse/test_data/mutable_local.ir delete mode 100644 crates/mitki-parse/test_data/mutable_local.mitki delete mode 100644 crates/mitki-parse/test_data/named_struct_empty_fields.ir delete mode 100644 crates/mitki-parse/test_data/named_struct_empty_fields.mitki delete mode 100644 crates/mitki-parse/test_data/param_list.ir delete mode 100644 crates/mitki-parse/test_data/param_list.mitki delete mode 100644 crates/mitki-parse/test_data/pattern_params.ir delete mode 100644 crates/mitki-parse/test_data/pattern_params.mitki delete mode 100644 crates/mitki-parse/test_data/qualified_path.ir delete mode 100644 crates/mitki-parse/test_data/qualified_path.mitki delete mode 100644 crates/mitki-parse/test_data/return.ir delete mode 100644 crates/mitki-parse/test_data/return.mitki delete mode 100644 crates/mitki-parse/test_data/std_alloc_module.ir delete mode 100644 crates/mitki-parse/test_data/std_alloc_module.mitki delete mode 100644 crates/mitki-parse/test_data/string_literal.ir delete mode 100644 crates/mitki-parse/test_data/string_literal.mitki delete mode 100644 crates/mitki-parse/test_data/struct_def.ir delete mode 100644 crates/mitki-parse/test_data/struct_def.mitki delete mode 100644 crates/mitki-parse/test_data/struct_expr.ir delete mode 100644 crates/mitki-parse/test_data/struct_expr.mitki delete mode 100644 crates/mitki-parse/test_data/struct_expr_shorthand.ir delete mode 100644 crates/mitki-parse/test_data/struct_expr_shorthand.mitki delete mode 100644 crates/mitki-parse/test_data/tuple_expr.ir delete mode 100644 crates/mitki-parse/test_data/tuple_expr.mitki delete mode 100644 crates/mitki-parse/test_data/unary_expr.ir delete mode 100644 crates/mitki-parse/test_data/unary_expr.mitki delete mode 100644 crates/mitki-parse/test_data/val_stmt.ir delete mode 100644 crates/mitki-parse/test_data/val_stmt.mitki delete mode 100644 crates/mitki-parse/test_data/wasm_import_export_function.ir delete mode 100644 crates/mitki-parse/test_data/wasm_import_export_function.mitki delete mode 100644 crates/mitki-resolve/Cargo.toml delete mode 100644 crates/mitki-resolve/src/compiler_intrinsics.rs delete mode 100644 crates/mitki-resolve/src/lib.rs delete mode 100644 crates/mitki-resolve/src/resolver.rs delete mode 100644 crates/mitki-resolve/src/runtime.rs delete mode 100644 crates/mitki-resolve/src/scope.rs delete mode 100644 crates/mitki-resolve/src/signature.rs delete mode 100644 crates/mitki-span/Cargo.toml delete mode 100644 crates/mitki-span/src/lib.rs delete mode 100644 crates/mitki-span/src/symbol.rs delete mode 100644 crates/mitki-tokenizer/Cargo.toml delete mode 100644 crates/mitki-tokenizer/src/cursor.rs delete mode 100644 crates/mitki-tokenizer/src/lib.rs delete mode 100644 crates/mitki-typeck/Cargo.toml delete mode 100644 crates/mitki-typeck/src/infer.rs delete mode 100644 crates/mitki-typeck/src/lib.rs delete mode 100644 crates/mitki-wasm-runtime/Cargo.toml delete mode 100644 crates/mitki-wasm-runtime/examples/inspect_and_invoke.rs delete mode 100644 crates/mitki-wasm-runtime/inspect_and_invoke.rs delete mode 100644 crates/mitki-wasm-runtime/src/lib.rs delete mode 100644 crates/mitki-wasm-runtime/src/runtime.rs delete mode 100644 crates/mitki-wasm-runtime/src/runtime_v2.rs delete mode 100644 crates/mitki-yellow/Cargo.toml delete mode 100644 crates/mitki-yellow/src/ast.rs delete mode 100644 crates/mitki-yellow/src/builder.rs delete mode 100644 crates/mitki-yellow/src/lib.rs delete mode 100644 crates/mitki-yellow/src/maybe_dangling.rs delete mode 100644 crates/mitki-yellow/src/nodes.rs delete mode 100644 crates/mitki-yellow/src/nodes/node.rs delete mode 100644 crates/mitki-yellow/src/nodes/token.rs delete mode 100644 crates/mitki-yellow/src/nodes/tree.rs delete mode 100644 crates/mitki-yellow/src/syntax.rs delete mode 100644 crates/mitki-yellow/src/syntax_kind.rs delete mode 100644 crates/mitki-yellow/src/syntax_set.rs delete mode 100644 crates/mitki-yellow/src/trivia.rs delete mode 100644 crates/mitki/Cargo.toml delete mode 100644 crates/mitki/src/main.rs delete mode 100644 crates/mitki/tests/cli_wasm.rs delete mode 100644 deny.toml delete mode 100644 rust-toolchain.toml delete mode 100644 rustfmt.toml delete mode 100644 stdlib/src/alloc.mitki delete mode 100644 stdlib/src/alloc/int.mitki delete mode 100644 stdlib/src/env.mitki delete mode 100644 stdlib/src/io.mitki delete mode 100644 stdlib/src/lexer.mitki delete mode 100644 stdlib/src/lib.mitki delete mode 100644 stdlib/src/str.mitki delete mode 100644 stdlib/src/vec.mitki delete mode 100644 stdlib/src/vec/int.mitki diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml deleted file mode 100644 index 65185f1..0000000 --- a/.github/workflows/dependencies.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Weekly cargo update -on: - schedule: - - cron: '0 0 * * Sun' - workflow_dispatch: -permissions: - contents: read -defaults: - run: - shell: bash -env: - PR_TITLE: Weekly `cargo update` - PR_MESSAGE: | - Automation to keep dependencies in `Cargo.lock` current. - - The following is the output from `cargo update`: - COMMIT_MESSAGE: "cargo update \n\n" - -jobs: - update: - name: update dependencies - runs-on: ubuntu-latest - env: - RUST_CHANNEL: nightly - steps: - - name: checkout the source code - uses: actions/checkout@v5 - - uses: Swatinem/rust-cache@v2 - - name: Install Rust toolchain - run: | - rustup update --no-self-update ${{ env.RUST_CHANNEL }} - rustup default ${{ env.RUST_CHANNEL }} - - name: cargo update - run: cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log - - name: upload Cargo.lock artifact for use in PR - uses: actions/upload-artifact@v4 - with: - name: Cargo-lock - path: Cargo.lock - retention-days: 1 - - name: upload cargo-update log artifact for use in PR - uses: actions/upload-artifact@v4 - with: - name: cargo-updates - path: cargo_update.log - retention-days: 1 - - pr: - name: amend PR - needs: update - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - name: checkout the source code - uses: actions/checkout@v5 - - - name: download Cargo.lock from update job - uses: actions/download-artifact@v4 - with: - name: Cargo-lock - - name: download cargo-update log from update job - uses: actions/download-artifact@v4 - with: - name: cargo-updates - - - name: craft PR body and commit message - run: | - echo "${COMMIT_MESSAGE}" > commit.txt - cat cargo_update.log >> commit.txt - - echo "${PR_MESSAGE}" > body.md - echo '```txt' >> body.md - cat cargo_update.log >> body.md - echo '```' >> body.md - - - name: commit - run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - git switch --force-create cargo_update - git add ./Cargo.lock - git commit --no-verify --file=commit.txt - - - name: push - run: git push --no-verify --force --set-upstream origin cargo_update - - - name: edit existing open pull request - id: edit - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - STATE=$(gh pr view cargo_update --repo $GITHUB_REPOSITORY --json state --jq '.state') - if [[ "$STATE" != "OPEN" ]]; then - exit 1 - fi - - gh pr edit cargo_update --title "${PR_TITLE}" --body-file body.md --repo $GITHUB_REPOSITORY - - - name: open new pull request - if: steps.edit.outcome != 'success' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh pr create --title "${PR_TITLE}" --body-file body.md --repo $GITHUB_REPOSITORY diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 93321b4..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Rust -on: - push: - branches: ["master"] - merge_group: - pull_request: - workflow_dispatch: -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - CARGO_NET_RETRY: 10 - RUST_BACKTRACE: short - RUSTFLAGS: "-D warnings" - RUSTUP_MAX_RETRIES: 10 -jobs: - rust: - runs-on: ubuntu-latest - env: - RUST_CHANNEL: nightly - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - name: Install Rust toolchain - run: | - rustup update --no-self-update ${{ env.RUST_CHANNEL }} - rustup default ${{ env.RUST_CHANNEL }} - rustup component add --toolchain ${{ env.RUST_CHANNEL }} rustfmt clippy - - name: Cache Dependencies - uses: Swatinem/rust-cache@v2 - with: - key: ${{ env.RUST_CHANNEL }} - - name: cargo build - run: cargo build --quiet - - name: cargo clippy - run: cargo clippy --quiet - - name: cargo test - run: cargo test -- --nocapture --quiet - - name: cargo fmt --check - run: cargo fmt --check - unused_dependencies: - runs-on: ubuntu-latest - env: - RUST_CHANNEL: nightly - steps: - - uses: actions/checkout@v5 - - name: Install Rust toolchain - run: | - rustup update --no-self-update ${{ env.RUST_CHANNEL }} - rustup default ${{ env.RUST_CHANNEL }} - - name: install cargo-udeps - uses: taiki-e/install-action@cargo-udeps - - name: cargo udeps - run: cargo udeps - cargo-deny: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: EmbarkStudios/cargo-deny-action@v2 - benchmarks: - if: github.event_name != 'merge_group' - name: Benchmarks - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - - name: Setup Rust toolchain - uses: dtolnay/rust-toolchain@master - id: rust-toolchain - with: - toolchain: stable - - name: "Setup codspeed" - uses: taiki-e/install-action@v2 - with: - tool: cargo-codspeed - - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.toml') }} - restore-keys: | - ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}- - ${{ runner.os }}-cargo- - - name: "Build benchmarks" - run: cargo codspeed build - - name: "Run benchmarks" - uses: CodSpeedHQ/action@v4 - with: - mode: instrumentation - run: cargo codspeed run - token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index cc8af1c..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,51 +0,0 @@ -# Mitki Contribution Guide - -This document explains how to contribute changes to the Mitki repository. - -## Project Structure for OpenAI Codex Navigation - -- `crates/` - workspace crates for the language and tools. Each subdirectory - contains a Rust crate implementing part of the compiler or its tooling, such - as the parser, analysis engine, or language server. Add new crates here when - extending Mitki's functionality. -- `crates/mitki` - command line interface providing `run` and `lsp` subcommands. -- `crates/mitki-analysis` - semantic analysis including type inference and HIR. -- `crates/mitki-benchmark` - micro-benchmarks for parser and IDE features. -- `crates/mitki-db` - salsa database wiring all compiler components together. -- `crates/mitki-errors` - utilities for emitting diagnostics and errors. -- `crates/mitki-ide` - higher level APIs used by the LSP and tools. -- `crates/mitki-inputs` - representation of input files and line indexing. -- `crates/mitki-lsp-server` - Language Server Protocol implementation. -- `crates/mitki-parse` - grammar parser building syntax trees. -- `crates/mitki-span` - interned symbols used throughout the compiler. -- `crates/mitki-tokenizer` - lexical tokenizer producing syntax tokens. -- `crates/mitki-yellow` - green/red tree representation for syntax nodes. -- `target/` - build artifacts (not committed) -- `Cargo.toml` and `Cargo.lock` - workspace manifests -- `README.md` - overview of the project - - -## Required checks - -Before committing, ensure the following commands succeed: - -```bash -cargo fmt --all -- --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace -``` - -If formatting or linting fail because the tools are missing, install them with: - -```bash -rustup component add rustfmt clippy -``` - -Commit code only after these checks pass. - -## Pull request guidelines - -- Provide a clear description of your changes. -- Reference relevant issues when applicable. -- Keep each pull request focused on a single topic. - diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index b7a82f7..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,4001 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9698bf0769c641b18618039fe2ebd41eb3541f98433000f64e663fab7cea2c87" -dependencies = [ - "gimli", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "ambient-authority" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "annotate-snippets" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" -dependencies = [ - "anstyle", - "unicode-width", -] - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "assert_cmd" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - -[[package]] -name = "blake3" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "boxcar" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" - -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" -dependencies = [ - "allocator-api2", -] - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" - -[[package]] -name = "cap-fs-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" -dependencies = [ - "cap-primitives", - "cap-std", - "io-lifetimes", - "windows-sys 0.59.0", -] - -[[package]] -name = "cap-net-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" -dependencies = [ - "cap-primitives", - "cap-std", - "rustix 1.1.4", - "smallvec", -] - -[[package]] -name = "cap-primitives" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" -dependencies = [ - "ambient-authority", - "fs-set-times", - "io-extras", - "io-lifetimes", - "ipnet", - "maybe-owned", - "rustix 1.1.4", - "rustix-linux-procfs", - "windows-sys 0.59.0", - "winx", -] - -[[package]] -name = "cap-rand" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" -dependencies = [ - "ambient-authority", - "rand 0.8.5", -] - -[[package]] -name = "cap-std" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" -dependencies = [ - "cap-primitives", - "io-extras", - "io-lifetimes", - "rustix 1.1.4", -] - -[[package]] -name = "cap-time-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" -dependencies = [ - "ambient-authority", - "cap-primitives", - "iana-time-zone", - "once_cell", - "rustix 1.1.4", - "winx", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" - -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.18", -] - -[[package]] -name = "codspeed" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c2eb3388ebe26b5a0ab6bf4969d9c4840143d7f6df07caa3cc851b0606cef6" -dependencies = [ - "anyhow", - "cc", - "colored", - "getrandom 0.2.17", - "glob", - "libc", - "nix", - "serde", - "serde_json", - "statrs", -] - -[[package]] -name = "codspeed-criterion-compat" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e270597a1d1e183f86d1cc9f94f0133654ee3daf201c17903ee29363555dd7" -dependencies = [ - "clap", - "codspeed", - "codspeed-criterion-compat-walltime", - "colored", - "regex", -] - -[[package]] -name = "codspeed-criterion-compat-walltime" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c2613d2fac930fe34456be76f9124ee0800bb9db2e7fd2d6c65b9ebe98a292" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "codspeed", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpp_demangle" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cranelift-assembler-x64" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40630d663279bc855bff805d6f5e8a0b6a1867f9df95b010511ac6dc894e9395" -dependencies = [ - "cranelift-assembler-x64-meta", -] - -[[package]] -name = "cranelift-assembler-x64-meta" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee6aec5ceb55e5fdbcf7ef677d7c7195531360ff181ce39b2b31df11d57305f" -dependencies = [ - "cranelift-srcgen", -] - -[[package]] -name = "cranelift-bforest" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a92d78cc3f087d7e7073828f08d98c7074a3a062b6b29a1b7783ce74305685e" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-bitset" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcc73d756f2e0d7eda6144fe64a2bc69c624de893cb1be51f1442aed77881d2" -dependencies = [ - "serde", - "serde_derive", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-codegen" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d94c2cd0d73b41369b88da1129589bc3a2d99cf49979af1d14751f35b7a1b" -dependencies = [ - "bumpalo", - "cranelift-assembler-x64", - "cranelift-bforest", - "cranelift-bitset", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-control", - "cranelift-entity", - "cranelift-isle", - "gimli", - "hashbrown 0.15.5", - "libm", - "log", - "pulley-interpreter", - "regalloc2", - "rustc-hash", - "serde", - "smallvec", - "target-lexicon", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235da0e52ee3a0052d0e944c3470ff025b1f4234f6ec4089d3109f2d2ffa6cbd" -dependencies = [ - "cranelift-assembler-x64-meta", - "cranelift-codegen-shared", - "cranelift-srcgen", - "heck", - "pulley-interpreter", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c07c6c440bd1bf920ff7597a1e743ede1f68dcd400730bd6d389effa7662af" - -[[package]] -name = "cranelift-control" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8797c022e02521901e1aee483dea3ed3c67f2bf0a26405c9dd48e8ee7a70944b" -dependencies = [ - "arbitrary", -] - -[[package]] -name = "cranelift-entity" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d8e72637246edd2cba337939850caa8b201f6315925ec4c156fdd089999699" -dependencies = [ - "cranelift-bitset", - "serde", - "serde_derive", - "wasmtime-internal-core", -] - -[[package]] -name = "cranelift-frontend" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c31db0085c3dfa131e739c3b26f9f9c84d69a9459627aac1ac4ef8355e3411b" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524d804c1ebd8c542e6f64e71aa36934cec17c5da4a9ae3799796220317f5d23" - -[[package]] -name = "cranelift-native" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9598f02540e382e1772416eba18e93c5275b746adbbf06ac1f3cf149415270" -dependencies = [ - "cranelift-codegen", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-srcgen" -version = "0.129.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d953932541249c91e3fa70a75ff1e52adc62979a2a8132145d4b9b3e6d1a9b6a" - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "uuid", -] - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dissimilar" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" - -[[package]] -name = "drop_bomb" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" -dependencies = [ - "env_filter", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "expect-test" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" -dependencies = [ - "dissimilar", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix 1.1.4", - "windows-sys 0.59.0", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fluent-uri" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs-set-times" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" -dependencies = [ - "io-lifetimes", - "rustix 1.1.4", - "windows-sys 0.59.0", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "fxprof-processed-profile" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" -dependencies = [ - "bitflags 2.11.0", - "debugid", - "rustc-hash", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "rand_core 0.10.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "gimli" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" -dependencies = [ - "fnv", - "hashbrown 0.16.1", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.1.5", - "serde", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", - "serde", - "serde_core", -] - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "im-rc" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" -dependencies = [ - "bitmaps", - "rand_core 0.6.4", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "intrusive-collections" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" -dependencies = [ - "memoffset", -] - -[[package]] -name = "inventory" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" -dependencies = [ - "rustversion", -] - -[[package]] -name = "io-extras" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" -dependencies = [ - "io-lifetimes", - "windows-sys 0.59.0", -] - -[[package]] -name = "io-lifetimes" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "ittapi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" -dependencies = [ - "anyhow", - "ittapi-sys", - "log", -] - -[[package]] -name = "ittapi-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" -dependencies = [ - "cc", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.182" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libmimalloc-sys" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "libc", -] - -[[package]] -name = "line-index" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e27e0ed5a392a7f5ba0b3808a2afccff16c64933312c84b57618b49d1209bd2" -dependencies = [ - "nohash-hasher", - "text-size", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lsp-server" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e" -dependencies = [ - "crossbeam-channel", - "log", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "lsp-types" -version = "0.97.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" -dependencies = [ - "bitflags 1.3.2", - "fluent-uri", - "serde", - "serde_json", - "serde_repr", -] - -[[package]] -name = "mach2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] - -[[package]] -name = "maybe-owned" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memfd" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" -dependencies = [ - "rustix 1.1.4", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mimalloc" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" -dependencies = [ - "libmimalloc-sys", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "mitki" -version = "0.1.0" -dependencies = [ - "anyhow", - "assert_cmd", - "camino", - "clap", - "mimalloc", - "mitki-abi", - "mitki-comptime-wasm", - "mitki-db", - "mitki-errors", - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-lsp-server", - "mitki-resolve", - "mitki-typeck", - "mitki-wasm-runtime", - "mitki-yellow", - "predicates", - "salsa", - "tempfile", - "wasmparser 0.245.1", -] - -[[package]] -name = "mitki-abi" -version = "0.1.0" -dependencies = [ - "anyhow", - "blake3", - "expect-test", - "wasmparser 0.245.1", -] - -[[package]] -name = "mitki-abi-lower" -version = "0.1.0" -dependencies = [ - "anyhow", - "mitki-abi", - "mitki-db", - "mitki-hir", - "mitki-lower", - "mitki-span", - "salsa", - "serde", -] - -[[package]] -name = "mitki-analysis" -version = "0.1.0" -dependencies = [ - "mitki-db", - "mitki-errors", - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-parse", - "mitki-resolve", - "mitki-span", - "mitki-typeck", - "mitki-yellow", - "patmat", - "rustc-hash", - "salsa", - "text-size", -] - -[[package]] -name = "mitki-backend-wasm" -version = "0.1.0" -dependencies = [ - "anyhow", - "expect-test", - "mitki-abi", - "mitki-abi-lower", - "mitki-analysis", - "mitki-codegen-core", - "mitki-comptime-wasm", - "mitki-db", - "mitki-errors", - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-parse", - "mitki-resolve", - "mitki-span", - "mitki-typeck", - "mitki-wasm-runtime", - "mitki-yellow", - "rustc-hash", - "salsa", - "serde", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", - "wasmprinter 0.245.1", - "wasmtime", -] - -[[package]] -name = "mitki-benchmark" -version = "0.1.0" -dependencies = [ - "codspeed-criterion-compat", - "mitki-analysis", - "mitki-ide", - "mitki-inputs", - "mitki-parse", - "mitki-tokenizer", - "mitki-yellow", - "salsa", - "text-size", -] - -[[package]] -name = "mitki-codegen-core" -version = "0.1.0" -dependencies = [ - "mitki-hir", - "mitki-lower", - "mitki-resolve", - "mitki-span", - "rustc-hash", - "salsa", -] - -[[package]] -name = "mitki-comptime-wasm" -version = "0.1.0" -dependencies = [ - "anyhow", - "mitki-abi", - "mitki-analysis", - "mitki-backend-wasm", - "mitki-codegen-core", - "mitki-db", - "mitki-errors", - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-typeck", - "mitki-wasm-runtime", - "rustc-hash", - "salsa", - "wasmtime", -] - -[[package]] -name = "mitki-db" -version = "0.1.0" -dependencies = [ - "mitki-analysis", - "mitki-errors", - "mitki-inputs", - "mitki-lower", - "salsa", - "tempfile", -] - -[[package]] -name = "mitki-errors" -version = "0.1.0" -dependencies = [ - "annotate-snippets", - "salsa", - "text-size", -] - -[[package]] -name = "mitki-hir" -version = "0.1.0" -dependencies = [ - "mitki-hir-macros", - "mitki-inputs", - "mitki-span", - "salsa", - "trybuild", -] - -[[package]] -name = "mitki-hir-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "mitki-ide" -version = "0.1.0" -dependencies = [ - "mitki-analysis", - "mitki-db", - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-parse", - "mitki-resolve", - "mitki-span", - "mitki-typeck", - "mitki-yellow", - "salsa", - "text-size", -] - -[[package]] -name = "mitki-inputs" -version = "0.1.0" -dependencies = [ - "camino", - "line-index", - "salsa", -] - -[[package]] -name = "mitki-lower" -version = "0.1.0" -dependencies = [ - "camino", - "hashbrown 0.15.5", - "indexmap", - "mitki-hir", - "mitki-inputs", - "mitki-parse", - "mitki-span", - "mitki-yellow", - "rustc-hash", - "salsa", -] - -[[package]] -name = "mitki-lsp-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "lsp-server", - "lsp-types", - "mitki-db", - "mitki-ide", - "mitki-inputs", - "salsa", - "serde", - "serde_json", - "text-size", -] - -[[package]] -name = "mitki-parse" -version = "0.1.0" -dependencies = [ - "drop_bomb", - "expect-test", - "mitki-errors", - "mitki-inputs", - "mitki-tokenizer", - "mitki-yellow", - "salsa", - "text-size", -] - -[[package]] -name = "mitki-resolve" -version = "0.1.0" -dependencies = [ - "mitki-hir", - "mitki-inputs", - "mitki-lower", - "mitki-span", - "rustc-hash", - "salsa", -] - -[[package]] -name = "mitki-span" -version = "0.1.0" -dependencies = [ - "salsa", -] - -[[package]] -name = "mitki-tokenizer" -version = "0.1.0" -dependencies = [ - "ascii", - "memchr", - "mitki-yellow", - "quickcheck", - "text-size", -] - -[[package]] -name = "mitki-typeck" -version = "0.1.0" -dependencies = [ - "mitki-hir", - "mitki-lower", - "mitki-resolve", - "mitki-span", - "rustc-hash", - "salsa", -] - -[[package]] -name = "mitki-wasm-runtime" -version = "0.1.0" -dependencies = [ - "anyhow", - "mitki-abi", - "mitki-comptime-wasm", - "mitki-db", - "mitki-inputs", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", - "wasmtime", - "wasmtime-wasi", -] - -[[package]] -name = "mitki-yellow" -version = "0.1.0" -dependencies = [ - "salsa", - "text-size", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "crc32fast", - "hashbrown 0.15.5", - "indexmap", - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "patmat" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf6d0b888cd93a2daf48713a085d4edd85a31f1b528195b2e00d83a7153cc47" -dependencies = [ - "foldhash 0.2.0", - "hashbrown 0.16.1", - "indexmap", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", -] - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulley-interpreter" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2d61e068654529dc196437f8df0981db93687fdc67dec6a5de92363120b9da" -dependencies = [ - "cranelift-bitset", - "log", - "pulley-macros", - "wasmtime-internal-core", -] - -[[package]] -name = "pulley-macros" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f210c61b6ecfaebbba806b6d9113a222519d4e5cc4ab2d5ecca047bb7927ae" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quickcheck" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c589f335db0f6aaa168a7cd27b1fc6920f5e1470c804f814d9cd6e62a0f70b" -dependencies = [ - "env_logger", - "log", - "rand 0.10.0", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" -dependencies = [ - "getrandom 0.4.1", - "rand_core 0.10.0", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "regalloc2" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" -dependencies = [ - "allocator-api2", - "bumpalo", - "hashbrown 0.15.5", - "log", - "rustc-hash", - "smallvec", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustix-linux-procfs" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" -dependencies = [ - "once_cell", - "rustix 1.1.4", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "salsa" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77debccd43ba198e9cee23efd7f10330ff445e46a98a2b107fed9094a1ee676" -dependencies = [ - "boxcar", - "crossbeam-queue", - "crossbeam-utils", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "intrusive-collections", - "inventory", - "parking_lot", - "portable-atomic", - "rustc-hash", - "salsa-macro-rules", - "salsa-macros", - "smallvec", - "thin-vec", - "tracing", -] - -[[package]] -name = "salsa-macro-rules" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea07adbf42d91cc076b7daf3b38bc8168c19eb362c665964118a89bc55ef19a5" - -[[package]] -name = "salsa-macros" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d4d8b66451b9c75ddf740b7fc8399bc7b8ba33e854a5d7526d18708f67b05" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "statrs" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" -dependencies = [ - "approx", - "num-traits", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-interface" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" -dependencies = [ - "bitflags 2.11.0", - "cap-fs-ext", - "cap-std", - "fd-lock", - "io-lifetimes", - "rustix 0.38.44", - "windows-sys 0.59.0", - "winx", -] - -[[package]] -name = "target-lexicon" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" - -[[package]] -name = "target-triple" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.1", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "text-size" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" - -[[package]] -name = "thin-vec" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml" -version = "1.0.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 1.0.0+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_datetime" -version = "1.0.0+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.0.9+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "trybuild" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml 1.0.3+spec-1.1.0", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-compose" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92cda9c76ca8dcac01a8b497860c2cb15cd6f216dc07060517df5abbe82512ac" -dependencies = [ - "anyhow", - "heck", - "im-rc", - "indexmap", - "log", - "petgraph", - "serde", - "serde_derive", - "serde_yaml", - "smallvec", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", - "wat", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" -dependencies = [ - "leb128fmt", - "wasmparser 0.245.1", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", - "serde", -] - -[[package]] -name = "wasmparser" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.16.1", - "indexmap", - "semver", - "serde", -] - -[[package]] -name = "wasmprinter" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09390d7b2bd7b938e563e4bff10aa345ef2e27a3bc99135697514ef54495e68f" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.244.0", -] - -[[package]] -name = "wasmprinter" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.245.1", -] - -[[package]] -name = "wasmtime" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39bef52be4fb4c5b47d36f847172e896bc94b35c9c6a6f07117686bd16ed89a7" -dependencies = [ - "addr2line", - "async-trait", - "bitflags 2.11.0", - "bumpalo", - "cc", - "cfg-if", - "encoding_rs", - "futures", - "fxprof-processed-profile", - "gimli", - "ittapi", - "libc", - "log", - "mach2", - "memfd", - "object", - "once_cell", - "postcard", - "pulley-interpreter", - "rayon", - "rustix 1.1.4", - "semver", - "serde", - "serde_derive", - "serde_json", - "smallvec", - "target-lexicon", - "tempfile", - "wasm-compose", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", - "wasmtime-environ", - "wasmtime-internal-cache", - "wasmtime-internal-component-macro", - "wasmtime-internal-component-util", - "wasmtime-internal-core", - "wasmtime-internal-cranelift", - "wasmtime-internal-fiber", - "wasmtime-internal-jit-debug", - "wasmtime-internal-jit-icache-coherence", - "wasmtime-internal-unwinder", - "wasmtime-internal-versioned-export-macros", - "wasmtime-internal-winch", - "wat", - "windows-sys 0.61.2", -] - -[[package]] -name = "wasmtime-environ" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb637d5aa960ac391ca5a4cbf3e45807632e56beceeeb530e14dfa67fdfccc62" -dependencies = [ - "anyhow", - "cpp_demangle", - "cranelift-bitset", - "cranelift-entity", - "gimli", - "hashbrown 0.15.5", - "indexmap", - "log", - "object", - "postcard", - "rustc-demangle", - "semver", - "serde", - "serde_derive", - "smallvec", - "target-lexicon", - "wasm-encoder 0.244.0", - "wasmparser 0.244.0", - "wasmprinter 0.244.0", - "wasmtime-internal-component-util", - "wasmtime-internal-core", -] - -[[package]] -name = "wasmtime-internal-cache" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab6c428c610ae3e7acd25ca2681b4d23672c50d8769240d9dda99b751d4deec" -dependencies = [ - "base64", - "directories-next", - "log", - "postcard", - "rustix 1.1.4", - "serde", - "serde_derive", - "sha2", - "toml 0.9.12+spec-1.1.0", - "wasmtime-environ", - "windows-sys 0.61.2", - "zstd", -] - -[[package]] -name = "wasmtime-internal-component-macro" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca768b11d5e7de017e8c3d4d444da6b4ce3906f565bcbc253d76b4ecbb5d2869" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn", - "wasmtime-internal-component-util", - "wasmtime-internal-wit-bindgen", - "wit-parser", -] - -[[package]] -name = "wasmtime-internal-component-util" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f504faf96c9b409051e96a1434655eea7f56a90bed9cb1e22e22c941253fd" - -[[package]] -name = "wasmtime-internal-core" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a4a3f055a804a2f3d86e816a9df78a8fa57762212a8506164959224a40cd48" -dependencies = [ - "anyhow", - "libm", -] - -[[package]] -name = "wasmtime-internal-cranelift" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55154a91d22ad51f9551124ce7fb49ddddc6a82c4910813db4c790c97c9ccf32" -dependencies = [ - "cfg-if", - "cranelift-codegen", - "cranelift-control", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "gimli", - "itertools 0.14.0", - "log", - "object", - "pulley-interpreter", - "smallvec", - "target-lexicon", - "thiserror 2.0.18", - "wasmparser 0.244.0", - "wasmtime-environ", - "wasmtime-internal-core", - "wasmtime-internal-unwinder", - "wasmtime-internal-versioned-export-macros", -] - -[[package]] -name = "wasmtime-internal-fiber" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05decfad1021ad2efcca5c1be9855acb54b6ee7158ac4467119b30b7481508e3" -dependencies = [ - "cc", - "cfg-if", - "libc", - "rustix 1.1.4", - "wasmtime-environ", - "wasmtime-internal-versioned-export-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "wasmtime-internal-jit-debug" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924980c50427885fd4feed2049b88380178e567768aaabf29045b02eb262eaa7" -dependencies = [ - "cc", - "object", - "rustix 1.1.4", - "wasmtime-internal-versioned-export-macros", -] - -[[package]] -name = "wasmtime-internal-jit-icache-coherence" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57d24e8d1334a0e5a8b600286ffefa1fc4c3e8176b110dff6fbc1f43c4a599b" -dependencies = [ - "cfg-if", - "libc", - "wasmtime-internal-core", - "windows-sys 0.61.2", -] - -[[package]] -name = "wasmtime-internal-unwinder" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1a144bd4393593a868ba9df09f34a6a360cb5db6e71815f20d3f649c6e6735" -dependencies = [ - "cfg-if", - "cranelift-codegen", - "log", - "object", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-internal-versioned-export-macros" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a6948b56bb00c62dbd205ea18a4f1ceccbe1e4b8479651fdb0bab2553790f20" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "wasmtime-internal-winch" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9130b3ab6fb01be80b27b9a2c84817af29ae8224094f2503d2afa9fea5bf9d00" -dependencies = [ - "cranelift-codegen", - "gimli", - "log", - "object", - "target-lexicon", - "wasmparser 0.244.0", - "wasmtime-environ", - "wasmtime-internal-cranelift", - "winch-codegen", -] - -[[package]] -name = "wasmtime-internal-wit-bindgen" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102d0d70dbfede00e4cc9c24e86df6d32c03bf6f5ad06b5d6c76b0a4a5004c4a" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "heck", - "indexmap", - "wit-parser", -] - -[[package]] -name = "wasmtime-wasi" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea938f6f4f11e5ffe6d8b6f34c9a994821db9511c3e9c98e535896f27d06bb92" -dependencies = [ - "async-trait", - "bitflags 2.11.0", - "bytes", - "cap-fs-ext", - "cap-net-ext", - "cap-rand", - "cap-std", - "cap-time-ext", - "fs-set-times", - "futures", - "io-extras", - "io-lifetimes", - "rustix 1.1.4", - "system-interface", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", - "wasmtime", - "wasmtime-wasi-io", - "wiggle", - "windows-sys 0.61.2", -] - -[[package]] -name = "wasmtime-wasi-io" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cb16a88d0443b509d6eca4298617233265179090abf03e0a8042b9b251e9da" -dependencies = [ - "async-trait", - "bytes", - "futures", - "tracing", - "wasmtime", -] - -[[package]] -name = "wast" -version = "35.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" -dependencies = [ - "leb128", -] - -[[package]] -name = "wast" -version = "245.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" -dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width", - "wasm-encoder 0.245.1", -] - -[[package]] -name = "wat" -version = "1.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" -dependencies = [ - "wast 245.0.1", -] - -[[package]] -name = "wiggle" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dca2bf96d20f0c70e6741cc6c8c1a9ee4c3c0310c7ad1971242628c083cc9a5" -dependencies = [ - "bitflags 2.11.0", - "thiserror 2.0.18", - "tracing", - "wasmtime", - "wasmtime-environ", - "wiggle-macro", -] - -[[package]] -name = "wiggle-generate" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d8c016d6d3ec6dc6b8c80c23cede4ee2386ccf347d01984f7991d7659f73ef" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", - "wasmtime-environ", - "witx", -] - -[[package]] -name = "wiggle-macro" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a267096e48857096f035fffca29e22f0bbe840af4d74a6725eb695e1782110" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wiggle-generate", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winch-codegen" -version = "42.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1977857998e4dd70d26e2bfc0618a9684a2fb65b1eca174dc13f3b3e9c2159ca" -dependencies = [ - "cranelift-assembler-x64", - "cranelift-codegen", - "gimli", - "regalloc2", - "smallvec", - "target-lexicon", - "thiserror 2.0.18", - "wasmparser 0.244.0", - "wasmtime-environ", - "wasmtime-internal-core", - "wasmtime-internal-cranelift", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" - -[[package]] -name = "winx" -version = "0.36.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" -dependencies = [ - "bitflags 2.11.0", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder 0.244.0", - "wasm-metadata", - "wasmparser 0.244.0", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.244.0", -] - -[[package]] -name = "witx" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" -dependencies = [ - "anyhow", - "log", - "thiserror 1.0.69", - "wast 35.0.2", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index a12bffe..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,72 +0,0 @@ -[workspace] -members = ["crates/*"] -resolver = "3" - -[workspace.package] -edition = "2024" -license = "MIT OR Apache-2.0" - -[workspace.lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test)'] } -unreachable_pub = "warn" -unused_qualifications = "warn" - -[workspace.lints.clippy] -dbg_macro = "warn" -explicit_deref_methods = "warn" -explicit_into_iter_loop = "warn" -filter_map_next = "warn" -flat_map_option = "warn" -implicit_clone = "warn" -inefficient_to_string = "warn" -large_stack_arrays = "warn" -large_types_passed_by_value = "warn" -linkedlist = "warn" -map_err_ignore = "warn" -map_flatten = "warn" -map_unwrap_or = "warn" -mem_forget = "warn" -needless_borrow = "warn" -needless_continue = "warn" -needless_for_each = "warn" -needless_pass_by_value = "warn" -single_match_else = "warn" -unused_self = "warn" -unused_trait_names = "warn" - -[workspace.dependencies] -anyhow = "1.0" -blake3 = "1.8" -camino = "1.1" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -mitki-abi-lower = { path = "crates/mitki-abi-lower" } -mitki-abi = { path = "crates/mitki-abi" } -mitki-analysis = { path = "crates/mitki-analysis" } -mitki-backend-wasm = { path = "crates/mitki-backend-wasm" } -mitki-codegen-core = { path = "crates/mitki-codegen-core" } -mitki-comptime-wasm = { path = "crates/mitki-comptime-wasm" } -mitki-db = { path = "crates/mitki-db" } -mitki-errors = { path = "crates/mitki-errors" } -mitki-hir = { path = "crates/mitki-hir" } -mitki-ide = { path = "crates/mitki-ide" } -mitki-inputs = { path = "crates/mitki-inputs" } -mitki-lsp-server = { path = "crates/mitki-lsp-server" } -mitki-lower = { path = "crates/mitki-lower" } -mitki-parse = { path = "crates/mitki-parse" } -mitki-resolve = { path = "crates/mitki-resolve" } -mitki-span = { path = "crates/mitki-span" } -mitki-typeck = { path = "crates/mitki-typeck" } -mitki-tokenizer = { path = "crates/mitki-tokenizer" } -mitki-wasm-runtime = { path = "crates/mitki-wasm-runtime" } -mitki-yellow = { path = "crates/mitki-yellow" } -salsa = { version = "0.26", default-features = false, features = [ - "macros", - "inventory", -] } -rustc-hash = "2.0" -text-size = "1.1" -wasm-encoder = "0.245.1" -wasmparser = "0.245.1" -wasmtime = "42.0.1" -wasmtime-wasi = "42.0.1" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f81539b..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Andrey Nikolaev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index b66d140..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# The Mitki Programming Language - -It is an open-source, statically typed programming language. diff --git a/crates/mitki-abi-lower/Cargo.toml b/crates/mitki-abi-lower/Cargo.toml deleted file mode 100644 index 36dfdfb..0000000 --- a/crates/mitki-abi-lower/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "mitki-abi-lower" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -mitki-abi.workspace = true -mitki-hir.workspace = true -mitki-lower.workspace = true -mitki-span.workspace = true -serde.workspace = true -salsa.workspace = true - -[dev-dependencies] -mitki-db.workspace = true diff --git a/crates/mitki-abi-lower/src/abi.rs b/crates/mitki-abi-lower/src/abi.rs deleted file mode 100644 index f0b49d6..0000000 --- a/crates/mitki-abi-lower/src/abi.rs +++ /dev/null @@ -1,1198 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use mitki_abi::{AbiTypeKind, SemanticTypeGraph, SigId, TransportClass, TransportRef, TypeId}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::item::scope::{enum_variants, struct_fields}; -use serde::{Deserialize, Serialize}; - -const ARC_ALIGN: u32 = 8; -const ARC_HEADER_SIZE: u32 = 8; -const ARRAY_HEADER_SIZE: u32 = 8; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiFunctionAbi { - pub params: Vec, - pub result: MitkiResultAbi, - #[serde(skip)] - pub type_values: BTreeMap, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiParamAbi { - pub name: Option, - pub passing: MitkiPassingAbi, - pub value: MitkiValueAbi, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiResultAbi { - pub passing: MitkiPassingAbi, - pub value: MitkiValueAbi, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum MitkiPassingAbi { - Unit, - I32, - F64, - Pointer, - OutPointer, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiValueAbi { - pub kind: MitkiValueKind, - pub boundary: MitkiLoweringAbi, - pub runtime: MitkiLoweringAbi, - pub pointee: Option, - #[serde(skip)] - pub runtime_member_order: Option>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum MitkiValueKind { - Unit, - Int, - Bool, - Float, - Char, - Function, - Opaque, - String, - Array, - Tuple, - Record, - Struct, - Enum, - Union, - Intersection, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag = "kind", content = "layout")] -pub enum MitkiLoweringAbi { - Unit, - I32, - F64, - Pointer, - Aggregate(MitkiAggregateLayoutAbi), -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiAggregateLayoutAbi { - pub size: u32, - pub align: u32, - pub kind: MitkiAggregateKindAbi, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag = "kind", content = "layout")] -pub enum MitkiAggregateKindAbi { - Fields(Vec), - Enum(MitkiEnumLayoutAbi), -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiFieldAbi { - pub name: Option, - pub offset: u32, - pub value: Box, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiEnumLayoutAbi { - pub payload_offset: u32, - pub variants: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiVariantAbi { - pub name: String, - pub tag: i32, - pub fields: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case", tag = "kind", content = "layout")] -pub enum MitkiPointeeAbi { - String, - Array(MitkiArrayLayoutAbi), - Aggregate(MitkiAggregateLayoutAbi), -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MitkiArrayLayoutAbi { - pub object_align: u32, - pub item_size: u32, - pub item_align: u32, - pub item_stride: u32, - pub data_offset: u32, - pub item: Box, -} - -#[derive(Clone, Copy)] -pub enum LoweringMode { - Boundary, - Runtime, -} - -impl MitkiFunctionAbi { - pub fn from_v2_signature( - graph: &SemanticTypeGraph, - signature_id: SigId, - ) -> Result { - let signature = graph - .signatures - .get(signature_id.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 signature `{}`", signature_id.0))?; - let mut builder = V2AbiBuilder::new(graph); - let params = signature - .params - .iter() - .map(|transport| { - let passing = v2_passing(graph, transport)?; - let value = builder.value_abi(builder.transport_value_type(transport)?)?; - Ok(MitkiParamAbi { name: None, passing, value }) - }) - .collect::, String>>()?; - let result_passing = v2_passing(graph, &signature.result)?; - let result_value = builder.value_abi(builder.transport_value_type(&signature.result)?)?; - Ok(Self { - params, - result: MitkiResultAbi { passing: result_passing, value: result_value }, - type_values: builder.cache.clone(), - }) - } - - pub fn from_v2_signature_with_runtime_types<'db>( - db: &'db dyn salsa::Database, - graph: &'db SemanticTypeGraph, - signature_id: SigId, - param_runtime_tys: &[Ty<'db>], - result_runtime_ty: Ty<'db>, - ) -> Result { - let signature = graph - .signatures - .get(signature_id.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 signature `{}`", signature_id.0))?; - if signature.params.len() != param_runtime_tys.len() { - return Err(format!( - "internal error: ABI v2 signature `{}` expected {} runtime param type(s), found {}", - signature_id.0, - signature.params.len(), - param_runtime_tys.len() - )); - } - let mut abi = Self::from_v2_signature(graph, signature_id)?; - for ((transport, runtime_ty), param) in signature - .params - .iter() - .zip(param_runtime_tys.iter().copied()) - .zip(abi.params.iter_mut()) - { - apply_top_level_runtime_ty( - db, - graph, - transport.semantic_type, - runtime_ty, - &mut param.value, - )?; - } - apply_top_level_runtime_ty( - db, - graph, - signature.result.semantic_type, - result_runtime_ty, - &mut abi.result.value, - )?; - Ok(abi) - } -} - -impl MitkiValueAbi { - pub fn lowering(&self, mode: LoweringMode) -> &MitkiLoweringAbi { - match mode { - LoweringMode::Boundary => &self.boundary, - LoweringMode::Runtime => &self.runtime, - } - } -} - -fn v2_passing( - graph: &SemanticTypeGraph, - transport: &TransportRef, -) -> Result { - ensure_runtime_core_transport(graph, transport)?; - match transport.transport_class { - TransportClass::Immediate => match type_kind(graph, transport_passing_type(transport))? { - AbiTypeKind::Unit => Ok(MitkiPassingAbi::Unit), - AbiTypeKind::Tuple { elems } if elems.is_empty() => Ok(MitkiPassingAbi::Unit), - AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Char - | AbiTypeKind::Enum { .. } => Ok(MitkiPassingAbi::I32), - AbiTypeKind::Float { .. } => Ok(MitkiPassingAbi::F64), - other => { - Err(format!("internal error: immediate ABI v2 transport cannot lower `{other:?}`")) - } - }, - TransportClass::CanonicalValue => Ok(MitkiPassingAbi::Pointer), - TransportClass::CapabilityHandle => Ok(MitkiPassingAbi::I32), - } -} - -fn transport_passing_type(transport: &TransportRef) -> TypeId { - transport.transport_type.unwrap_or(transport.semantic_type) -} - -fn ensure_runtime_core_transport( - graph: &SemanticTypeGraph, - transport: &TransportRef, -) -> Result<(), String> { - let mut stack = vec![transport.semantic_type]; - if let Some(transport_type) = transport.transport_type { - stack.push(transport_type); - } - let mut seen = BTreeSet::new(); - while let Some(ty) = stack.pop() { - if !seen.insert(ty) { - continue; - } - let node = graph - .types - .get(ty.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 type `{}`", ty.0))?; - match &node.kind { - AbiTypeKind::Function { .. } | AbiTypeKind::Opaque { .. } => {} - _ => stack.extend(type_edges(&node.kind)), - } - } - Ok(()) -} - -fn type_kind(graph: &SemanticTypeGraph, ty: TypeId) -> Result<&AbiTypeKind, String> { - graph - .types - .get(ty.0 as usize) - .map(|node| &node.kind) - .ok_or_else(|| format!("unknown ABI v2 type `{}`", ty.0)) -} - -fn type_edges(kind: &AbiTypeKind) -> Vec { - match kind { - AbiTypeKind::Array { elem } => vec![*elem], - AbiTypeKind::Tuple { elems } => elems.clone(), - AbiTypeKind::Record { fields } | AbiTypeKind::Struct { fields, .. } => { - fields.iter().map(|field| field.ty).collect() - } - AbiTypeKind::Enum { variants, .. } => { - variants.iter().flat_map(|variant| variant.fields.iter().copied()).collect() - } - AbiTypeKind::Union { members } => members.clone(), - AbiTypeKind::Intersection { members, carrier, .. } => { - let mut edges = members.clone(); - edges.push(*carrier); - edges - } - AbiTypeKind::Function { params, result, .. } => { - let mut edges = params.clone(); - edges.push(*result); - edges - } - AbiTypeKind::Unit - | AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Float { .. } - | AbiTypeKind::Char - | AbiTypeKind::String - | AbiTypeKind::Opaque { .. } => Vec::new(), - } -} - -struct V2AbiBuilder<'a> { - graph: &'a SemanticTypeGraph, - cache: BTreeMap, - visiting: BTreeSet, -} - -impl<'a> V2AbiBuilder<'a> { - fn new(graph: &'a SemanticTypeGraph) -> Self { - Self { graph, cache: BTreeMap::new(), visiting: BTreeSet::new() } - } - - fn value_abi(&mut self, ty: TypeId) -> Result { - if let Some(value) = self.cache.get(&ty) { - return Ok(value.clone()); - } - if !self.visiting.insert(ty) { - return self.recursive_placeholder(ty); - } - - let kind = self.type_kind(ty)?.clone(); - let value = match kind { - AbiTypeKind::Unit => MitkiValueAbi { - kind: MitkiValueKind::Unit, - boundary: MitkiLoweringAbi::Unit, - runtime: MitkiLoweringAbi::Unit, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Int { .. } => MitkiValueAbi { - kind: MitkiValueKind::Int, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Bool => MitkiValueAbi { - kind: MitkiValueKind::Bool, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Float { .. } => MitkiValueAbi { - kind: MitkiValueKind::Float, - boundary: MitkiLoweringAbi::F64, - runtime: MitkiLoweringAbi::F64, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Char => MitkiValueAbi { - kind: MitkiValueKind::Char, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Function { .. } => MitkiValueAbi { - kind: MitkiValueKind::Function, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::Aggregate(function_runtime_layout_abi()), - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Opaque { .. } => MitkiValueAbi { - kind: MitkiValueKind::Opaque, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::Pointer, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::String => MitkiValueAbi { - kind: MitkiValueKind::String, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: Some(MitkiPointeeAbi::String), - runtime_member_order: None, - }, - AbiTypeKind::Array { elem } => MitkiValueAbi { - kind: MitkiValueKind::Array, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: Some(MitkiPointeeAbi::Array(self.array_layout(elem)?)), - runtime_member_order: None, - }, - AbiTypeKind::Tuple { elems } if elems.is_empty() => MitkiValueAbi { - kind: MitkiValueKind::Unit, - boundary: MitkiLoweringAbi::Unit, - runtime: MitkiLoweringAbi::Unit, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Tuple { elems } => MitkiValueAbi { - kind: MitkiValueKind::Tuple, - boundary: MitkiLoweringAbi::Aggregate(self.fields_layout( - elems.iter().copied().map(|field| (None, field)), - LoweringMode::Boundary, - )?), - runtime: MitkiLoweringAbi::Aggregate(self.fields_layout( - elems.iter().copied().map(|field| (None, field)), - LoweringMode::Runtime, - )?), - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Record { fields } => MitkiValueAbi { - kind: MitkiValueKind::Record, - boundary: MitkiLoweringAbi::Aggregate( - self.fields_layout(self.named_fields(&fields)?, LoweringMode::Boundary)?, - ), - runtime: MitkiLoweringAbi::Aggregate( - self.fields_layout(self.named_fields(&fields)?, LoweringMode::Runtime)?, - ), - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Struct { fields, .. } => { - let runtime_layout = - self.fields_layout(self.named_fields(&fields)?, LoweringMode::Runtime)?; - MitkiValueAbi { - kind: MitkiValueKind::Struct, - boundary: MitkiLoweringAbi::Aggregate( - self.fields_layout(self.named_fields(&fields)?, LoweringMode::Boundary)?, - ), - runtime: MitkiLoweringAbi::Pointer, - pointee: Some(MitkiPointeeAbi::Aggregate(runtime_layout)), - runtime_member_order: None, - } - } - AbiTypeKind::Enum { variants, .. } => { - let runtime_layout = self.enum_layout(&variants, LoweringMode::Runtime)?; - MitkiValueAbi { - kind: MitkiValueKind::Enum, - boundary: MitkiLoweringAbi::Aggregate( - self.enum_layout(&variants, LoweringMode::Boundary)?, - ), - runtime: MitkiLoweringAbi::Pointer, - pointee: Some(MitkiPointeeAbi::Aggregate(runtime_layout)), - runtime_member_order: None, - } - } - AbiTypeKind::Union { members } => MitkiValueAbi { - kind: MitkiValueKind::Union, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Aggregate( - self.union_layout(&members, LoweringMode::Runtime)?, - ), - pointee: None, - runtime_member_order: Some(members), - }, - AbiTypeKind::Intersection { carrier, facet_plan, .. } => { - let live_members = intersection_live_members(self.graph, facet_plan)?; - if live_members.is_empty() { - self.value_abi(carrier)? - } else { - let mut runtime_members = Vec::with_capacity(1 + live_members.len()); - runtime_members.push(carrier); - runtime_members.extend(live_members.iter().copied()); - MitkiValueAbi { - kind: MitkiValueKind::Intersection, - boundary: MitkiLoweringAbi::Aggregate(self.fields_layout( - runtime_members.iter().copied().map(|member| (None, member)), - LoweringMode::Boundary, - )?), - runtime: MitkiLoweringAbi::Aggregate(self.fields_layout( - runtime_members.iter().copied().map(|member| (None, member)), - LoweringMode::Runtime, - )?), - pointee: None, - runtime_member_order: Some(runtime_members), - } - } - } - }; - - self.visiting.remove(&ty); - self.cache.insert(ty, value.clone()); - Ok(value) - } - - fn type_kind(&self, ty: TypeId) -> Result<&AbiTypeKind, String> { - self.graph - .types - .get(ty.0 as usize) - .map(|node| &node.kind) - .ok_or_else(|| format!("unknown ABI v2 type `{}`", ty.0)) - } - - fn transport_value_type(&self, transport: &TransportRef) -> Result { - if let Some(transport_type) = transport.transport_type - && (!matches!(transport.transport_class, TransportClass::CanonicalValue) - || intersection_reuses_carrier_transport(self.graph, transport.semantic_type)?) - { - return Ok(transport_type); - } - Ok(transport.semantic_type) - } - - fn recursive_placeholder(&self, ty: TypeId) -> Result { - Ok(match self.type_kind(ty)? { - AbiTypeKind::Unit => MitkiValueAbi { - kind: MitkiValueKind::Unit, - boundary: MitkiLoweringAbi::Unit, - runtime: MitkiLoweringAbi::Unit, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Bool => MitkiValueAbi { - kind: MitkiValueKind::Bool, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Int { .. } => MitkiValueAbi { - kind: MitkiValueKind::Int, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Float { .. } => MitkiValueAbi { - kind: MitkiValueKind::Float, - boundary: MitkiLoweringAbi::F64, - runtime: MitkiLoweringAbi::F64, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Char => MitkiValueAbi { - kind: MitkiValueKind::Char, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::I32, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Function { .. } => MitkiValueAbi { - kind: MitkiValueKind::Function, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::Aggregate(function_runtime_layout_abi()), - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Opaque { .. } => MitkiValueAbi { - kind: MitkiValueKind::Opaque, - boundary: MitkiLoweringAbi::I32, - runtime: MitkiLoweringAbi::Pointer, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::String => MitkiValueAbi { - kind: MitkiValueKind::String, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: Some(MitkiPointeeAbi::String), - runtime_member_order: None, - }, - AbiTypeKind::Array { .. } => MitkiValueAbi { - kind: MitkiValueKind::Array, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Struct { .. } => MitkiValueAbi { - kind: MitkiValueKind::Struct, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Enum { .. } => MitkiValueAbi { - kind: MitkiValueKind::Enum, - boundary: MitkiLoweringAbi::Pointer, - runtime: MitkiLoweringAbi::Pointer, - pointee: None, - runtime_member_order: None, - }, - AbiTypeKind::Intersection { carrier, facet_plan, .. } - if intersection_live_members(self.graph, *facet_plan)?.is_empty() => - { - self.recursive_placeholder(*carrier)? - } - AbiTypeKind::Tuple { .. } - | AbiTypeKind::Record { .. } - | AbiTypeKind::Union { .. } - | AbiTypeKind::Intersection { .. } => { - return Err("recursive types are not supported yet".to_owned()); - } - }) - } - - fn array_layout(&mut self, elem: TypeId) -> Result { - let item = self.value_abi(elem)?; - let (item_size, item_align) = lowering_size_align(item.lowering(LoweringMode::Runtime))?; - let item_stride = align_to(item_size, item_align.max(1)); - let data_offset = align_to(ARC_HEADER_SIZE + ARRAY_HEADER_SIZE, item_align.max(1)) - .checked_sub(ARC_HEADER_SIZE) - .ok_or_else(|| "array layout underflowed".to_owned())?; - Ok(MitkiArrayLayoutAbi { - object_align: ARC_ALIGN.max(item_align), - item_size, - item_align, - item_stride, - data_offset, - item: Box::new(item), - }) - } - - fn fields_layout( - &mut self, - fields: I, - mode: LoweringMode, - ) -> Result - where - I: IntoIterator, TypeId)>, - { - let mut abi_fields = Vec::new(); - let mut size = 0u32; - let mut align = 1u32; - - for field in fields { - let (name, ty) = field; - let value = self.value_abi(ty)?; - let (field_size, field_align) = lowering_size_align(value.lowering(mode))?; - let offset = align_to(size, field_align.max(1)); - size = offset - .checked_add(field_size) - .ok_or_else(|| "aggregate layout overflowed".to_owned())?; - align = align.max(field_align); - abi_fields.push(MitkiFieldAbi { name, offset, value: Box::new(value) }); - } - - Ok(MitkiAggregateLayoutAbi { - size: align_to(size, align.max(1)), - align: align.max(1), - kind: MitkiAggregateKindAbi::Fields(abi_fields), - }) - } - - fn enum_layout( - &mut self, - variants: &[mitki_abi::EnumVariant], - mode: LoweringMode, - ) -> Result { - let mut variant_layouts = Vec::with_capacity(variants.len()); - let mut max_variant_align = 1u32; - let mut max_variant_size = 0u32; - - for variant in variants { - let fields_layout = self - .fields_layout(variant.fields.iter().copied().map(|field| (None, field)), mode)?; - let MitkiAggregateKindAbi::Fields(fields) = fields_layout.kind else { - unreachable!(); - }; - max_variant_align = max_variant_align.max(fields_layout.align); - max_variant_size = max_variant_size.max(fields_layout.size); - variant_layouts.push((self.variant_name(variant.name)?.to_owned(), fields)); - } - - let payload_offset = align_to(4, max_variant_align.max(1)); - let align = 4u32.max(max_variant_align); - let size = align_to( - payload_offset - .checked_add(max_variant_size) - .ok_or_else(|| "enum layout overflowed".to_owned())?, - align, - ); - let abi_variants = variant_layouts - .into_iter() - .enumerate() - .map(|(index, (name, fields))| { - Ok(MitkiVariantAbi { - name, - tag: i32::try_from(index) - .map_err(|_error| "enum tag exceeded i32 range".to_owned())?, - fields: fields - .into_iter() - .map(|mut field| { - field.offset = field - .offset - .checked_add(payload_offset) - .ok_or_else(|| "enum field offset overflowed".to_owned())?; - Ok(field) - }) - .collect::, String>>()?, - }) - }) - .collect::, String>>()?; - Ok(MitkiAggregateLayoutAbi { - size, - align, - kind: MitkiAggregateKindAbi::Enum(MitkiEnumLayoutAbi { - payload_offset, - variants: abi_variants, - }), - }) - } - - fn union_layout( - &mut self, - members: &[TypeId], - mode: LoweringMode, - ) -> Result { - let mut variant_fields = Vec::with_capacity(members.len()); - let mut max_variant_align = 1u32; - let mut max_variant_size = 0u32; - - for &member in members { - let fields_layout = self.fields_layout(std::iter::once((None, member)), mode)?; - let MitkiAggregateKindAbi::Fields(fields) = fields_layout.kind else { - unreachable!(); - }; - max_variant_align = max_variant_align.max(fields_layout.align); - max_variant_size = max_variant_size.max(fields_layout.size); - variant_fields.push(fields); - } - - let payload_offset = align_to(4, max_variant_align.max(1)); - let align = 4u32.max(max_variant_align); - let size = align_to( - payload_offset - .checked_add(max_variant_size) - .ok_or_else(|| "union layout overflowed".to_owned())?, - align, - ); - let variants = variant_fields - .into_iter() - .enumerate() - .map(|(index, fields)| { - Ok(MitkiVariantAbi { - name: format!("arm{index}"), - tag: i32::try_from(index) - .map_err(|_error| "union arm tag exceeded i32 range".to_owned())?, - fields: fields - .into_iter() - .map(|mut field| { - field.offset = field - .offset - .checked_add(payload_offset) - .ok_or_else(|| "union arm field offset overflowed".to_owned())?; - Ok(field) - }) - .collect::, String>>()?, - }) - }) - .collect::, String>>()?; - Ok(MitkiAggregateLayoutAbi { - size, - align, - kind: MitkiAggregateKindAbi::Enum(MitkiEnumLayoutAbi { payload_offset, variants }), - }) - } - - fn named_fields( - &self, - fields: &[mitki_abi::RecordField], - ) -> Result, TypeId)>, String> { - fields - .iter() - .map(|field| Ok((Some(self.field_name(field.name)?.to_owned()), field.ty))) - .collect() - } - - fn field_name(&self, id: mitki_abi::FieldNameId) -> Result<&str, String> { - let string_id = *self - .graph - .field_names - .get(id.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 field name `{}`", id.0))?; - self.string_value(string_id) - } - - fn variant_name(&self, id: mitki_abi::VariantNameId) -> Result<&str, String> { - let string_id = *self - .graph - .variant_names - .get(id.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 variant name `{}`", id.0))?; - self.string_value(string_id) - } - - fn string_value(&self, id: mitki_abi::StringId) -> Result<&str, String> { - self.graph - .strings - .get(id.0 as usize) - .map(String::as_str) - .ok_or_else(|| format!("unknown ABI v2 string `{}`", id.0)) - } -} - -fn apply_top_level_runtime_ty( - db: &dyn salsa::Database, - graph: &SemanticTypeGraph, - semantic_type: TypeId, - runtime_ty: Ty<'_>, - value: &mut MitkiValueAbi, -) -> Result<(), String> { - if value.kind != MitkiValueKind::Union && value.kind != MitkiValueKind::Intersection { - return Ok(()); - } - let mut builder = V2AbiBuilder::new(graph); - match type_kind(graph, semantic_type)? { - AbiTypeKind::Union { members } => { - let runtime_member_order = runtime_union_member_order(db, graph, members, runtime_ty)?; - value.runtime = MitkiLoweringAbi::Aggregate( - builder.union_layout(&runtime_member_order, LoweringMode::Runtime)?, - ); - value.runtime_member_order = Some(runtime_member_order); - } - AbiTypeKind::Intersection { carrier, facet_plan, .. } => { - let runtime_member_order = - runtime_intersection_member_order(db, graph, *carrier, *facet_plan, runtime_ty)?; - if runtime_member_order.len() == 1 { - *value = builder.value_abi(runtime_member_order[0])?; - } else { - value.runtime = MitkiLoweringAbi::Aggregate(builder.fields_layout( - runtime_member_order.iter().copied().map(|member| (None, member)), - LoweringMode::Runtime, - )?); - value.runtime_member_order = Some(runtime_member_order); - } - } - _ => {} - } - Ok(()) -} - -fn runtime_union_member_order( - db: &dyn salsa::Database, - graph: &SemanticTypeGraph, - members: &[TypeId], - runtime_ty: Ty<'_>, -) -> Result, String> { - let TyKind::Union(runtime_members) = runtime_ty.kind(db) else { - return Ok(members.to_vec()); - }; - runtime_members - .iter() - .map(|&runtime_member| { - members - .iter() - .copied() - .find(|&semantic_member| { - semantic_type_matches_runtime_ty(db, graph, semantic_member, runtime_member) - }) - .ok_or_else(|| { - format!( - "internal error: union runtime member `{}` did not match semantic ABI \ - members", - runtime_member.display(db) - ) - }) - }) - .collect() -} - -fn semantic_type_matches_runtime_ty( - db: &dyn salsa::Database, - graph: &SemanticTypeGraph, - semantic_type: TypeId, - runtime_ty: Ty<'_>, -) -> bool { - let Ok(kind) = type_kind(graph, semantic_type) else { - return false; - }; - match (kind, runtime_ty.kind(db)) { - (AbiTypeKind::Unit, TyKind::Tuple(items)) => items.is_empty(), - (AbiTypeKind::Bool, TyKind::Bool) - | (AbiTypeKind::Char, TyKind::Char) - | (AbiTypeKind::String, TyKind::String) => true, - (AbiTypeKind::Int { signed, bits }, TyKind::Int) => *signed && *bits == 32, - (AbiTypeKind::Float { bits }, TyKind::Float) => *bits == 64, - (AbiTypeKind::Array { elem }, TyKind::Array(runtime_elem)) => { - semantic_type_matches_runtime_ty(db, graph, *elem, *runtime_elem) - } - (AbiTypeKind::Tuple { elems }, TyKind::Tuple(runtime_items)) => { - elems.len() == runtime_items.len() - && elems.iter().zip(runtime_items.iter()).all(|(&semantic_elem, &runtime_elem)| { - semantic_type_matches_runtime_ty(db, graph, semantic_elem, runtime_elem) - }) - } - (AbiTypeKind::Record { fields }, TyKind::Record(runtime_fields)) => { - let mut runtime_fields = runtime_fields.clone(); - runtime_fields.sort_by_key(|(name, _)| name.text(db).to_owned()); - fields.len() == runtime_fields.len() - && fields.iter().zip(runtime_fields.iter()).all( - |(semantic_field, (runtime_name, runtime_field_ty))| { - field_name_matches(graph, semantic_field.name, runtime_name.text(db)) - && semantic_type_matches_runtime_ty( - db, - graph, - semantic_field.ty, - *runtime_field_ty, - ) - }, - ) - } - (AbiTypeKind::Struct { nominal, fields }, TyKind::Struct(struct_ty)) => { - symbol_matches(graph, *nominal, struct_ty.name(db).text(db)) - && fields.len() == struct_fields(db, *struct_ty).len() - && fields.iter().zip(struct_fields(db, *struct_ty).iter()).all( - |(semantic_field, (runtime_name, runtime_field_ty))| { - field_name_matches(graph, semantic_field.name, runtime_name.text(db)) - && semantic_type_matches_runtime_ty( - db, - graph, - semantic_field.ty, - *runtime_field_ty, - ) - }, - ) - } - (AbiTypeKind::Enum { nominal, variants }, TyKind::Enum(enum_ty)) => { - let runtime_variants = enum_variants(db, *enum_ty); - symbol_matches(graph, *nominal, enum_ty.name(db).text(db)) - && variants.len() == runtime_variants.len() - && variants.iter().zip(runtime_variants.iter()).all( - |(semantic_variant, (runtime_name, runtime_fields))| { - variant_name_matches(graph, semantic_variant.name, runtime_name.text(db)) - && semantic_variant.fields.len() == runtime_fields.len() - && semantic_variant.fields.iter().zip(runtime_fields.iter()).all( - |(&semantic_field_ty, runtime_field_ty)| { - semantic_type_matches_runtime_ty( - db, - graph, - semantic_field_ty, - *runtime_field_ty, - ) - }, - ) - }, - ) - } - (AbiTypeKind::Union { members }, TyKind::Union(runtime_members)) => { - members.len() == runtime_members.len() - && members.iter().all(|&semantic_member| { - runtime_members.iter().any(|&runtime_member| { - semantic_type_matches_runtime_ty(db, graph, semantic_member, runtime_member) - }) - }) - } - (AbiTypeKind::Function { params, result, .. }, TyKind::Function { inputs, output }) => { - params.len() == inputs.len() - && params.iter().zip(inputs.iter()).all(|(&semantic_param, &runtime_param)| { - semantic_type_matches_runtime_ty(db, graph, semantic_param, runtime_param) - }) - && semantic_type_matches_runtime_ty(db, graph, *result, *output) - } - (AbiTypeKind::Intersection { carrier, facet_plan, .. }, TyKind::Inter(runtime_members)) => { - let Ok(expected) = - runtime_intersection_member_order(db, graph, *carrier, *facet_plan, runtime_ty) - else { - return false; - }; - expected.len() == runtime_members.len() - && expected.iter().zip(runtime_members.iter()).all( - |(&semantic_member, &runtime_member)| { - semantic_type_matches_runtime_ty(db, graph, semantic_member, runtime_member) - }, - ) - } - _ => false, - } -} - -fn intersection_reuses_carrier_transport( - graph: &SemanticTypeGraph, - semantic_type: TypeId, -) -> Result { - let AbiTypeKind::Intersection { facet_plan, .. } = type_kind(graph, semantic_type)? else { - return Ok(false); - }; - Ok(intersection_live_members(graph, *facet_plan)?.is_empty()) -} - -fn intersection_live_members( - graph: &SemanticTypeGraph, - plan_id: Option, -) -> Result, String> { - let Some(plan_id) = plan_id else { - return Ok(Vec::new()); - }; - let plan = graph - .facet_plans - .get(plan_id.0 as usize) - .ok_or_else(|| format!("unknown ABI v2 facet plan `{}`", plan_id.0))?; - Ok(plan - .entries - .iter() - .filter(|entry| entry.kind != mitki_abi::FacetPlanEntryKind::Erased) - .map(|entry| entry.member) - .collect()) -} - -fn runtime_intersection_member_order( - db: &dyn salsa::Database, - graph: &SemanticTypeGraph, - carrier: TypeId, - facet_plan: Option, - runtime_ty: Ty<'_>, -) -> Result, String> { - let TyKind::Inter(runtime_members) = runtime_ty.kind(db) else { - let mut members = vec![carrier]; - members.extend(intersection_live_members(graph, facet_plan)?); - return Ok(members); - }; - - let mut members = Vec::with_capacity(runtime_members.len()); - for &runtime_member in runtime_members { - let semantic_member = std::iter::once(carrier) - .chain(intersection_live_members(graph, facet_plan)?) - .find(|&semantic_member| { - semantic_type_matches_runtime_ty(db, graph, semantic_member, runtime_member) - }) - .ok_or_else(|| { - format!( - "internal error: intersection runtime member `{}` did not match semantic ABI \ - members", - runtime_member.display(db) - ) - })?; - members.push(semantic_member); - } - Ok(members) -} - -fn field_name_matches(graph: &SemanticTypeGraph, id: mitki_abi::FieldNameId, name: &str) -> bool { - graph - .field_names - .get(id.0 as usize) - .and_then(|string_id| graph.strings.get(string_id.0 as usize)) - .is_some_and(|value| value == name) -} - -fn variant_name_matches( - graph: &SemanticTypeGraph, - id: mitki_abi::VariantNameId, - name: &str, -) -> bool { - graph - .variant_names - .get(id.0 as usize) - .and_then(|string_id| graph.strings.get(string_id.0 as usize)) - .is_some_and(|value| value == name) -} - -fn symbol_matches(graph: &SemanticTypeGraph, id: mitki_abi::SymbolId, name: &str) -> bool { - graph - .nominal_symbols - .get(id.0 as usize) - .and_then(|string_id| graph.strings.get(string_id.0 as usize)) - .is_some_and(|value| value == name) -} - -fn lowering_size_align(lowering: &MitkiLoweringAbi) -> Result<(u32, u32), String> { - Ok(match lowering { - MitkiLoweringAbi::Unit => (0, 1), - MitkiLoweringAbi::I32 | MitkiLoweringAbi::Pointer => (4, 4), - MitkiLoweringAbi::F64 => (8, 8), - MitkiLoweringAbi::Aggregate(layout) => (layout.size, layout.align), - }) -} - -fn align_to(offset: u32, align: u32) -> u32 { - let align = align.max(1); - let mask = align - 1; - (offset + mask) & !mask -} - -fn function_value_layout() -> MitkiAggregateLayoutAbi { - MitkiAggregateLayoutAbi { size: 8, align: 4, kind: MitkiAggregateKindAbi::Fields(Vec::new()) } -} - -fn function_runtime_layout_abi() -> MitkiAggregateLayoutAbi { - function_value_layout() -} - -#[cfg(test)] -mod tests { - use mitki_abi::{ - AbiTypeKind, CapabilityId, ExecutionDomain, FunctionSignature, SemanticTypeGraph, SigId, - TransportClass, TransportRef, - }; - - use super::{ - MitkiFunctionAbi, MitkiLoweringAbi, MitkiPassingAbi, MitkiValueKind, - function_runtime_layout_abi, - }; - - #[test] - fn from_v2_signature_uses_handle_lanes_for_function_values() { - let mut graph = SemanticTypeGraph::default(); - let int_ty = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let function_ty = graph.push_type(AbiTypeKind::Function { - params: Vec::new(), - result: int_ty, - domain: ExecutionDomain::Runtime, - }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: function_ty, - params: vec![TransportRef { - semantic_type: function_ty, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }], - result: TransportRef { - semantic_type: function_ty, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }, - }); - - let abi = MitkiFunctionAbi::from_v2_signature(&graph, SigId(0)) - .expect("function handles should lower through ABI v2 signatures"); - - assert_eq!(abi.params[0].passing, MitkiPassingAbi::I32); - assert_eq!(abi.result.passing, MitkiPassingAbi::I32); - assert_eq!(abi.params[0].value.kind, MitkiValueKind::Function); - assert_eq!(abi.params[0].value.boundary, MitkiLoweringAbi::I32); - assert_eq!( - abi.params[0].value.runtime, - MitkiLoweringAbi::Aggregate(function_runtime_layout_abi()) - ); - } - - #[test] - fn from_v2_signature_uses_handle_lanes_for_opaque_values() { - let mut graph = SemanticTypeGraph::default(); - let opaque_ty = graph.push_type(AbiTypeKind::Opaque { capability_id: CapabilityId(0) }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: opaque_ty, - params: vec![TransportRef { - semantic_type: opaque_ty, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }], - result: TransportRef { - semantic_type: opaque_ty, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }, - }); - - let abi = MitkiFunctionAbi::from_v2_signature(&graph, SigId(0)) - .expect("opaque handles should lower through ABI v2 signatures"); - - assert_eq!(abi.params[0].passing, MitkiPassingAbi::I32); - assert_eq!(abi.result.passing, MitkiPassingAbi::I32); - assert_eq!(abi.params[0].value.kind, MitkiValueKind::Opaque); - assert_eq!(abi.params[0].value.boundary, MitkiLoweringAbi::I32); - assert_eq!(abi.params[0].value.runtime, MitkiLoweringAbi::Pointer); - } - - #[test] - fn from_v2_signature_uses_canonical_pointers_for_union_values() { - let mut graph = SemanticTypeGraph::default(); - let int_ty = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let string_ty = graph.push_type(AbiTypeKind::String); - let union_ty = graph.push_type(AbiTypeKind::Union { members: vec![int_ty, string_ty] }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: union_ty, - params: vec![TransportRef { - semantic_type: union_ty, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }], - result: TransportRef { - semantic_type: union_ty, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - }); - - let abi = MitkiFunctionAbi::from_v2_signature(&graph, SigId(0)) - .expect("union values should lower through ABI v2 signatures"); - - assert_eq!(abi.params[0].passing, MitkiPassingAbi::Pointer); - assert_eq!(abi.result.passing, MitkiPassingAbi::Pointer); - assert_eq!(abi.params[0].value.kind, MitkiValueKind::Union); - assert_eq!(abi.params[0].value.boundary, MitkiLoweringAbi::Pointer); - assert!(matches!(abi.params[0].value.runtime, MitkiLoweringAbi::Aggregate(_))); - } -} diff --git a/crates/mitki-abi-lower/src/abi_v2.rs b/crates/mitki-abi-lower/src/abi_v2.rs deleted file mode 100644 index 0345960..0000000 --- a/crates/mitki-abi-lower/src/abi_v2.rs +++ /dev/null @@ -1,854 +0,0 @@ -use std::collections::BTreeMap; - -use mitki_abi::{ - AbiTypeKind, BoundaryMemory, EnumVariant, ExecutionDomain, FacetPlan, FacetPlanEntry, - FacetPlanEntryKind, FacetPlanId, FieldNameId, FunctionInstance, - FunctionSignature as AbiFunctionSignature, GenericOrigin, GenericOriginId, InstanceId, - LinkageKind, RecordField, SemanticTypeGraph, SigId, StringId, SymbolId, TransportClass, - TransportRef, TypeFingerprint, TypeId, TypeNode, VariantNameId, WASM_CORE_V2_M32_PROFILE, - WASM_CORE_V2_M32_PROFILE_MAJOR, WASM_CORE_V2_M32_PROFILE_MINOR, encode_semantic_type_graph, - validate_graph_schema, -}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::item::scope::{enum_variants, struct_fields}; -use salsa::plumbing::AsId as _; - -pub const MITKI_ABI_V2_CUSTOM_SECTION: &str = "mitki.abi.v2"; -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BoundaryFunctionMetadata<'db> { - pub logical_name: String, - pub generic_origin_name: Option, - pub wasm_module_name: Option, - pub wasm_field_name: String, - pub param_tys: Vec>, - pub result_ty: Ty<'db>, - pub type_args: Vec>, - pub domain: ExecutionDomain, - pub linkage: LinkageKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BuiltFunctionInstance { - pub signature_id: SigId, - pub symbol_id: SymbolId, - pub wasm_field_name: String, - pub linkage: LinkageKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BuiltAbiV2 { - pub graph: SemanticTypeGraph, - pub functions: Vec, -} - -pub fn build_module_abi_v2<'db>( - db: &'db dyn salsa::Database, - functions: &[BoundaryFunctionMetadata<'db>], -) -> Result { - let mut builder = AbiGraphBuilder::new(db); - let mut built_functions = Vec::with_capacity(functions.len()); - - for function in functions { - let symbol_id = builder.intern_symbol(&function.logical_name); - let function_type = builder.intern_function_type( - &function.param_tys, - function.result_ty, - function.domain, - )?; - let signature_id = SigId(builder.graph.signatures.len() as u32); - let params = function - .param_tys - .iter() - .map(|&ty| builder.transport_ref(ty)) - .collect::, _>>()?; - let result = builder.transport_ref(function.result_ty)?; - builder.graph.signatures.push(AbiFunctionSignature { - id: signature_id, - function_type, - params, - result, - }); - - let wasm_module_name = - function.wasm_module_name.as_ref().map(|name| builder.intern_string(name)); - let wasm_field_name = builder.intern_string(&function.wasm_field_name); - let generic_origin = - function.generic_origin_name.as_deref().map(|name| builder.intern_generic_origin(name)); - let type_args = function - .type_args - .iter() - .map(|&ty| builder.intern_type(ty, None)) - .collect::, _>>()?; - let instance_id = InstanceId(builder.graph.function_instances.len() as u32); - builder.graph.function_instances.push(FunctionInstance { - id: instance_id, - logical_symbol: symbol_id, - generic_origin, - type_args, - signature: signature_id, - domain: function.domain, - linkage: function.linkage, - wasm_module_name, - wasm_field_name: Some(wasm_field_name), - }); - built_functions.push(BuiltFunctionInstance { - signature_id, - symbol_id, - wasm_field_name: function.wasm_field_name.clone(), - linkage: function.linkage, - }); - } - - builder.graph.populate_recursive_groups(); - builder.graph.populate_fingerprints().map_err(|error| error.to_string())?; - builder.graph.normalize_commutative_members().map_err(|error| error.to_string())?; - builder.graph.populate_fingerprints().map_err(|error| error.to_string())?; - finalize_intersections(&mut builder.graph)?; - builder.graph.populate_fingerprints().map_err(|error| error.to_string())?; - builder.graph.recompute_required_features(); - validate_graph_schema(&builder.graph).map_err(|error| error.to_string())?; - Ok(BuiltAbiV2 { graph: builder.graph, functions: built_functions }) -} - -pub fn encode_module_abi_v2(graph: &SemanticTypeGraph) -> anyhow::Result> { - encode_semantic_type_graph(graph) -} - -struct AbiGraphBuilder<'db> { - db: &'db dyn salsa::Database, - graph: SemanticTypeGraph, - strings: BTreeMap, - field_names: BTreeMap, - variant_names: BTreeMap, - symbols: BTreeMap, - generic_origins: BTreeMap, - types: BTreeMap<(u32, u8), TypeId>, -} - -impl<'db> AbiGraphBuilder<'db> { - fn new(db: &'db dyn salsa::Database) -> Self { - let mut graph = SemanticTypeGraph::default(); - let mut strings = BTreeMap::new(); - let profile = StringId(0); - strings.insert(WASM_CORE_V2_M32_PROFILE.to_owned(), profile); - graph.strings.push(WASM_CORE_V2_M32_PROFILE.to_owned()); - graph.transport_profile = profile; - graph.profile_version_major = WASM_CORE_V2_M32_PROFILE_MAJOR; - graph.profile_version_minor = WASM_CORE_V2_M32_PROFILE_MINOR; - let memory = StringId(1); - strings.insert("memory".to_owned(), memory); - graph.strings.push("memory".to_owned()); - graph.boundary_memory = BoundaryMemory { memory_index: 0, export_name: Some(memory) }; - Self { - db, - graph, - strings, - field_names: BTreeMap::new(), - variant_names: BTreeMap::new(), - symbols: BTreeMap::new(), - generic_origins: BTreeMap::new(), - types: BTreeMap::new(), - } - } - - fn intern_string(&mut self, value: &str) -> StringId { - if let Some(&id) = self.strings.get(value) { - return id; - } - let id = StringId(self.graph.strings.len() as u32); - self.graph.strings.push(value.to_owned()); - self.strings.insert(value.to_owned(), id); - id - } - - fn intern_field_name(&mut self, value: &str) -> FieldNameId { - if let Some(&id) = self.field_names.get(value) { - return id; - } - let string_id = self.intern_string(value); - let id = FieldNameId(self.graph.field_names.len() as u32); - self.graph.field_names.push(string_id); - self.field_names.insert(value.to_owned(), id); - id - } - - fn intern_variant_name(&mut self, value: &str) -> VariantNameId { - if let Some(&id) = self.variant_names.get(value) { - return id; - } - let string_id = self.intern_string(value); - let id = VariantNameId(self.graph.variant_names.len() as u32); - self.graph.variant_names.push(string_id); - self.variant_names.insert(value.to_owned(), id); - id - } - - fn intern_symbol(&mut self, value: &str) -> SymbolId { - if let Some(&id) = self.symbols.get(value) { - return id; - } - let string_id = self.intern_string(value); - let id = SymbolId(self.graph.nominal_symbols.len() as u32); - self.graph.nominal_symbols.push(string_id); - self.symbols.insert(value.to_owned(), id); - id - } - - fn intern_generic_origin(&mut self, value: &str) -> GenericOriginId { - if let Some(&id) = self.generic_origins.get(value) { - return id; - } - let symbol = self.intern_symbol(value); - let id = GenericOriginId(self.graph.generic_origins.len() as u32); - self.graph.generic_origins.push(GenericOrigin { id, symbol }); - self.generic_origins.insert(value.to_owned(), id); - id - } - - fn transport_ref(&mut self, ty: Ty<'db>) -> Result { - let semantic_type = self.intern_type(ty, None)?; - Ok(TransportRef { - semantic_type, - transport_class: boundary_transport_class(self.db, ty), - transport_type: None, - }) - } - - fn type_key(&self, ty: Ty<'db>, domain: Option) -> (u32, u8) { - let discriminator = if matches!(ty.kind(self.db), TyKind::Function { .. }) { - domain.unwrap_or(ExecutionDomain::Both).as_bits() - } else { - 0 - }; - (ty_bits(ty), discriminator) - } - - fn intern_function_type( - &mut self, - params: &[Ty<'db>], - result: Ty<'db>, - domain: ExecutionDomain, - ) -> Result { - let function_ty = - Ty::new(self.db, TyKind::Function { inputs: params.to_vec(), output: result }); - self.intern_type(function_ty, Some(domain)) - } - - fn intern_type( - &mut self, - ty: Ty<'db>, - function_domain: Option, - ) -> Result { - self.intern_type_with_bindings(ty, function_domain, &mut BTreeMap::new()) - } - - fn intern_type_with_bindings( - &mut self, - ty: Ty<'db>, - function_domain: Option, - recursive_bindings: &mut BTreeMap, - ) -> Result { - if let TyKind::Var(id) = ty.kind(self.db) - && let Some(&bound) = recursive_bindings.get(id) - { - return Ok(bound); - } - if let TyKind::Rec(id, body) = ty.kind(self.db) { - let key = self.type_key(ty, function_domain); - if let Some(&existing) = self.types.get(&key) { - return Ok(existing); - } - let type_id = TypeId(self.graph.types.len() as u32); - let debug_name = self.intern_debug_name(&ty.display(self.db).to_string()); - self.graph.types.push(TypeNode { - id: type_id, - fingerprint: TypeFingerprint::ZERO, - kind: AbiTypeKind::Unit, - flags: 0, - recursive_group: None, - debug_name: Some(debug_name), - }); - self.types.insert(key, type_id); - let previous = recursive_bindings.insert(*id, type_id); - let kind = - self.lower_type_kind_with_bindings(*body, function_domain, recursive_bindings)?; - if let Some(previous) = previous { - recursive_bindings.insert(*id, previous); - } else { - recursive_bindings.remove(id); - } - self.graph.types[type_id.0 as usize].kind = kind; - return Ok(type_id); - } - let key = self.type_key(ty, function_domain); - if let Some(&id) = self.types.get(&key) { - return Ok(id); - } - let id = TypeId(self.graph.types.len() as u32); - let debug_name = self.intern_debug_name(&ty.display(self.db).to_string()); - self.graph.types.push(TypeNode { - id, - fingerprint: TypeFingerprint::ZERO, - kind: AbiTypeKind::Unit, - flags: 0, - recursive_group: None, - debug_name: Some(debug_name), - }); - self.types.insert(key, id); - let kind = self.lower_type_kind_with_bindings(ty, function_domain, recursive_bindings)?; - self.graph.types[id.0 as usize].kind = kind; - Ok(id) - } - - fn intern_debug_name(&mut self, value: &str) -> mitki_abi::DebugNameId { - let string_id = self.intern_string(value); - let id = mitki_abi::DebugNameId(self.graph.debug_names.len() as u32); - self.graph.debug_names.push(string_id); - id - } - - fn lower_type_kind_with_bindings( - &mut self, - ty: Ty<'db>, - function_domain: Option, - recursive_bindings: &mut BTreeMap, - ) -> Result { - Ok(match ty.kind(self.db) { - TyKind::Bool => AbiTypeKind::Bool, - TyKind::Float => AbiTypeKind::Float { bits: 64 }, - TyKind::Int => AbiTypeKind::Int { signed: true, bits: 32 }, - TyKind::ExactInt(_) => { - return Err(format!( - "cannot encode exact-width integer `{}` into ABI v2 metadata yet", - ty.display(self.db) - )); - } - TyKind::Char => AbiTypeKind::Char, - TyKind::String => AbiTypeKind::String, - TyKind::Pointer { .. } => { - return Err(format!( - "cannot encode raw pointer type `{}` into ABI v2 metadata", - ty.display(self.db) - )); - } - TyKind::Array(elem) => AbiTypeKind::Array { - elem: self.intern_type_with_bindings(*elem, None, recursive_bindings)?, - }, - TyKind::Tuple(elems) => AbiTypeKind::Tuple { - elems: elems - .iter() - .map(|&elem| self.intern_type_with_bindings(elem, None, recursive_bindings)) - .collect::, _>>()?, - }, - TyKind::Record(fields) => { - let mut ordered = fields.clone(); - ordered.sort_by_key(|(name, _)| name.text(self.db).to_owned()); - AbiTypeKind::Record { - fields: ordered - .iter() - .map(|(name, ty)| { - Ok(RecordField { - name: self.intern_field_name(name.text(self.db)), - ty: self.intern_type_with_bindings( - *ty, - None, - recursive_bindings, - )?, - }) - }) - .collect::, String>>()?, - } - } - TyKind::ExternStruct(_) => { - return Err(format!( - "cannot encode extern struct `{}` into ABI v2 metadata", - ty.display(self.db) - )); - } - TyKind::Function { inputs, output } => AbiTypeKind::Function { - params: inputs - .iter() - .map(|&input| self.intern_type_with_bindings(input, None, recursive_bindings)) - .collect::, _>>()?, - result: self.intern_type_with_bindings(*output, None, recursive_bindings)?, - domain: function_domain.unwrap_or(ExecutionDomain::Both), - }, - TyKind::Struct(struct_ty) => { - let nominal = self.intern_symbol(struct_ty.name(self.db).text(self.db)); - AbiTypeKind::Struct { - nominal, - fields: struct_fields(self.db, *struct_ty) - .iter() - .map(|(name, ty)| { - Ok(RecordField { - name: self.intern_field_name(name.text(self.db)), - ty: self.intern_type_with_bindings( - *ty, - None, - recursive_bindings, - )?, - }) - }) - .collect::, String>>()?, - } - } - TyKind::Enum(enum_ty) => { - let nominal = self.intern_symbol(enum_ty.name(self.db).text(self.db)); - AbiTypeKind::Enum { - nominal, - variants: enum_variants(self.db, *enum_ty) - .iter() - .map(|(name, fields)| { - Ok(EnumVariant { - name: self.intern_variant_name(name.text(self.db)), - fields: fields - .iter() - .map(|ty| { - self.intern_type_with_bindings( - *ty, - None, - recursive_bindings, - ) - }) - .collect::, _>>()?, - }) - }) - .collect::, String>>()?, - } - } - TyKind::Union(members) => AbiTypeKind::Union { - members: members - .iter() - .map(|&member| self.intern_type_with_bindings(member, None, recursive_bindings)) - .collect::, _>>()?, - }, - TyKind::Inter(members) => { - let members = members - .iter() - .map(|&member| self.intern_type_with_bindings(member, None, recursive_bindings)) - .collect::, _>>()?; - let carrier = *members.first().ok_or_else(|| { - format!("intersection `{}` does not have a carrier member", ty.display(self.db)) - })?; - AbiTypeKind::Intersection { members, carrier, facet_plan: None } - } - TyKind::Rec(id, body) => { - let existing = recursive_bindings.get(id).copied(); - let kind = - self.lower_type_kind_with_bindings(*body, function_domain, recursive_bindings)?; - if let Some(existing) = existing { - recursive_bindings.insert(*id, existing); - } else { - recursive_bindings.remove(id); - } - kind - } - TyKind::Var(id) if recursive_bindings.contains_key(id) => { - return Err(format!( - "ABI v2 metadata builder lost recursive binding for `{}`", - ty.display(self.db) - )); - } - TyKind::Unknown | TyKind::Var(_) => { - return Err(format!( - "cannot encode unresolved type `{}` into ABI v2 metadata", - ty.display(self.db) - )); - } - }) - } -} - -fn finalize_intersections(graph: &mut SemanticTypeGraph) -> Result<(), String> { - let fingerprints = graph.types.iter().map(|node| node.fingerprint).collect::>(); - - for index in 0..graph.types.len() { - let AbiTypeKind::Intersection { members, .. } = &graph.types[index].kind else { - continue; - }; - let mut normalized_members = members.clone(); - normalized_members.sort_by_key(|member| { - (fingerprints.get(member.0 as usize).copied().unwrap_or(TypeFingerprint::ZERO), *member) - }); - normalized_members.dedup_by_key(|member| { - fingerprints.get(member.0 as usize).copied().unwrap_or(TypeFingerprint::ZERO) - }); - - if normalized_members.is_empty() { - return Err(format!( - "intersection type `{}` did not contain any members after normalization", - index - )); - } - - let chosen_carrier = choose_intersection_carrier(graph, &normalized_members, &fingerprints) - .ok_or_else(|| { - format!("intersection type `{}` did not have a valid carrier member", index) - })?; - - let mut entries = Vec::with_capacity(normalized_members.len()); - let mut live_entries = 0usize; - for &member in &normalized_members { - let kind = if member == chosen_carrier { - FacetPlanEntryKind::Erased - } else if matches!( - semantic_transport_class(graph, member)?, - TransportClass::CapabilityHandle - ) { - live_entries += 1; - FacetPlanEntryKind::HandleFacet - } else { - live_entries += 1; - FacetPlanEntryKind::ValueFacet - }; - entries.push(FacetPlanEntry { member, kind }); - } - - let plan_id = if live_entries == 0 { - None - } else { - let id = FacetPlanId(graph.facet_plans.len() as u32); - graph.facet_plans.push(FacetPlan { id, entries }); - Some(id) - }; - if let AbiTypeKind::Intersection { members, carrier, facet_plan } = - &mut graph.types[index].kind - { - *members = normalized_members; - *carrier = chosen_carrier; - *facet_plan = plan_id; - }; - } - - for index in 0..graph.signatures.len() { - let mut params = graph.signatures[index].params.clone(); - for transport in &mut params { - finalize_transport_ref(graph, transport)?; - } - let mut result = graph.signatures[index].result.clone(); - finalize_transport_ref(graph, &mut result)?; - graph.signatures[index].params = params; - graph.signatures[index].result = result; - } - - Ok(()) -} - -fn finalize_transport_ref( - graph: &SemanticTypeGraph, - transport: &mut TransportRef, -) -> Result<(), String> { - let AbiTypeKind::Intersection { carrier, facet_plan, .. } = - &type_kind(graph, transport.semantic_type)?.kind - else { - return Ok(()); - }; - let live_entries = facet_plan - .and_then(|plan_id| graph.facet_plans.get(plan_id.0 as usize)) - .map_or(0, |plan| { - plan.entries.iter().filter(|entry| entry.kind != FacetPlanEntryKind::Erased).count() - }); - transport.transport_type = Some(*carrier); - transport.transport_class = if live_entries == 0 { - semantic_transport_class(graph, *carrier)? - } else { - TransportClass::CanonicalValue - }; - Ok(()) -} - -fn choose_intersection_carrier( - graph: &SemanticTypeGraph, - members: &[TypeId], - fingerprints: &[TypeFingerprint], -) -> Option { - members.iter().copied().min_by_key(|member| { - ( - intersection_carrier_priority(graph, *member), - fingerprints.get(member.0 as usize).copied().unwrap_or(TypeFingerprint::ZERO), - *member, - ) - }) -} - -fn intersection_carrier_priority(graph: &SemanticTypeGraph, ty: TypeId) -> u8 { - match type_kind(graph, ty).map(|node| &node.kind) { - Ok(AbiTypeKind::Record { .. }) => 0, - Ok(AbiTypeKind::Struct { .. }) => 1, - Ok(AbiTypeKind::Tuple { .. }) => 2, - Ok(AbiTypeKind::Enum { .. }) => 3, - Ok(AbiTypeKind::Union { .. }) => 4, - Ok(AbiTypeKind::Array { .. }) => 5, - Ok(AbiTypeKind::String) => 6, - Ok(AbiTypeKind::Unit) - | Ok(AbiTypeKind::Bool) - | Ok(AbiTypeKind::Int { .. }) - | Ok(AbiTypeKind::Float { .. }) - | Ok(AbiTypeKind::Char) => 7, - Ok(AbiTypeKind::Function { .. }) => 8, - Ok(AbiTypeKind::Opaque { .. }) => 9, - Ok(AbiTypeKind::Intersection { .. }) => 10, - Err(_) => 11, - } -} - -fn semantic_transport_class( - graph: &SemanticTypeGraph, - ty: TypeId, -) -> Result { - let node = type_kind(graph, ty)?; - Ok(match &node.kind { - AbiTypeKind::Unit - | AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Float { .. } - | AbiTypeKind::Char => TransportClass::Immediate, - AbiTypeKind::Enum { variants, .. } - if variants.iter().all(|variant| variant.fields.is_empty()) => - { - TransportClass::Immediate - } - AbiTypeKind::Function { .. } | AbiTypeKind::Opaque { .. } => { - TransportClass::CapabilityHandle - } - AbiTypeKind::Intersection { carrier, facet_plan, .. } => { - let live_entries = facet_plan - .and_then(|plan_id| graph.facet_plans.get(plan_id.0 as usize)) - .map_or(0, |plan| { - plan.entries - .iter() - .filter(|entry| entry.kind != FacetPlanEntryKind::Erased) - .count() - }); - if live_entries == 0 { - semantic_transport_class(graph, *carrier)? - } else { - TransportClass::CanonicalValue - } - } - AbiTypeKind::String - | AbiTypeKind::Array { .. } - | AbiTypeKind::Tuple { .. } - | AbiTypeKind::Record { .. } - | AbiTypeKind::Struct { .. } - | AbiTypeKind::Union { .. } => TransportClass::CanonicalValue, - AbiTypeKind::Enum { .. } => TransportClass::CanonicalValue, - }) -} - -fn type_kind(graph: &SemanticTypeGraph, ty: TypeId) -> Result<&TypeNode, String> { - graph.types.get(ty.0 as usize).ok_or_else(|| format!("unknown ABI v2 type `{}`", ty.0)) -} - -fn ty_bits(ty: Ty<'_>) -> u32 { - u32::try_from(ty.as_id().as_bits()).expect("type id should fit into u32") -} - -fn boundary_transport_class(db: &dyn salsa::Database, ty: Ty<'_>) -> TransportClass { - match ty.kind(db) { - TyKind::Bool | TyKind::Float | TyKind::Int | TyKind::Char => TransportClass::Immediate, - TyKind::Tuple(items) if items.is_empty() => TransportClass::Immediate, - TyKind::Enum(enum_ty) if is_nullary_enum_ty(db, *enum_ty) => TransportClass::Immediate, - TyKind::Function { .. } => TransportClass::CapabilityHandle, - TyKind::Inter(members) => boundary_intersection_transport_class(db, members), - TyKind::ExactInt(_) | TyKind::Pointer { .. } | TyKind::ExternStruct(_) => { - TransportClass::CanonicalValue - } - TyKind::String - | TyKind::Array(_) - | TyKind::Tuple(_) - | TyKind::Record(_) - | TyKind::Union(_) - | TyKind::Rec(_, _) - | TyKind::Struct(_) - | TyKind::Enum(_) - | TyKind::Unknown - | TyKind::Var(_) => TransportClass::CanonicalValue, - } -} - -fn boundary_intersection_transport_class( - db: &dyn salsa::Database, - members: &[Ty<'_>], -) -> TransportClass { - let Some(carrier_index) = choose_intersection_carrier_index(db, members) else { - return TransportClass::CanonicalValue; - }; - let carrier = members[carrier_index]; - let carrier_bits = ty_bits(carrier); - let mut seen = std::collections::BTreeSet::new(); - let mut live_members = 0usize; - - for &member in members { - let bits = ty_bits(member); - if !seen.insert(bits) { - continue; - } - if bits == carrier_bits { - continue; - } - live_members += 1; - } - - if live_members == 0 { - boundary_transport_class(db, carrier) - } else { - TransportClass::CanonicalValue - } -} - -fn choose_intersection_carrier_index( - db: &dyn salsa::Database, - members: &[Ty<'_>], -) -> Option { - members - .iter() - .enumerate() - .min_by_key(|(_, ty)| (transport_carrier_priority(db, **ty), ty_bits(**ty))) - .map(|(index, _)| index) -} - -fn transport_carrier_priority(db: &dyn salsa::Database, ty: Ty<'_>) -> u8 { - match ty.kind(db) { - TyKind::Record(_) => 0, - TyKind::ExternStruct(_) => 1, - TyKind::Struct(_) => 2, - TyKind::Tuple(_) => 3, - TyKind::Enum(_) => 4, - TyKind::Union(_) => 5, - TyKind::Array(_) => 6, - TyKind::String => 7, - TyKind::Bool | TyKind::Float | TyKind::Int | TyKind::ExactInt(_) | TyKind::Char => 8, - TyKind::Pointer { .. } => 9, - TyKind::Function { .. } => 10, - TyKind::Unknown | TyKind::Var(_) | TyKind::Rec(_, _) | TyKind::Inter(_) => 11, - } -} - -fn is_nullary_enum_ty<'db>( - db: &'db dyn salsa::Database, - enum_ty: mitki_hir::ty::EnumTy<'db>, -) -> bool { - enum_variants(db, enum_ty).iter().all(|(_, fields)| fields.is_empty()) -} - -#[cfg(test)] -mod tests { - use mitki_abi::{ - AbiTypeKind, REQUIRED_FEATURE_RECURSIVE_CANONICAL, REQUIRED_FEATURE_UNION_TRANSPORT, - TransportClass, - }; - use mitki_db::RootDatabase; - use mitki_hir::ty::{Ty, TyKind}; - - use super::{AbiGraphBuilder, finalize_intersections, finalize_transport_ref}; - - #[test] - fn builder_lowers_recursive_types_into_graph_edges() { - let db = RootDatabase::default(); - let mut builder = AbiGraphBuilder::new(&db); - let recursive = - Ty::new(&db, TyKind::Rec(0, Ty::new(&db, TyKind::Array(Ty::new(&db, TyKind::Var(0)))))); - - let type_id = builder.intern_type(recursive, None).expect("recursive type should lower"); - builder.graph.populate_recursive_groups(); - - assert!(matches!( - builder.graph.types[type_id.0 as usize].kind, - AbiTypeKind::Array { elem } if elem == type_id - )); - assert_eq!(builder.graph.recursive_groups.len(), 1); - assert_eq!( - builder.graph.types[type_id.0 as usize].recursive_group, - Some(mitki_abi::RecursiveGroupId(0)) - ); - } - - #[test] - fn builder_marks_required_features_from_boundary_transports() { - let db = RootDatabase::default(); - let mut builder = AbiGraphBuilder::new(&db); - let recursive = - Ty::new(&db, TyKind::Rec(0, Ty::new(&db, TyKind::Array(Ty::new(&db, TyKind::Var(0)))))); - let union = Ty::new(&db, TyKind::Union(vec![Ty::new(&db, TyKind::Int), recursive])); - let function_type = builder - .intern_function_type( - &[union], - Ty::new(&db, TyKind::Int), - mitki_abi::ExecutionDomain::Runtime, - ) - .expect("function type"); - let result = builder.transport_ref(Ty::new(&db, TyKind::Int)).expect("result transport"); - let param = builder.transport_ref(union).expect("transport should build"); - - assert_eq!(param.transport_class, TransportClass::CanonicalValue); - builder.graph.signatures.push(mitki_abi::FunctionSignature { - id: mitki_abi::SigId(0), - function_type, - params: vec![param], - result, - }); - builder.graph.populate_recursive_groups(); - builder.graph.recompute_required_features(); - - assert_eq!( - builder.graph.required_features, - REQUIRED_FEATURE_RECURSIVE_CANONICAL | REQUIRED_FEATURE_UNION_TRANSPORT - ); - } - - #[test] - fn intersections_choose_deterministic_carriers_and_facet_plans() { - let db = RootDatabase::default(); - let mut builder = AbiGraphBuilder::new(&db); - let intersection = Ty::new( - &db, - TyKind::Inter(vec![ - Ty::new(&db, TyKind::Record(vec![])), - Ty::new( - &db, - TyKind::Function { - inputs: vec![Ty::new(&db, TyKind::Int)], - output: Ty::new(&db, TyKind::Int), - }, - ), - ]), - ); - - let semantic_type = builder.intern_type(intersection, None).expect("intersection type"); - let mut transport = builder.transport_ref(intersection).expect("transport should build"); - builder.graph.populate_recursive_groups(); - builder.graph.populate_fingerprints().expect("fingerprints"); - builder.graph.normalize_commutative_members().expect("normalized members"); - builder.graph.populate_fingerprints().expect("fingerprints"); - finalize_intersections(&mut builder.graph).expect("intersection finalization"); - - finalize_transport_ref(&builder.graph, &mut transport).expect("transport finalization"); - - let AbiTypeKind::Intersection { carrier, facet_plan, .. } = - &builder.graph.types[semantic_type.0 as usize].kind - else { - panic!("expected intersection type"); - }; - assert_eq!(transport.transport_class, TransportClass::CanonicalValue); - assert_eq!(transport.transport_type, Some(*carrier)); - assert!(matches!(builder.graph.types[carrier.0 as usize].kind, AbiTypeKind::Record { .. })); - let plan_id = facet_plan.expect("expected facet plan"); - let plan = &builder.graph.facet_plans[plan_id.0 as usize]; - assert_eq!(plan.entries.len(), 2); - assert_eq!( - plan.entries - .iter() - .filter(|entry| entry.kind == mitki_abi::FacetPlanEntryKind::Erased) - .count(), - 1 - ); - assert_eq!( - plan.entries - .iter() - .filter(|entry| entry.kind == mitki_abi::FacetPlanEntryKind::HandleFacet) - .count(), - 1 - ); - } -} diff --git a/crates/mitki-abi-lower/src/lib.rs b/crates/mitki-abi-lower/src/lib.rs deleted file mode 100644 index c54529a..0000000 --- a/crates/mitki-abi-lower/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod abi; -mod abi_v2; -mod transport; - -pub use abi::{ - LoweringMode, MitkiAggregateKindAbi, MitkiAggregateLayoutAbi, MitkiArrayLayoutAbi, - MitkiEnumLayoutAbi, MitkiFieldAbi, MitkiFunctionAbi, MitkiLoweringAbi, MitkiParamAbi, - MitkiPassingAbi, MitkiPointeeAbi, MitkiResultAbi, MitkiValueAbi, MitkiValueKind, - MitkiVariantAbi, -}; -pub use abi_v2::{ - BoundaryFunctionMetadata, BuiltAbiV2, BuiltFunctionInstance, MITKI_ABI_V2_CUSTOM_SECTION, - build_module_abi_v2, encode_module_abi_v2, -}; -pub use transport::{BoundaryTransportPlan, boundary_transport_class}; diff --git a/crates/mitki-abi-lower/src/transport.rs b/crates/mitki-abi-lower/src/transport.rs deleted file mode 100644 index e21191c..0000000 --- a/crates/mitki-abi-lower/src/transport.rs +++ /dev/null @@ -1,98 +0,0 @@ -use mitki_abi::TransportClass; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::item::scope::enum_variants; -use salsa::plumbing::AsId as _; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct BoundaryTransportPlan { - pub transport_class: TransportClass, -} - -pub fn boundary_transport_class(db: &dyn salsa::Database, ty: Ty<'_>) -> TransportClass { - match ty.kind(db) { - TyKind::Bool | TyKind::Float | TyKind::Int | TyKind::Char => TransportClass::Immediate, - TyKind::Tuple(items) if items.is_empty() => TransportClass::Immediate, - TyKind::Enum(enum_ty) if is_nullary_enum(db, *enum_ty) => TransportClass::Immediate, - TyKind::Function { .. } => TransportClass::CapabilityHandle, - TyKind::Inter(members) => boundary_intersection_transport_class(db, members), - TyKind::ExactInt(_) | TyKind::Pointer { .. } | TyKind::ExternStruct(_) => { - TransportClass::CanonicalValue - } - TyKind::String - | TyKind::Array(_) - | TyKind::Tuple(_) - | TyKind::Record(_) - | TyKind::Union(_) - | TyKind::Rec(_, _) - | TyKind::Struct(_) - | TyKind::Enum(_) - | TyKind::Unknown - | TyKind::Var(_) => TransportClass::CanonicalValue, - } -} - -fn boundary_intersection_transport_class( - db: &dyn salsa::Database, - members: &[Ty<'_>], -) -> TransportClass { - let Some(carrier_index) = choose_intersection_carrier_index(db, members) else { - return TransportClass::CanonicalValue; - }; - let carrier = members[carrier_index]; - let carrier_bits = ty_bits(carrier); - let mut seen = std::collections::BTreeSet::new(); - let mut live_members = 0usize; - - for &member in members { - let bits = ty_bits(member); - if !seen.insert(bits) { - continue; - } - if bits == carrier_bits { - continue; - } - live_members += 1; - } - - if live_members == 0 { - boundary_transport_class(db, carrier) - } else { - TransportClass::CanonicalValue - } -} - -fn choose_intersection_carrier_index( - db: &dyn salsa::Database, - members: &[Ty<'_>], -) -> Option { - members - .iter() - .enumerate() - .min_by_key(|(_, ty)| (intersection_carrier_priority(db, **ty), ty_bits(**ty))) - .map(|(index, _)| index) -} - -fn intersection_carrier_priority(db: &dyn salsa::Database, ty: Ty<'_>) -> u8 { - match ty.kind(db) { - TyKind::Record(_) => 0, - TyKind::ExternStruct(_) => 1, - TyKind::Struct(_) => 2, - TyKind::Tuple(_) => 3, - TyKind::Enum(_) => 4, - TyKind::Union(_) => 5, - TyKind::Array(_) => 6, - TyKind::String => 7, - TyKind::Bool | TyKind::Float | TyKind::Int | TyKind::ExactInt(_) | TyKind::Char => 8, - TyKind::Pointer { .. } => 9, - TyKind::Function { .. } => 10, - TyKind::Unknown | TyKind::Var(_) | TyKind::Rec(_, _) | TyKind::Inter(_) => 11, - } -} - -fn is_nullary_enum(db: &dyn salsa::Database, enum_ty: mitki_hir::ty::EnumTy<'_>) -> bool { - enum_variants(db, enum_ty).iter().all(|(_, fields)| fields.is_empty()) -} - -fn ty_bits(ty: Ty<'_>) -> u32 { - u32::try_from(ty.as_id().as_bits()).expect("type id should fit into u32") -} diff --git a/crates/mitki-abi/Cargo.toml b/crates/mitki-abi/Cargo.toml deleted file mode 100644 index c853ee0..0000000 --- a/crates/mitki-abi/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "mitki-abi" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -blake3.workspace = true -wasmparser.workspace = true - -[dev-dependencies] -expect-test = "1.5" diff --git a/crates/mitki-abi/src/canonical.rs b/crates/mitki-abi/src/canonical.rs deleted file mode 100644 index 52100d4..0000000 --- a/crates/mitki-abi/src/canonical.rs +++ /dev/null @@ -1,730 +0,0 @@ -use anyhow::{anyhow, bail}; - -use crate::codec::{BinaryReader, BinaryWriter}; -use crate::metadata::{ABI_SEMANTIC_MAJOR, ABI_SEMANTIC_MINOR, TypeId}; - -pub const CANONICAL_BLOB_MAGIC: [u8; 8] = *b"MTKCV2\0\0"; -pub const CANONICAL_BLOB_ENCODING_VERSION: u16 = 2; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum TransportClass { - Immediate, - CanonicalValue, - CapabilityHandle, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum AbiValue { - Immediate(AbiScalar), - Handle { type_id: TypeId, handle_id: u32 }, - Canonical { transport_type: TypeId, graph: CanonicalGraph }, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum AbiScalar { - Unit, - Bool(bool), - Int { signed: bool, bits: u16, value: i64 }, - Float { bits: u16, raw_bits: u64 }, - Char { unicode_scalar: u32 }, - EnumTag { type_id: TypeId, variant_index: u32 }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct NodeId(pub u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct HandleSlotId(pub u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CanonicalGraph { - pub root: ValueRef, - pub nodes: Vec, - pub handles: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct HandleSlot { - pub type_id: TypeId, - pub handle_id: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ValueRef { - InlineScalar(AbiScalar), - NodeRef(NodeId), - HandleRef(HandleSlotId), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PackedScalarKind { - Bool, - I32, - I64, - F32, - F64, - Char, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ArrayElements { - PackedScalars { kind: PackedScalarKind, len: u32, bytes: Vec }, - Values(Vec), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum CanonicalNode { - String { transport_type: TypeId, value: String }, - Array { transport_type: TypeId, elements: ArrayElements }, - Tuple { transport_type: TypeId, fields: Vec }, - Record { transport_type: TypeId, fields: Vec }, - Struct { transport_type: TypeId, fields: Vec }, - Enum { transport_type: TypeId, variant_index: u32, fields: Vec }, - Union { transport_type: TypeId, arm_index: u32, payload: ValueRef }, - Intersection { transport_type: TypeId, carrier: ValueRef, facets: Vec }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CanonicalNodeKind { - String, - Array, - Tuple, - Record, - Struct, - Enum, - Union, - Intersection, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CanonicalBlobHeader { - pub transport_type: TypeId, - pub total_byte_len: u32, - pub node_count: u32, - pub handle_slot_count: u32, - pub flags: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CanonicalNodeHeader { - pub transport_type: TypeId, - pub kind: CanonicalNodeKind, - pub aux0: u32, - pub aux1: u32, - pub child_count: u32, - pub payload_len: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CanonicalBlobView<'a> { - bytes: &'a [u8], - header: CanonicalBlobHeader, - root: ValueRef, - nodes: Vec, - handles: Vec, - payload_ranges: Vec>, -} - -impl CanonicalGraph { - pub fn new(root: ValueRef) -> Self { - Self { root, nodes: Vec::new(), handles: Vec::new() } - } -} - -impl CanonicalNode { - pub fn kind(&self) -> CanonicalNodeKind { - match self { - Self::String { .. } => CanonicalNodeKind::String, - Self::Array { .. } => CanonicalNodeKind::Array, - Self::Tuple { .. } => CanonicalNodeKind::Tuple, - Self::Record { .. } => CanonicalNodeKind::Record, - Self::Struct { .. } => CanonicalNodeKind::Struct, - Self::Enum { .. } => CanonicalNodeKind::Enum, - Self::Union { .. } => CanonicalNodeKind::Union, - Self::Intersection { .. } => CanonicalNodeKind::Intersection, - } - } - - pub fn transport_type(&self) -> TypeId { - match self { - Self::String { transport_type, .. } - | Self::Array { transport_type, .. } - | Self::Tuple { transport_type, .. } - | Self::Record { transport_type, .. } - | Self::Struct { transport_type, .. } - | Self::Enum { transport_type, .. } - | Self::Union { transport_type, .. } - | Self::Intersection { transport_type, .. } => *transport_type, - } - } -} - -pub fn encode_canonical_blob(value: &AbiValue) -> anyhow::Result> { - let AbiValue::Canonical { transport_type, graph } = value else { - bail!("canonical blob encoding expects AbiValue::Canonical"); - }; - - let mut writer = BinaryWriter::new(); - writer.bytes(&CANONICAL_BLOB_MAGIC); - writer.u16(ABI_SEMANTIC_MAJOR); - writer.u16(ABI_SEMANTIC_MINOR); - writer.u16(CANONICAL_BLOB_ENCODING_VERSION); - writer.u32(0); - writer.u32(transport_type.0); - writer.u32(0); - writer.u32(graph.nodes.len() as u32); - writer.u32(graph.handles.len() as u32); - encode_value_ref(&mut writer, &graph.root); - for node in &graph.nodes { - encode_node(&mut writer, node)?; - } - writer.len(graph.handles.len()); - for handle in &graph.handles { - writer.u32(handle.type_id.0); - writer.u32(handle.handle_id); - } - - let mut bytes = writer.into_bytes(); - let total = u32::try_from(bytes.len()) - .map_err(|_error| anyhow!("canonical blob exceeded u32 length"))?; - bytes[22..26].copy_from_slice(&total.to_le_bytes()); - Ok(bytes) -} - -pub fn decode_canonical_blob(bytes: &[u8]) -> anyhow::Result { - let view = CanonicalBlobView::parse(bytes)?; - Ok(AbiValue::Canonical { transport_type: view.header.transport_type, graph: view.decode()? }) -} - -impl<'a> CanonicalBlobView<'a> { - pub fn parse(bytes: &'a [u8]) -> anyhow::Result { - let mut reader = BinaryReader::new(bytes); - if reader.fixed::<8>()? != CANONICAL_BLOB_MAGIC { - bail!("invalid canonical blob magic"); - } - let abi_major = reader.u16()?; - let abi_minor = reader.u16()?; - if abi_major != ABI_SEMANTIC_MAJOR || abi_minor != ABI_SEMANTIC_MINOR { - bail!("unsupported canonical blob ABI version {abi_major}.{abi_minor}"); - } - let encoding = reader.u16()?; - if encoding != CANONICAL_BLOB_ENCODING_VERSION { - bail!("unsupported canonical blob encoding version `{encoding}`"); - } - let flags = reader.u32()?; - let transport_type = TypeId(reader.u32()?); - let total_byte_len = reader.u32()?; - if total_byte_len as usize != bytes.len() { - bail!("canonical blob length header did not match the actual buffer size"); - } - let node_count = reader.u32()?; - let handle_slot_count = reader.u32()?; - let root = decode_value_ref(&mut reader)?; - let mut nodes = Vec::with_capacity(node_count as usize); - let mut payload_ranges = Vec::with_capacity(node_count as usize); - for _ in 0..node_count { - let node_start = reader.offset(); - let transport_type = TypeId(reader.u32()?); - let kind = decode_node_kind(reader.u8()?)?; - let aux0 = reader.u32()?; - let aux1 = reader.u32()?; - let child_count = reader.u32()?; - let payload_len = reader.u32()?; - for _ in 0..child_count { - let _ = decode_value_ref(&mut reader)?; - } - let payload_start = reader.offset(); - let _payload = reader.bytes(payload_len as usize)?; - let payload_end = reader.offset(); - let _ = node_start; - nodes.push(CanonicalNodeHeader { - transport_type, - kind, - aux0, - aux1, - child_count, - payload_len, - }); - payload_ranges.push(payload_start..payload_end); - } - let declared_handles = reader.len()? as u32; - if declared_handles != handle_slot_count { - bail!("canonical blob handle count header did not match encoded handle table"); - } - let mut handles = Vec::with_capacity(handle_slot_count as usize); - for _ in 0..handle_slot_count { - handles.push(HandleSlot { type_id: TypeId(reader.u32()?), handle_id: reader.u32()? }); - } - reader.ensure_finished()?; - Ok(Self { - bytes, - header: CanonicalBlobHeader { - transport_type, - total_byte_len, - node_count, - handle_slot_count, - flags, - }, - root, - nodes, - handles, - payload_ranges, - }) - } - - pub fn header(&self) -> &CanonicalBlobHeader { - &self.header - } - - pub fn root(&self) -> &ValueRef { - &self.root - } - - pub fn nodes(&self) -> &[CanonicalNodeHeader] { - &self.nodes - } - - pub fn handles(&self) -> &[HandleSlot] { - &self.handles - } - - pub fn payload_bytes(&self, node: usize) -> Option<&'a [u8]> { - self.payload_ranges.get(node).and_then(|range| self.bytes.get(range.clone())) - } - - pub fn decode(&self) -> anyhow::Result { - let mut reader = BinaryReader::new(self.bytes); - let _ = reader.fixed::<8>()?; - let _ = reader.u16()?; - let _ = reader.u16()?; - let _ = reader.u16()?; - let _ = reader.u32()?; - let _ = reader.u32()?; - let _ = reader.u32()?; - let node_count = reader.u32()?; - let handle_count = reader.u32()?; - let root = decode_value_ref(&mut reader)?; - let mut nodes = Vec::with_capacity(node_count as usize); - for _ in 0..node_count { - nodes.push(decode_node(&mut reader)?); - } - let table_count = reader.len()? as u32; - if table_count != handle_count { - bail!("canonical blob handle table count changed during decode"); - } - let mut handles = Vec::with_capacity(handle_count as usize); - for _ in 0..handle_count { - handles.push(HandleSlot { type_id: TypeId(reader.u32()?), handle_id: reader.u32()? }); - } - Ok(CanonicalGraph { root, nodes, handles }) - } -} - -fn encode_node(writer: &mut BinaryWriter, node: &CanonicalNode) -> anyhow::Result<()> { - writer.u32(node.transport_type().0); - writer.u8(match node.kind() { - CanonicalNodeKind::String => 0, - CanonicalNodeKind::Array => 1, - CanonicalNodeKind::Tuple => 2, - CanonicalNodeKind::Record => 3, - CanonicalNodeKind::Struct => 4, - CanonicalNodeKind::Enum => 5, - CanonicalNodeKind::Union => 6, - CanonicalNodeKind::Intersection => 7, - }); - - match node { - CanonicalNode::String { value, .. } => { - writer.u32(value.len() as u32); - writer.u32(0); - writer.u32(0); - writer.u32(value.len() as u32); - writer.bytes(value.as_bytes()); - } - CanonicalNode::Array { elements, .. } => match elements { - ArrayElements::PackedScalars { kind, len, bytes } => { - writer.u32(*len); - writer.u32(encode_packed_scalar_kind(*kind) as u32); - writer.u32(0); - writer.u32(bytes.len() as u32); - writer.bytes(bytes); - } - ArrayElements::Values(values) => { - writer.u32(values.len() as u32); - writer.u32(u32::MAX); - writer.u32(values.len() as u32); - writer.u32(0); - for value in values { - encode_value_ref(writer, value); - } - } - }, - CanonicalNode::Tuple { fields, .. } - | CanonicalNode::Record { fields, .. } - | CanonicalNode::Struct { fields, .. } => { - writer.u32(0); - writer.u32(0); - writer.u32(fields.len() as u32); - writer.u32(0); - for field in fields { - encode_value_ref(writer, field); - } - } - CanonicalNode::Enum { variant_index, fields, .. } => { - writer.u32(*variant_index); - writer.u32(0); - writer.u32(fields.len() as u32); - writer.u32(0); - for field in fields { - encode_value_ref(writer, field); - } - } - CanonicalNode::Union { arm_index, payload, .. } => { - writer.u32(*arm_index); - writer.u32(0); - writer.u32(1); - writer.u32(0); - encode_value_ref(writer, payload); - } - CanonicalNode::Intersection { carrier, facets, .. } => { - writer.u32(facets.len() as u32); - writer.u32(0); - writer.u32(1 + facets.len() as u32); - writer.u32(0); - encode_value_ref(writer, carrier); - for facet in facets { - encode_value_ref(writer, facet); - } - } - } - Ok(()) -} - -fn decode_node(reader: &mut BinaryReader<'_>) -> anyhow::Result { - let transport_type = TypeId(reader.u32()?); - let kind = decode_node_kind(reader.u8()?)?; - let aux0 = reader.u32()?; - let aux1 = reader.u32()?; - let child_count = reader.u32()?; - let payload_len = reader.u32()?; - Ok(match kind { - CanonicalNodeKind::String => { - if child_count != 0 { - bail!("string canonical node cannot have child refs"); - } - let bytes = reader.bytes(payload_len as usize)?; - CanonicalNode::String { - transport_type, - value: String::from_utf8(bytes.to_vec()) - .map_err(|_error| anyhow!("canonical string payload was not valid UTF-8"))?, - } - } - CanonicalNodeKind::Array => { - if aux1 == u32::MAX { - let mut values = Vec::with_capacity(child_count as usize); - for _ in 0..child_count { - values.push(decode_value_ref(reader)?); - } - if payload_len != 0 { - let _ = reader.bytes(payload_len as usize)?; - } - CanonicalNode::Array { transport_type, elements: ArrayElements::Values(values) } - } else { - if child_count != 0 { - bail!("packed scalar array node cannot have child refs"); - } - let bytes = reader.bytes(payload_len as usize)?.to_vec(); - CanonicalNode::Array { - transport_type, - elements: ArrayElements::PackedScalars { - kind: decode_packed_scalar_kind(aux1 as u8)?, - len: aux0, - bytes, - }, - } - } - } - CanonicalNodeKind::Tuple => CanonicalNode::Tuple { - transport_type, - fields: decode_value_refs(reader, child_count as usize)?, - }, - CanonicalNodeKind::Record => CanonicalNode::Record { - transport_type, - fields: decode_value_refs(reader, child_count as usize)?, - }, - CanonicalNodeKind::Struct => CanonicalNode::Struct { - transport_type, - fields: decode_value_refs(reader, child_count as usize)?, - }, - CanonicalNodeKind::Enum => CanonicalNode::Enum { - transport_type, - variant_index: aux0, - fields: decode_value_refs(reader, child_count as usize)?, - }, - CanonicalNodeKind::Union => { - if child_count != 1 { - bail!("canonical union node must encode exactly one payload ref"); - } - CanonicalNode::Union { - transport_type, - arm_index: aux0, - payload: decode_value_ref(reader)?, - } - } - CanonicalNodeKind::Intersection => { - if child_count == 0 { - bail!("canonical intersection node must encode a carrier ref"); - } - let carrier = decode_value_ref(reader)?; - let facets = decode_value_refs(reader, child_count.saturating_sub(1) as usize)?; - if aux0 != facets.len() as u32 { - bail!("canonical intersection node facet count did not match aux data"); - } - CanonicalNode::Intersection { transport_type, carrier, facets } - } - }) -} - -fn decode_value_refs(reader: &mut BinaryReader<'_>, len: usize) -> anyhow::Result> { - (0..len).map(|_| decode_value_ref(reader)).collect() -} - -fn encode_value_ref(writer: &mut BinaryWriter, value: &ValueRef) { - match value { - ValueRef::InlineScalar(scalar) => { - writer.u8(0); - encode_scalar(writer, scalar); - } - ValueRef::NodeRef(id) => { - writer.u8(1); - writer.u32(id.0); - } - ValueRef::HandleRef(id) => { - writer.u8(2); - writer.u32(id.0); - } - } -} - -fn decode_value_ref(reader: &mut BinaryReader<'_>) -> anyhow::Result { - Ok(match reader.u8()? { - 0 => ValueRef::InlineScalar(decode_scalar(reader)?), - 1 => ValueRef::NodeRef(NodeId(reader.u32()?)), - 2 => ValueRef::HandleRef(HandleSlotId(reader.u32()?)), - value => bail!("unknown canonical value-ref tag `{value}`"), - }) -} - -fn encode_scalar(writer: &mut BinaryWriter, scalar: &AbiScalar) { - match scalar { - AbiScalar::Unit => writer.u8(0), - AbiScalar::Bool(value) => { - writer.u8(1); - writer.bool(*value); - } - AbiScalar::Int { signed, bits, value } => { - writer.u8(2); - writer.bool(*signed); - writer.u16(*bits); - writer.i64(*value); - } - AbiScalar::Float { bits, raw_bits } => { - writer.u8(3); - writer.u16(*bits); - writer.u64(*raw_bits); - } - AbiScalar::Char { unicode_scalar } => { - writer.u8(4); - writer.u32(*unicode_scalar); - } - AbiScalar::EnumTag { type_id, variant_index } => { - writer.u8(5); - writer.u32(type_id.0); - writer.u32(*variant_index); - } - } -} - -fn decode_scalar(reader: &mut BinaryReader<'_>) -> anyhow::Result { - Ok(match reader.u8()? { - 0 => AbiScalar::Unit, - 1 => AbiScalar::Bool(reader.bool()?), - 2 => AbiScalar::Int { signed: reader.bool()?, bits: reader.u16()?, value: reader.i64()? }, - 3 => AbiScalar::Float { bits: reader.u16()?, raw_bits: reader.u64()? }, - 4 => AbiScalar::Char { unicode_scalar: reader.u32()? }, - 5 => AbiScalar::EnumTag { type_id: TypeId(reader.u32()?), variant_index: reader.u32()? }, - value => bail!("unknown abi scalar tag `{value}`"), - }) -} - -fn encode_packed_scalar_kind(kind: PackedScalarKind) -> u8 { - match kind { - PackedScalarKind::Bool => 0, - PackedScalarKind::I32 => 1, - PackedScalarKind::I64 => 2, - PackedScalarKind::F32 => 3, - PackedScalarKind::F64 => 4, - PackedScalarKind::Char => 5, - } -} - -fn decode_packed_scalar_kind(tag: u8) -> anyhow::Result { - Ok(match tag { - 0 => PackedScalarKind::Bool, - 1 => PackedScalarKind::I32, - 2 => PackedScalarKind::I64, - 3 => PackedScalarKind::F32, - 4 => PackedScalarKind::F64, - 5 => PackedScalarKind::Char, - value => bail!("unknown packed scalar kind `{value}`"), - }) -} - -fn decode_node_kind(tag: u8) -> anyhow::Result { - Ok(match tag { - 0 => CanonicalNodeKind::String, - 1 => CanonicalNodeKind::Array, - 2 => CanonicalNodeKind::Tuple, - 3 => CanonicalNodeKind::Record, - 4 => CanonicalNodeKind::Struct, - 5 => CanonicalNodeKind::Enum, - 6 => CanonicalNodeKind::Union, - 7 => CanonicalNodeKind::Intersection, - value => bail!("unknown canonical node kind `{value}`"), - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_value() -> AbiValue { - AbiValue::Canonical { - transport_type: TypeId(7), - graph: CanonicalGraph { - root: ValueRef::NodeRef(NodeId(2)), - nodes: vec![ - CanonicalNode::String { transport_type: TypeId(1), value: "hello".to_owned() }, - CanonicalNode::Array { - transport_type: TypeId(2), - elements: ArrayElements::PackedScalars { - kind: PackedScalarKind::I32, - len: 2, - bytes: [20i32.to_le_bytes(), 22i32.to_le_bytes()].concat(), - }, - }, - CanonicalNode::Struct { - transport_type: TypeId(7), - fields: vec![ValueRef::NodeRef(NodeId(0)), ValueRef::NodeRef(NodeId(1))], - }, - ], - handles: Vec::new(), - }, - } - } - - #[test] - fn canonical_blob_round_trips() { - let value = sample_value(); - let bytes = encode_canonical_blob(&value).expect("canonical blob should encode"); - let decoded = decode_canonical_blob(&bytes).expect("canonical blob should decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_blob_round_trips_handle_slots() { - let value = AbiValue::Canonical { - transport_type: TypeId(9), - graph: CanonicalGraph { - root: ValueRef::HandleRef(HandleSlotId(0)), - nodes: Vec::new(), - handles: vec![HandleSlot { type_id: TypeId(3), handle_id: 77 }], - }, - }; - - let bytes = encode_canonical_blob(&value).expect("canonical blob with handles"); - let view = CanonicalBlobView::parse(&bytes).expect("view should parse"); - assert_eq!(view.handles(), &[HandleSlot { type_id: TypeId(3), handle_id: 77 }]); - let decoded = decode_canonical_blob(&bytes).expect("canonical blob should decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_blob_view_exposes_payload_nodes() { - let value = sample_value(); - let bytes = encode_canonical_blob(&value).expect("canonical blob should encode"); - let view = CanonicalBlobView::parse(&bytes).expect("view should parse"); - assert_eq!(view.nodes().len(), 3); - assert_eq!(view.payload_bytes(0).expect("string payload"), b"hello"); - } - - #[test] - fn packed_scalar_arrays_round_trip() { - let value = AbiValue::Canonical { - transport_type: TypeId(3), - graph: CanonicalGraph { - root: ValueRef::NodeRef(NodeId(0)), - nodes: vec![CanonicalNode::Array { - transport_type: TypeId(3), - elements: ArrayElements::PackedScalars { - kind: PackedScalarKind::Bool, - len: 3, - bytes: vec![1, 0, 1], - }, - }], - handles: Vec::new(), - }, - }; - let decoded = - decode_canonical_blob(&encode_canonical_blob(&value).expect("encode")).expect("decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_blob_round_trips_union_nodes() { - let value = AbiValue::Canonical { - transport_type: TypeId(11), - graph: CanonicalGraph { - root: ValueRef::NodeRef(NodeId(0)), - nodes: vec![CanonicalNode::Union { - transport_type: TypeId(11), - arm_index: 1, - payload: ValueRef::InlineScalar(AbiScalar::Int { - signed: true, - bits: 32, - value: 42, - }), - }], - handles: Vec::new(), - }, - }; - - let decoded = - decode_canonical_blob(&encode_canonical_blob(&value).expect("encode")).expect("decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_blob_round_trips_intersection_nodes() { - let value = AbiValue::Canonical { - transport_type: TypeId(12), - graph: CanonicalGraph { - root: ValueRef::NodeRef(NodeId(0)), - nodes: vec![CanonicalNode::Intersection { - transport_type: TypeId(12), - carrier: ValueRef::InlineScalar(AbiScalar::Int { - signed: true, - bits: 32, - value: 7, - }), - facets: vec![ValueRef::HandleRef(HandleSlotId(0))], - }], - handles: vec![HandleSlot { type_id: TypeId(9), handle_id: 44 }], - }, - }; - - let decoded = - decode_canonical_blob(&encode_canonical_blob(&value).expect("encode")).expect("decode"); - assert_eq!(decoded, value); - } -} diff --git a/crates/mitki-abi/src/codec.rs b/crates/mitki-abi/src/codec.rs deleted file mode 100644 index 7e21883..0000000 --- a/crates/mitki-abi/src/codec.rs +++ /dev/null @@ -1,150 +0,0 @@ -use anyhow::{Context as _, anyhow, bail}; - -#[derive(Default)] -pub(crate) struct BinaryWriter { - bytes: Vec, -} - -impl BinaryWriter { - pub(crate) fn new() -> Self { - Self::default() - } - - pub(crate) fn into_bytes(self) -> Vec { - self.bytes - } - - pub(crate) fn bytes(&mut self, bytes: &[u8]) { - self.bytes.extend_from_slice(bytes); - } - - pub(crate) fn u8(&mut self, value: u8) { - self.bytes.push(value); - } - - pub(crate) fn u16(&mut self, value: u16) { - self.bytes.extend_from_slice(&value.to_le_bytes()); - } - - pub(crate) fn u32(&mut self, value: u32) { - self.bytes.extend_from_slice(&value.to_le_bytes()); - } - - pub(crate) fn u64(&mut self, value: u64) { - self.bytes.extend_from_slice(&value.to_le_bytes()); - } - - pub(crate) fn i64(&mut self, value: i64) { - self.bytes.extend_from_slice(&value.to_le_bytes()); - } - - pub(crate) fn bool(&mut self, value: bool) { - self.u8(u8::from(value)); - } - - pub(crate) fn len(&mut self, len: usize) { - self.u32(u32::try_from(len).expect("length should fit into u32")); - } - - pub(crate) fn string(&mut self, value: &str) { - self.len(value.len()); - self.bytes(value.as_bytes()); - } - - pub(crate) fn option(&mut self, value: Option, encode: impl FnOnce(&mut Self, T)) { - match value { - Some(value) => { - self.bool(true); - encode(self, value); - } - None => self.bool(false), - } - } -} - -pub(crate) struct BinaryReader<'a> { - bytes: &'a [u8], - offset: usize, -} - -impl<'a> BinaryReader<'a> { - pub(crate) fn new(bytes: &'a [u8]) -> Self { - Self { bytes, offset: 0 } - } - - pub(crate) fn offset(&self) -> usize { - self.offset - } - - pub(crate) fn finished(&self) -> bool { - self.offset == self.bytes.len() - } - - pub(crate) fn ensure_finished(&self) -> anyhow::Result<()> { - if self.finished() { - return Ok(()); - } - bail!( - "binary codec did not consume the full buffer: {} trailing byte(s)", - self.bytes.len() - self.offset - ) - } - - pub(crate) fn bytes(&mut self, len: usize) -> anyhow::Result<&'a [u8]> { - let end = self.offset.checked_add(len).ok_or_else(|| anyhow!("binary read overflowed"))?; - let slice = - self.bytes.get(self.offset..end).ok_or_else(|| anyhow!("unexpected end of buffer"))?; - self.offset = end; - Ok(slice) - } - - pub(crate) fn fixed(&mut self) -> anyhow::Result<[u8; N]> { - let bytes = self.bytes(N)?; - bytes.try_into().map_err(|_error| anyhow!("expected exactly {N} byte(s)")) - } - - pub(crate) fn u8(&mut self) -> anyhow::Result { - Ok(self.fixed::<1>()?[0]) - } - - pub(crate) fn u16(&mut self) -> anyhow::Result { - Ok(u16::from_le_bytes(self.fixed::<2>()?)) - } - - pub(crate) fn u32(&mut self) -> anyhow::Result { - Ok(u32::from_le_bytes(self.fixed::<4>()?)) - } - - pub(crate) fn u64(&mut self) -> anyhow::Result { - Ok(u64::from_le_bytes(self.fixed::<8>()?)) - } - - pub(crate) fn i64(&mut self) -> anyhow::Result { - Ok(i64::from_le_bytes(self.fixed::<8>()?)) - } - - pub(crate) fn bool(&mut self) -> anyhow::Result { - match self.u8()? { - 0 => Ok(false), - 1 => Ok(true), - value => bail!("invalid bool tag `{value}` in binary codec"), - } - } - - pub(crate) fn len(&mut self) -> anyhow::Result { - usize::try_from(self.u32()?).context("length did not fit into usize") - } - - pub(crate) fn string(&mut self) -> anyhow::Result { - let len = self.len()?; - let bytes = self.bytes(len)?; - String::from_utf8(bytes.to_vec()).context("binary codec string was not valid UTF-8") - } - - pub(crate) fn option( - &mut self, - decode: impl FnOnce(&mut Self) -> anyhow::Result, - ) -> anyhow::Result> { - if self.bool()? { decode(self).map(Some) } else { Ok(None) } - } -} diff --git a/crates/mitki-abi/src/contract.rs b/crates/mitki-abi/src/contract.rs deleted file mode 100644 index 6d1d7ad..0000000 --- a/crates/mitki-abi/src/contract.rs +++ /dev/null @@ -1,992 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use anyhow::{anyhow, bail}; - -use crate::{ - ABI_SEMANTIC_MAJOR, ABI_SEMANTIC_MINOR, AbiTypeKind, FunctionInstance, FunctionSignature, - InstanceId, LinkageKind, METADATA_ENCODING_VERSION, REQUIRED_FEATURE_HANDLES, - REQUIRED_FEATURE_INTERSECTION_TRANSPORT, REQUIRED_FEATURE_RECURSIVE_CANONICAL, - REQUIRED_FEATURE_UNION_TRANSPORT, SemanticTypeGraph, SigId, StringId, SymbolId, TransportClass, - TransportRef, TypeId, TypeNode, -}; - -pub const WASM_CORE_V2_M32_PROFILE: &str = "wasm-core-v2/m32"; -pub const WASM_CORE_V2_M32_PROFILE_MAJOR: u16 = 1; -pub const WASM_CORE_V2_M32_PROFILE_MINOR: u16 = 0; - -pub const ABI_V2_ALLOC_EXPORT: &str = "mitki:abi/2/alloc"; -pub const ABI_V2_BLOB_RELEASE_EXPORT: &str = "mitki:abi/2/blob_release"; -pub const ABI_V2_HANDLE_RETAIN_EXPORT: &str = "mitki:abi/2/handle_retain"; -pub const ABI_V2_HANDLE_RELEASE_EXPORT: &str = "mitki:abi/2/handle_release"; -pub const ABI_V2_INVOKE_EXPORT_PREFIX: &str = "mitki:abi/2/invoke$"; -pub const TYPED_BOUNDARY_EXPORT_PREFIX: &str = "mitki:typed/2/f$"; - -pub(crate) const WASM_CORE_V2_M32_SUPPORTED_REQUIRED_FEATURES: u64 = REQUIRED_FEATURE_HANDLES - | REQUIRED_FEATURE_RECURSIVE_CANONICAL - | REQUIRED_FEATURE_UNION_TRANSPORT - | REQUIRED_FEATURE_INTERSECTION_TRANSPORT; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct GraphSupport { - pub required_features: u64, - pub transport_profile: &'static str, - pub profile_version_major: u16, - pub profile_version_minor: u16, -} - -pub const WASM_CORE_V2_M32_RUNTIME_SUPPORT: GraphSupport = GraphSupport { - required_features: WASM_CORE_V2_M32_SUPPORTED_REQUIRED_FEATURES, - transport_profile: WASM_CORE_V2_M32_PROFILE, - profile_version_major: WASM_CORE_V2_M32_PROFILE_MAJOR, - profile_version_minor: WASM_CORE_V2_M32_PROFILE_MINOR, -}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ContractValType { - I32, - I64, - F32, - F64, - V128, - Ref, -} - -impl ContractValType { - pub fn as_str(self) -> &'static str { - match self { - Self::I32 => "i32", - Self::I64 => "i64", - Self::F32 => "f32", - Self::F64 => "f64", - Self::V128 => "v128", - Self::Ref => "ref", - } - } -} - -impl std::fmt::Display for ContractValType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WasmSignatureShape { - pub params: Vec, - pub results: Vec, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct ModuleLinkage { - pub imports: BTreeSet<(String, String)>, - pub function_imports: BTreeMap<(String, String), WasmSignatureShape>, - pub exports: BTreeSet, - pub function_exports: BTreeMap, - pub memory_exports: BTreeSet, -} - -pub fn typed_boundary_wasm_name(metadata_index: usize) -> String { - format!("{TYPED_BOUNDARY_EXPORT_PREFIX}{metadata_index}") -} - -pub fn handle_invoke_export_name(signature_id: SigId) -> String { - format!("{ABI_V2_INVOKE_EXPORT_PREFIX}{}", signature_id.0) -} - -pub fn helper_export_signature( - name: &str, - graph: Option<&SemanticTypeGraph>, -) -> anyhow::Result> { - Ok(match name { - ABI_V2_ALLOC_EXPORT => Some(WasmSignatureShape { - params: vec![ContractValType::I32, ContractValType::I32], - results: vec![ContractValType::I32], - }), - ABI_V2_BLOB_RELEASE_EXPORT => { - Some(WasmSignatureShape { params: vec![ContractValType::I32], results: Vec::new() }) - } - ABI_V2_HANDLE_RETAIN_EXPORT => Some(WasmSignatureShape { - params: vec![ContractValType::I32], - results: vec![ContractValType::I32], - }), - ABI_V2_HANDLE_RELEASE_EXPORT => { - Some(WasmSignatureShape { params: vec![ContractValType::I32], results: Vec::new() }) - } - _ if name.starts_with(ABI_V2_INVOKE_EXPORT_PREFIX) => { - let graph = - graph.ok_or_else(|| anyhow!("invoke helper signature lookup requires metadata"))?; - let raw = name.trim_start_matches(ABI_V2_INVOKE_EXPORT_PREFIX); - let sig_id = raw - .parse::() - .map(SigId) - .map_err(|error| anyhow!("invalid invoke helper signature id `{raw}`: {error}"))?; - let mut shape = typed_signature_wasm_shape(graph, signature(graph, sig_id)?)?; - shape.params.insert(0, ContractValType::I32); - Some(shape) - } - _ => None, - }) -} - -pub fn string_value(graph: &SemanticTypeGraph, id: StringId) -> anyhow::Result<&str> { - graph - .strings - .get(id.0 as usize) - .map(String::as_str) - .ok_or_else(|| anyhow!("unknown ABI v2 string `{}`", id.0)) -} - -pub fn symbol_name(graph: &SemanticTypeGraph, id: SymbolId) -> anyhow::Result<&str> { - let string_id = *graph - .nominal_symbols - .get(id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 symbol `{}`", id.0))?; - string_value(graph, string_id) -} - -pub fn field_name(graph: &SemanticTypeGraph, id: crate::FieldNameId) -> anyhow::Result<&str> { - let string_id = *graph - .field_names - .get(id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 field name `{}`", id.0))?; - string_value(graph, string_id) -} - -pub fn variant_name(graph: &SemanticTypeGraph, id: crate::VariantNameId) -> anyhow::Result<&str> { - let string_id = *graph - .variant_names - .get(id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 variant name `{}`", id.0))?; - string_value(graph, string_id) -} - -pub(crate) fn type_node(graph: &SemanticTypeGraph, ty: TypeId) -> anyhow::Result<&TypeNode> { - graph.types.get(ty.0 as usize).ok_or_else(|| anyhow!("unknown ABI v2 type `{}`", ty.0)) -} - -pub fn signature(graph: &SemanticTypeGraph, id: SigId) -> anyhow::Result<&FunctionSignature> { - graph - .signatures - .get(id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 signature `{}`", id.0)) -} - -pub fn function_instance_wasm_module_name<'a>( - graph: &'a SemanticTypeGraph, - instance: &'a FunctionInstance, -) -> anyhow::Result> { - instance.wasm_module_name.map(|id| string_value(graph, id)).transpose() -} - -pub fn function_instance_wasm_field_name<'a>( - graph: &'a SemanticTypeGraph, - instance: &'a FunctionInstance, -) -> anyhow::Result> { - instance.wasm_field_name.map(|id| string_value(graph, id)).transpose() -} - -pub fn export_wasm_name<'a>( - graph: &'a SemanticTypeGraph, - instance: &'a FunctionInstance, -) -> anyhow::Result<&'a str> { - function_instance_wasm_field_name(graph, instance)? - .ok_or_else(|| anyhow!("typed v2 export is missing its Wasm linkage name")) -} - -pub fn find_export_instance<'a>( - graph: &'a SemanticTypeGraph, - name: &str, -) -> anyhow::Result<&'a FunctionInstance> { - let mut matches = graph.function_instances.iter().filter(|instance| { - instance.linkage == LinkageKind::WasmExport - && (symbol_name(graph, instance.logical_symbol).is_ok_and(|symbol| symbol == name) - || function_instance_wasm_field_name(graph, instance) - .ok() - .flatten() - .is_some_and(|field| field == name)) - }); - let Some(instance) = matches.next() else { - bail!("typed v2 invocation could not find export `{name}`"); - }; - if matches.next().is_some() { - bail!("typed v2 invocation found multiple exports named `{name}`"); - } - Ok(instance) -} - -pub fn find_export_instance_by_id( - graph: &SemanticTypeGraph, - id: InstanceId, -) -> anyhow::Result<&FunctionInstance> { - graph - .function_instances - .iter() - .find(|instance| instance.linkage == LinkageKind::WasmExport && instance.id == id) - .ok_or_else(|| anyhow!("typed v2 invocation could not find export instance `{}`", id.0)) -} - -pub fn find_import_instance_by_linkage<'a>( - graph: &'a SemanticTypeGraph, - module: &str, - name: &str, -) -> Option<&'a FunctionInstance> { - graph.function_instances.iter().find(|instance| { - instance.linkage == LinkageKind::WasmImport - && function_instance_wasm_module_name(graph, instance) - .ok() - .flatten() - .is_some_and(|candidate| candidate == module) - && function_instance_wasm_field_name(graph, instance) - .ok() - .flatten() - .is_some_and(|candidate| candidate == name) - }) -} - -pub fn transport_carrier_type(transport: &TransportRef) -> TypeId { - transport.transport_type.unwrap_or(transport.semantic_type) -} - -pub fn signature_uses_canonical_transport(signature: &FunctionSignature) -> bool { - signature - .params - .iter() - .chain(std::iter::once(&signature.result)) - .any(|transport| transport.transport_class == TransportClass::CanonicalValue) -} - -pub fn immediate_is_unit_like(kind: &AbiTypeKind) -> bool { - match kind { - AbiTypeKind::Unit => true, - AbiTypeKind::Tuple { elems } => elems.is_empty(), - _ => false, - } -} - -pub fn transport_wasm_lane( - graph: &SemanticTypeGraph, - transport: &TransportRef, -) -> anyhow::Result> { - let transport_ty = transport_carrier_type(transport); - Ok(match transport.transport_class { - TransportClass::Immediate => match &type_node(graph, transport_ty)?.kind { - AbiTypeKind::Unit => None, - AbiTypeKind::Tuple { elems } if elems.is_empty() => None, - AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Char - | AbiTypeKind::Enum { .. } => Some(ContractValType::I32), - AbiTypeKind::Float { .. } => Some(ContractValType::F64), - other => bail!("immediate ABI v2 transport cannot lower `{other:?}`"), - }, - TransportClass::CanonicalValue | TransportClass::CapabilityHandle => { - Some(ContractValType::I32) - } - }) -} - -pub fn transport_has_wasm_lane( - graph: &SemanticTypeGraph, - transport: &TransportRef, -) -> anyhow::Result { - Ok(transport_wasm_lane(graph, transport)?.is_some()) -} - -pub fn typed_signature_wasm_shape( - graph: &SemanticTypeGraph, - signature: &FunctionSignature, -) -> anyhow::Result { - let params = signature - .params - .iter() - .map(|transport| transport_wasm_lane(graph, transport)) - .collect::>>()? - .into_iter() - .flatten() - .collect(); - let results = transport_wasm_lane(graph, &signature.result)?.into_iter().collect(); - Ok(WasmSignatureShape { params, results }) -} - -pub fn validate_graph_support( - graph: &SemanticTypeGraph, - support: GraphSupport, -) -> anyhow::Result<()> { - if graph.semantic_version_major != ABI_SEMANTIC_MAJOR { - bail!( - "runtime supports ABI v2 semantic major {}, found {}", - ABI_SEMANTIC_MAJOR, - graph.semantic_version_major - ); - } - if graph.semantic_version_minor > ABI_SEMANTIC_MINOR { - bail!( - "runtime supports ABI v2 semantic minor up to {}, found {}", - ABI_SEMANTIC_MINOR, - graph.semantic_version_minor - ); - } - if graph.encoding_version != METADATA_ENCODING_VERSION { - bail!( - "runtime supports ABI v2 metadata encoding version {}, found {}", - METADATA_ENCODING_VERSION, - graph.encoding_version - ); - } - let unsupported = graph.required_features & !support.required_features; - if unsupported != 0 { - bail!("runtime does not support required ABI v2 feature bits `0x{unsupported:x}`"); - } - let profile = string_value(graph, graph.transport_profile)?; - if profile != support.transport_profile { - bail!("runtime does not support ABI v2 transport profile `{profile}`"); - } - if graph.profile_version_major > support.profile_version_major - || (graph.profile_version_major == support.profile_version_major - && graph.profile_version_minor > support.profile_version_minor) - { - bail!( - "runtime supports ABI v2 transport profile version up to {}.{}, found {}.{}", - support.profile_version_major, - support.profile_version_minor, - graph.profile_version_major, - graph.profile_version_minor - ); - } - Ok(()) -} - -pub fn validate_graph_schema(graph: &SemanticTypeGraph) -> anyhow::Result<()> { - if graph.semantic_version_major != ABI_SEMANTIC_MAJOR { - bail!( - "ABI v2 metadata semantic major drifted: expected {}, found {}", - ABI_SEMANTIC_MAJOR, - graph.semantic_version_major - ); - } - if graph.semantic_version_minor > ABI_SEMANTIC_MINOR { - bail!( - "ABI v2 metadata semantic minor drifted past supported maximum {}", - ABI_SEMANTIC_MINOR - ); - } - if graph.encoding_version != METADATA_ENCODING_VERSION { - bail!( - "ABI v2 metadata encoding version drifted: expected {}, found {}", - METADATA_ENCODING_VERSION, - graph.encoding_version - ); - } - - let mut normalized = graph.clone(); - normalized.normalize_commutative_members()?; - for (index, (expected, actual)) in normalized.types.iter().zip(graph.types.iter()).enumerate() { - if expected.kind != actual.kind { - bail!("ABI v2 type `{index}` was not normalized deterministically"); - } - } - - let mut derived = graph.clone(); - derived.populate_recursive_groups(); - if derived.recursive_groups != graph.recursive_groups { - bail!("ABI v2 recursive groups drifted from the derived contract state"); - } - - derived.populate_fingerprints()?; - for (expected, actual) in derived.types.iter().zip(graph.types.iter()) { - if expected.fingerprint != actual.fingerprint { - bail!( - "ABI v2 fingerprint drifted for type `{}`: expected {}, found {}", - actual.id.0, - expected.fingerprint.to_hex(), - actual.fingerprint.to_hex() - ); - } - } - - derived.recompute_required_features(); - if derived.required_features != graph.required_features { - bail!( - "ABI v2 required feature bits drifted: expected `0x{:x}`, found `0x{:x}`", - derived.required_features, - graph.required_features - ); - } - - for (index, field_name_id) in graph.field_names.iter().copied().enumerate() { - let _ = string_value(graph, field_name_id) - .map_err(|error| anyhow!("invalid field name id at index {index}: {error}"))?; - } - for (index, variant_name_id) in graph.variant_names.iter().copied().enumerate() { - let _ = string_value(graph, variant_name_id) - .map_err(|error| anyhow!("invalid variant name id at index {index}: {error}"))?; - } - for (index, symbol_id) in graph.nominal_symbols.iter().copied().enumerate() { - let _ = string_value(graph, symbol_id) - .map_err(|error| anyhow!("invalid nominal symbol id at index {index}: {error}"))?; - } - - for (expected_index, node) in graph.types.iter().enumerate() { - if node.id.0 as usize != expected_index { - bail!("ABI v2 type id drifted at index {expected_index}: found {}", node.id.0); - } - let _ = type_node(graph, node.id)?; - validate_type_refs(graph, &node.kind)?; - } - - for (expected_index, group) in graph.recursive_groups.iter().enumerate() { - if group.id.0 as usize != expected_index { - bail!( - "ABI v2 recursive-group id drifted at index {expected_index}: found {}", - group.id.0 - ); - } - for &member in &group.members { - let node = type_node(graph, member)?; - if node.recursive_group != Some(group.id) { - bail!("ABI v2 recursive-group membership drifted for type `{}`", member.0); - } - } - } - - for (expected_index, signature_entry) in graph.signatures.iter().enumerate() { - if signature_entry.id.0 as usize != expected_index { - bail!( - "ABI v2 signature id drifted at index {expected_index}: found {}", - signature_entry.id.0 - ); - } - if !matches!( - type_node(graph, signature_entry.function_type)?.kind, - AbiTypeKind::Function { .. } - ) { - bail!("ABI v2 signature `{}` did not point at a function type", signature_entry.id.0); - } - for transport in - signature_entry.params.iter().chain(std::iter::once(&signature_entry.result)) - { - let _ = type_node(graph, transport.semantic_type)?; - if let Some(transport_type) = transport.transport_type { - let _ = type_node(graph, transport_type)?; - } - let _ = transport_wasm_lane(graph, transport)?; - } - } - - for (expected_index, origin) in graph.generic_origins.iter().enumerate() { - if origin.id.0 as usize != expected_index { - bail!( - "ABI v2 generic-origin id drifted at index {expected_index}: found {}", - origin.id.0 - ); - } - let _ = symbol_name(graph, origin.symbol)?; - } - - for (expected_index, plan) in graph.facet_plans.iter().enumerate() { - if plan.id.0 as usize != expected_index { - bail!("ABI v2 facet-plan id drifted at index {expected_index}: found {}", plan.id.0); - } - for entry in &plan.entries { - let _ = type_node(graph, entry.member)?; - } - } - - for (expected_index, instance) in graph.function_instances.iter().enumerate() { - if instance.id.0 as usize != expected_index { - bail!( - "ABI v2 function-instance id drifted at index {expected_index}: found {}", - instance.id.0 - ); - } - let _ = symbol_name(graph, instance.logical_symbol)?; - if let Some(origin) = instance.generic_origin { - graph - .generic_origins - .get(origin.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 generic origin `{}`", origin.0))?; - } - for &type_arg in &instance.type_args { - let _ = type_node(graph, type_arg)?; - } - let _ = signature(graph, instance.signature)?; - let _ = function_instance_wasm_module_name(graph, instance)?; - let _ = function_instance_wasm_field_name(graph, instance)?; - match instance.linkage { - LinkageKind::WasmExport | LinkageKind::StageEntry => { - if instance.wasm_field_name.is_none() { - bail!( - "ABI v2 function instance `{}` is missing its Wasm field name", - instance.id.0 - ); - } - } - LinkageKind::WasmImport => { - if instance.wasm_module_name.is_none() || instance.wasm_field_name.is_none() { - bail!("ABI v2 import instance `{}` is missing its Wasm linkage", instance.id.0); - } - } - LinkageKind::RawImport => {} - } - } - - for raw_import in &graph.raw_imports { - let _ = string_value(graph, raw_import.module)?; - let _ = string_value(graph, raw_import.field)?; - if let Some(symbol) = raw_import.symbol { - let _ = symbol_name(graph, symbol)?; - } - if let Some(signature_id) = raw_import.signature { - let _ = signature(graph, signature_id)?; - } - } - for raw_export in &graph.raw_exports { - let _ = string_value(graph, raw_export.name)?; - if let Some(symbol) = raw_export.symbol { - let _ = symbol_name(graph, symbol)?; - } - if let Some(signature_id) = raw_export.signature { - let _ = signature(graph, signature_id)?; - } - } - if let Some(export_name) = graph.boundary_memory.export_name { - let _ = string_value(graph, export_name)?; - } - - Ok(()) -} - -pub fn collect_module_linkage(bytes: &[u8]) -> anyhow::Result { - let mut function_types = Vec::new(); - let mut function_type_indices = Vec::new(); - let mut linkage = ModuleLinkage::default(); - - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - match payload { - wasmparser::Payload::TypeSection(reader) => { - for ty in reader.into_iter_err_on_gc_types() { - let ty = ty?; - function_types.push(WasmSignatureShape { - params: ty.params().iter().copied().map(contract_val_type).collect(), - results: ty.results().iter().copied().map(contract_val_type).collect(), - }); - } - } - wasmparser::Payload::ImportSection(reader) => { - for import in reader.into_imports() { - let import = import?; - let key = (import.module.to_owned(), import.name.to_owned()); - linkage.imports.insert(key.clone()); - match import.ty { - wasmparser::TypeRef::Func(type_index) - | wasmparser::TypeRef::FuncExact(type_index) => { - function_type_indices.push(type_index); - let shape = function_types - .get(type_index as usize) - .cloned() - .ok_or_else(|| anyhow!("missing function type `{type_index}`"))?; - linkage.function_imports.insert(key, shape); - } - wasmparser::TypeRef::Table(_) - | wasmparser::TypeRef::Memory(_) - | wasmparser::TypeRef::Global(_) - | wasmparser::TypeRef::Tag(_) => {} - } - } - } - wasmparser::Payload::FunctionSection(reader) => { - for function in reader { - function_type_indices.push(function?); - } - } - wasmparser::Payload::ExportSection(reader) => { - for export in reader { - let export = export?; - let name = export.name.to_owned(); - linkage.exports.insert(name.clone()); - match export.kind { - wasmparser::ExternalKind::Func | wasmparser::ExternalKind::FuncExact => { - let type_index = - *function_type_indices.get(export.index as usize).ok_or_else( - || anyhow!("missing function index `{}`", export.index), - )?; - let shape = function_types - .get(type_index as usize) - .cloned() - .ok_or_else(|| anyhow!("missing function type `{type_index}`"))?; - linkage.function_exports.insert(name, shape); - } - wasmparser::ExternalKind::Memory => { - linkage.memory_exports.insert(name); - } - wasmparser::ExternalKind::Table - | wasmparser::ExternalKind::Global - | wasmparser::ExternalKind::Tag => {} - } - } - } - _ => {} - } - } - - Ok(linkage) -} - -pub fn validate_wasm_module_contract( - bytes: &[u8], - graph: &SemanticTypeGraph, -) -> anyhow::Result<()> { - validate_graph_schema(graph)?; - let linkage = collect_module_linkage(bytes)?; - validate_boundary_memory(graph, &linkage)?; - validate_raw_linkage(graph, &linkage)?; - validate_function_instances(graph, &linkage)?; - validate_helper_exports(graph, &linkage)?; - Ok(()) -} - -fn validate_type_refs(graph: &SemanticTypeGraph, kind: &AbiTypeKind) -> anyhow::Result<()> { - match kind { - AbiTypeKind::Array { elem } => { - let _ = type_node(graph, *elem)?; - } - AbiTypeKind::Tuple { elems } | AbiTypeKind::Union { members: elems } => { - for &elem in elems { - let _ = type_node(graph, elem)?; - } - } - AbiTypeKind::Record { fields } | AbiTypeKind::Struct { fields, .. } => { - for field in fields { - let _ = field_name(graph, field.name)?; - let _ = type_node(graph, field.ty)?; - } - } - AbiTypeKind::Enum { variants, .. } => { - for variant in variants { - let _ = variant_name(graph, variant.name)?; - for &field in &variant.fields { - let _ = type_node(graph, field)?; - } - } - } - AbiTypeKind::Intersection { members, carrier, facet_plan } => { - for &member in members { - let _ = type_node(graph, member)?; - } - let _ = type_node(graph, *carrier)?; - if let Some(plan_id) = facet_plan { - graph - .facet_plans - .get(plan_id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 facet plan `{}`", plan_id.0))?; - } - } - AbiTypeKind::Function { params, result, .. } => { - for ¶m in params { - let _ = type_node(graph, param)?; - } - let _ = type_node(graph, *result)?; - } - AbiTypeKind::Unit - | AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Float { .. } - | AbiTypeKind::Char - | AbiTypeKind::String - | AbiTypeKind::Opaque { .. } => {} - } - Ok(()) -} - -fn validate_boundary_memory( - graph: &SemanticTypeGraph, - linkage: &ModuleLinkage, -) -> anyhow::Result<()> { - if let Some(export_name) = graph.boundary_memory.export_name { - let export_name = string_value(graph, export_name)?; - if !linkage.memory_exports.contains(export_name) { - bail!("ABI v2 boundary memory export `{export_name}` was not found in the module"); - } - } - Ok(()) -} - -fn validate_raw_linkage(graph: &SemanticTypeGraph, linkage: &ModuleLinkage) -> anyhow::Result<()> { - for import in &graph.raw_imports { - let module = string_value(graph, import.module)?; - let field = string_value(graph, import.field)?; - if !linkage.imports.contains(&(module.to_owned(), field.to_owned())) { - bail!("ABI v2 raw import `{module}::{field}` was not found in the module"); - } - } - for export in &graph.raw_exports { - let name = string_value(graph, export.name)?; - if !linkage.exports.contains(name) { - bail!("ABI v2 raw export `{name}` was not found in the module"); - } - } - Ok(()) -} - -fn validate_function_instances( - graph: &SemanticTypeGraph, - linkage: &ModuleLinkage, -) -> anyhow::Result<()> { - for instance in &graph.function_instances { - let signature = signature(graph, instance.signature)?; - if !matches!(type_node(graph, signature.function_type)?.kind, AbiTypeKind::Function { .. }) - { - bail!("ABI v2 function instance `{}` did not point at a function type", instance.id.0); - } - let expected_shape = typed_signature_wasm_shape(graph, signature)?; - match instance.linkage { - LinkageKind::WasmExport | LinkageKind::StageEntry => { - let export_name = export_wasm_name(graph, instance)?; - let actual_shape = linkage.function_exports.get(export_name).ok_or_else(|| { - anyhow!("ABI v2 export `{export_name}` was not found in the module") - })?; - if actual_shape != &expected_shape { - bail!( - "ABI v2 export `{export_name}` signature drifted from metadata: expected \ - {:?}, found {:?}", - expected_shape, - actual_shape - ); - } - } - LinkageKind::WasmImport => { - let module_name = function_instance_wasm_module_name(graph, instance)? - .ok_or_else(|| anyhow!("ABI v2 import instance is missing its module name"))?; - let field_name = function_instance_wasm_field_name(graph, instance)? - .ok_or_else(|| anyhow!("ABI v2 import instance is missing its field name"))?; - let actual_shape = linkage - .function_imports - .get(&(module_name.to_owned(), field_name.to_owned())) - .ok_or_else(|| { - anyhow!( - "ABI v2 import `{module_name}::{field_name}` was not found in the \ - module" - ) - })?; - if actual_shape != &expected_shape { - bail!( - "ABI v2 import `{module_name}::{field_name}` signature drifted from \ - metadata: expected {:?}, found {:?}", - expected_shape, - actual_shape - ); - } - } - LinkageKind::RawImport => {} - } - } - Ok(()) -} - -fn validate_helper_exports( - graph: &SemanticTypeGraph, - linkage: &ModuleLinkage, -) -> anyhow::Result<()> { - let mut required_helpers = Vec::new(); - if graph.function_instances.iter().any(|instance| { - matches!(instance.linkage, LinkageKind::WasmExport | LinkageKind::StageEntry) - && signature(graph, instance.signature).is_ok_and(signature_uses_canonical_transport) - }) { - required_helpers.push(ABI_V2_ALLOC_EXPORT); - required_helpers.push(ABI_V2_BLOB_RELEASE_EXPORT); - } - if graph.required_features & REQUIRED_FEATURE_HANDLES != 0 { - required_helpers.push(ABI_V2_HANDLE_RETAIN_EXPORT); - required_helpers.push(ABI_V2_HANDLE_RELEASE_EXPORT); - } - - for helper in required_helpers { - let Some(actual_shape) = linkage.function_exports.get(helper) else { - bail!("ABI v2 helper export `{helper}` was not found in the module"); - }; - let expected_shape = helper_export_signature(helper, Some(graph))? - .ok_or_else(|| anyhow!("missing ABI v2 helper contract for `{helper}`"))?; - if actual_shape != &expected_shape { - bail!( - "ABI v2 helper export `{helper}` signature drifted from the contract: expected \ - {:?}, found {:?}", - expected_shape, - actual_shape - ); - } - } - - for (export_name, actual_shape) in &linkage.function_exports { - let Some(expected_shape) = helper_export_signature(export_name, Some(graph))? else { - continue; - }; - if actual_shape != &expected_shape { - bail!( - "ABI v2 helper export `{export_name}` signature drifted from the contract: \ - expected {:?}, found {:?}", - expected_shape, - actual_shape - ); - } - } - - Ok(()) -} - -fn contract_val_type(value_type: wasmparser::ValType) -> ContractValType { - match value_type { - wasmparser::ValType::I32 => ContractValType::I32, - wasmparser::ValType::I64 => ContractValType::I64, - wasmparser::ValType::F32 => ContractValType::F32, - wasmparser::ValType::F64 => ContractValType::F64, - wasmparser::ValType::V128 => ContractValType::V128, - wasmparser::ValType::Ref(_) => ContractValType::Ref, - } -} - -#[cfg(test)] -mod tests { - use expect_test::expect; - - use super::*; - use crate::{ - BoundaryMemory, ExecutionDomain, FieldNameId, RawExportRecord, RecordField, SymbolId, - }; - - fn sample_graph() -> SemanticTypeGraph { - let mut graph = SemanticTypeGraph::default(); - let profile = graph.insert_string(WASM_CORE_V2_M32_PROFILE); - let memory = graph.insert_string("memory"); - let point = graph.insert_string("Point"); - let x = graph.insert_string("x"); - let y = graph.insert_string("y"); - let make = graph.insert_string("make_point"); - let typed_export = graph.insert_string(typed_boundary_wasm_name(0)); - graph.transport_profile = profile; - graph.profile_version_major = WASM_CORE_V2_M32_PROFILE_MAJOR; - graph.profile_version_minor = WASM_CORE_V2_M32_PROFILE_MINOR; - graph.boundary_memory = BoundaryMemory { memory_index: 0, export_name: Some(memory) }; - graph.field_names = vec![x, y]; - graph.nominal_symbols = vec![point, make]; - - let int = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let point = graph.push_type(AbiTypeKind::Struct { - nominal: SymbolId(0), - fields: vec![ - RecordField { name: FieldNameId(0), ty: int }, - RecordField { name: FieldNameId(1), ty: int }, - ], - }); - let function_type = graph.push_type(AbiTypeKind::Function { - params: Vec::new(), - result: point, - domain: ExecutionDomain::Runtime, - }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type, - params: Vec::new(), - result: TransportRef { - semantic_type: point, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - }); - graph.function_instances.push(FunctionInstance { - id: InstanceId(0), - logical_symbol: SymbolId(1), - generic_origin: None, - type_args: Vec::new(), - signature: SigId(0), - domain: ExecutionDomain::Runtime, - linkage: LinkageKind::WasmExport, - wasm_module_name: None, - wasm_field_name: Some(typed_export), - }); - graph.raw_exports.push(RawExportRecord { - name: typed_export, - symbol: Some(SymbolId(1)), - signature: Some(SigId(0)), - }); - graph.populate_recursive_groups(); - graph.normalize_commutative_members().expect("normalized graph"); - graph.populate_fingerprints().expect("fingerprints"); - graph.recompute_required_features(); - graph - } - - fn contract_dump(graph: &SemanticTypeGraph) -> String { - let instance = find_export_instance(graph, "make_point").expect("export instance"); - let signature = typed_signature_wasm_shape( - graph, - signature(graph, instance.signature).expect("signature"), - ) - .expect("typed signature"); - let mut output = String::new(); - output.push_str("contract.profile: "); - output.push_str(string_value(graph, graph.transport_profile).expect("profile")); - output.push('\n'); - output.push_str("contract.functions:\n"); - output.push_str(" - "); - output.push_str(symbol_name(graph, instance.logical_symbol).expect("symbol")); - output.push_str(" => "); - output.push_str(export_wasm_name(graph, instance).expect("export name")); - output.push('\n'); - output.push_str("contract.signature: ("); - output.push_str( - &signature.params.iter().map(ToString::to_string).collect::>().join(", "), - ); - output.push_str(") -> ("); - output.push_str( - &signature.results.iter().map(ToString::to_string).collect::>().join(", "), - ); - output.push_str(")\n"); - output.push_str("contract.fingerprints:\n"); - for node in &graph.types { - output.push_str(" - ["); - output.push_str(&node.id.0.to_string()); - output.push_str("] "); - output.push_str(&node.fingerprint.to_hex()); - output.push('\n'); - } - output - } - - #[test] - fn contract_helpers_snapshot_stays_stable() { - let graph = sample_graph(); - expect![[r#" -contract.profile: wasm-core-v2/m32 -contract.functions: - - make_point => mitki:typed/2/f$0 -contract.signature: () -> (i32) -contract.fingerprints: - - [0] d3b476ed5070a8014b52821b10c03e6c - - [1] 51bb14d9577e66538d9ecdba93907b15 - - [2] 4630744f55067020fab30a8204531198 -"#]] - .assert_eq(&contract_dump(&graph)); - } - - #[test] - fn graph_schema_validation_recomputes_fingerprints_and_feature_bits() { - let mut graph = sample_graph(); - graph.required_features ^= REQUIRED_FEATURE_RECURSIVE_CANONICAL; - let error = validate_graph_schema(&graph).expect_err("graph schema should fail"); - assert!(error.to_string().contains("required feature bits drifted")); - } - - #[test] - fn typed_boundary_names_and_helper_signatures_are_stable() { - assert_eq!(typed_boundary_wasm_name(7), "mitki:typed/2/f$7"); - assert_eq!(handle_invoke_export_name(SigId(3)), "mitki:abi/2/invoke$3"); - assert_eq!( - helper_export_signature(ABI_V2_HANDLE_RETAIN_EXPORT, None).expect("helper signature"), - Some(WasmSignatureShape { - params: vec![ContractValType::I32], - results: vec![ContractValType::I32], - }) - ); - } -} diff --git a/crates/mitki-abi/src/lib.rs b/crates/mitki-abi/src/lib.rs deleted file mode 100644 index c818155..0000000 --- a/crates/mitki-abi/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod canonical; -mod codec; -mod contract; -mod metadata; - -pub use canonical::{ - AbiScalar, AbiValue, ArrayElements, CANONICAL_BLOB_ENCODING_VERSION, CANONICAL_BLOB_MAGIC, - CanonicalBlobHeader, CanonicalBlobView, CanonicalGraph, CanonicalNode, CanonicalNodeHeader, - CanonicalNodeKind, HandleSlot, HandleSlotId, NodeId, PackedScalarKind, TransportClass, - ValueRef, decode_canonical_blob, encode_canonical_blob, -}; -pub use contract::{ - ABI_V2_ALLOC_EXPORT, ABI_V2_BLOB_RELEASE_EXPORT, ABI_V2_HANDLE_RELEASE_EXPORT, - ABI_V2_HANDLE_RETAIN_EXPORT, ABI_V2_INVOKE_EXPORT_PREFIX, ContractValType, GraphSupport, - ModuleLinkage, TYPED_BOUNDARY_EXPORT_PREFIX, WASM_CORE_V2_M32_PROFILE, - WASM_CORE_V2_M32_PROFILE_MAJOR, WASM_CORE_V2_M32_PROFILE_MINOR, - WASM_CORE_V2_M32_RUNTIME_SUPPORT, collect_module_linkage, export_wasm_name, field_name, - find_export_instance, find_export_instance_by_id, find_import_instance_by_linkage, - function_instance_wasm_field_name, function_instance_wasm_module_name, - handle_invoke_export_name, helper_export_signature, immediate_is_unit_like, signature, - signature_uses_canonical_transport, string_value, symbol_name, transport_carrier_type, - transport_has_wasm_lane, transport_wasm_lane, typed_boundary_wasm_name, - typed_signature_wasm_shape, validate_graph_schema, validate_graph_support, - validate_wasm_module_contract, variant_name, -}; -pub use metadata::{ - ABI_SEMANTIC_MAJOR, ABI_SEMANTIC_MINOR, AbiTypeKind, BoundaryMemory, CapabilityId, DebugNameId, - EnumVariant, ExecutionDomain, FacetPlan, FacetPlanEntry, FacetPlanEntryKind, FacetPlanId, - FieldNameId, FunctionInstance, FunctionSignature, GenericOrigin, GenericOriginId, InstanceId, - LinkageKind, METADATA_ENCODING_VERSION, METADATA_V2_MAGIC, REQUIRED_FEATURE_HANDLES, - REQUIRED_FEATURE_INTERSECTION_TRANSPORT, REQUIRED_FEATURE_RECURSIVE_CANONICAL, - REQUIRED_FEATURE_UNION_TRANSPORT, RawExportRecord, RawImportRecord, RecordField, - RecursiveGroup, RecursiveGroupId, SemanticTypeGraph, SigId, StringId, SymbolId, TransportRef, - TypeFingerprint, TypeId, TypeNode, VariantNameId, decode_semantic_type_graph, - encode_semantic_type_graph, -}; - -pub const CANONICAL_ABI_SEMANTIC_MAJOR: u16 = ABI_SEMANTIC_MAJOR; -pub const CANONICAL_ABI_SEMANTIC_MINOR: u16 = ABI_SEMANTIC_MINOR; diff --git a/crates/mitki-abi/src/metadata.rs b/crates/mitki-abi/src/metadata.rs deleted file mode 100644 index eef1190..0000000 --- a/crates/mitki-abi/src/metadata.rs +++ /dev/null @@ -1,1650 +0,0 @@ -use anyhow::{anyhow, bail}; - -use crate::TransportClass; -use crate::codec::{BinaryReader, BinaryWriter}; - -pub const METADATA_V2_MAGIC: [u8; 8] = *b"MTKABI2\0"; -pub const METADATA_ENCODING_VERSION: u16 = 2; -pub const ABI_SEMANTIC_MAJOR: u16 = 2; -pub const ABI_SEMANTIC_MINOR: u16 = 0; -pub const REQUIRED_FEATURE_HANDLES: u64 = 1 << 0; -pub const REQUIRED_FEATURE_RECURSIVE_CANONICAL: u64 = 1 << 1; -pub const REQUIRED_FEATURE_UNION_TRANSPORT: u64 = 1 << 2; -pub const REQUIRED_FEATURE_INTERSECTION_TRANSPORT: u64 = 1 << 3; - -macro_rules! id_type { - ($name:ident) => { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] - pub struct $name(pub u32); - }; -} - -id_type!(StringId); -id_type!(FieldNameId); -id_type!(VariantNameId); -id_type!(SymbolId); -id_type!(DebugNameId); -id_type!(RecursiveGroupId); -id_type!(TypeId); -id_type!(SigId); -id_type!(InstanceId); -id_type!(GenericOriginId); -id_type!(FacetPlanId); -id_type!(CapabilityId); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TypeFingerprint(pub [u8; 16]); - -impl TypeFingerprint { - pub const ZERO: Self = Self([0; 16]); - - pub fn to_hex(self) -> String { - let mut output = String::with_capacity(32); - for byte in self.0 { - use std::fmt::Write as _; - let _ = write!(&mut output, "{byte:02x}"); - } - output - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum ExecutionDomain { - Runtime, - Stage, - Both, -} - -impl ExecutionDomain { - pub fn as_bits(self) -> u8 { - match self { - Self::Runtime => 1, - Self::Stage => 2, - Self::Both => 3, - } - } - - pub fn from_bits(bits: u8) -> anyhow::Result { - match bits { - 1 => Ok(Self::Runtime), - 2 => Ok(Self::Stage), - 3 => Ok(Self::Both), - value => bail!("unknown execution domain bits `{value}`"), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RecordField { - pub name: FieldNameId, - pub ty: TypeId, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EnumVariant { - pub name: VariantNameId, - pub fields: Vec, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum FacetPlanEntryKind { - Erased, - ValueFacet, - HandleFacet, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FacetPlanEntry { - pub member: TypeId, - pub kind: FacetPlanEntryKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FacetPlan { - pub id: FacetPlanId, - pub entries: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum AbiTypeKind { - Unit, - Bool, - Int { signed: bool, bits: u16 }, - Float { bits: u16 }, - Char, - String, - Array { elem: TypeId }, - Tuple { elems: Vec }, - Record { fields: Vec }, - Struct { nominal: SymbolId, fields: Vec }, - Enum { nominal: SymbolId, variants: Vec }, - Union { members: Vec }, - Intersection { members: Vec, carrier: TypeId, facet_plan: Option }, - Function { params: Vec, result: TypeId, domain: ExecutionDomain }, - Opaque { capability_id: CapabilityId }, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TypeNode { - pub id: TypeId, - pub fingerprint: TypeFingerprint, - pub kind: AbiTypeKind, - pub flags: u32, - pub recursive_group: Option, - pub debug_name: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RecursiveGroup { - pub id: RecursiveGroupId, - pub members: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TransportRef { - pub semantic_type: TypeId, - pub transport_class: TransportClass, - pub transport_type: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FunctionSignature { - pub id: SigId, - pub function_type: TypeId, - pub params: Vec, - pub result: TransportRef, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct GenericOrigin { - pub id: GenericOriginId, - pub symbol: SymbolId, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum LinkageKind { - WasmExport, - WasmImport, - StageEntry, - RawImport, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FunctionInstance { - pub id: InstanceId, - pub logical_symbol: SymbolId, - pub generic_origin: Option, - pub type_args: Vec, - pub signature: SigId, - pub domain: ExecutionDomain, - pub linkage: LinkageKind, - pub wasm_module_name: Option, - pub wasm_field_name: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RawImportRecord { - pub module: StringId, - pub field: StringId, - pub symbol: Option, - pub signature: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RawExportRecord { - pub name: StringId, - pub symbol: Option, - pub signature: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BoundaryMemory { - pub memory_index: u32, - pub export_name: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SemanticTypeGraph { - pub semantic_version_major: u16, - pub semantic_version_minor: u16, - pub encoding_version: u16, - pub required_features: u64, - pub transport_profile: StringId, - pub profile_version_major: u16, - pub profile_version_minor: u16, - pub boundary_memory: BoundaryMemory, - pub strings: Vec, - pub field_names: Vec, - pub variant_names: Vec, - pub nominal_symbols: Vec, - pub debug_names: Vec, - pub types: Vec, - pub recursive_groups: Vec, - pub signatures: Vec, - pub generic_origins: Vec, - pub facet_plans: Vec, - pub function_instances: Vec, - pub raw_imports: Vec, - pub raw_exports: Vec, -} - -impl Default for SemanticTypeGraph { - fn default() -> Self { - Self { - semantic_version_major: ABI_SEMANTIC_MAJOR, - semantic_version_minor: ABI_SEMANTIC_MINOR, - encoding_version: METADATA_ENCODING_VERSION, - required_features: 0, - transport_profile: StringId(0), - profile_version_major: 0, - profile_version_minor: 0, - boundary_memory: BoundaryMemory { memory_index: 0, export_name: None }, - strings: Vec::new(), - field_names: Vec::new(), - variant_names: Vec::new(), - nominal_symbols: Vec::new(), - debug_names: Vec::new(), - types: Vec::new(), - recursive_groups: Vec::new(), - signatures: Vec::new(), - generic_origins: Vec::new(), - facet_plans: Vec::new(), - function_instances: Vec::new(), - raw_imports: Vec::new(), - raw_exports: Vec::new(), - } - } -} - -impl SemanticTypeGraph { - pub fn insert_string(&mut self, value: impl Into) -> StringId { - let id = StringId(self.strings.len() as u32); - self.strings.push(value.into()); - id - } - - pub fn push_type(&mut self, kind: AbiTypeKind) -> TypeId { - let id = TypeId(self.types.len() as u32); - self.types.push(TypeNode { - id, - fingerprint: TypeFingerprint::ZERO, - kind, - flags: 0, - recursive_group: None, - debug_name: None, - }); - id - } - - pub fn type_node(&self, id: TypeId) -> anyhow::Result<&TypeNode> { - self.types.get(id.0 as usize).ok_or_else(|| anyhow!("unknown type id `{}`", id.0)) - } - - pub fn populate_recursive_groups(&mut self) { - let mut state = TarjanState::default(); - for index in 0..self.types.len() { - if !state.indices.contains_key(&index) { - self.visit_scc(index, &mut state); - } - } - - self.recursive_groups.clear(); - for node in &mut self.types { - node.recursive_group = None; - } - - for component in state.components { - let is_recursive = component.len() > 1 - || self - .type_edges(TypeId(component[0] as u32)) - .contains(&TypeId(component[0] as u32)); - if !is_recursive { - continue; - } - let id = RecursiveGroupId(self.recursive_groups.len() as u32); - let members = - component.into_iter().map(|index| TypeId(index as u32)).collect::>(); - for member in &members { - if let Some(node) = self.types.get_mut(member.0 as usize) { - node.recursive_group = Some(id); - } - } - self.recursive_groups.push(RecursiveGroup { id, members }); - } - } - - pub fn populate_fingerprints(&mut self) -> anyhow::Result<()> { - let mut memo = vec![None; self.types.len()]; - for index in 0..self.types.len() { - let fingerprint = self.compute_fingerprint(TypeId(index as u32), &mut memo)?; - self.types[index].fingerprint = fingerprint; - } - Ok(()) - } - - pub fn recompute_required_features(&mut self) { - let mut features = 0; - if self - .signatures - .iter() - .flat_map(|signature| signature.params.iter().chain(std::iter::once(&signature.result))) - .any(|transport| transport.transport_class == TransportClass::CapabilityHandle) - { - features |= REQUIRED_FEATURE_HANDLES; - } - if self.signatures.iter().any(|signature| { - signature.params.iter().chain(std::iter::once(&signature.result)).any(|transport| { - transport.transport_class == TransportClass::CanonicalValue - && self.transport_ref_uses_recursive_type(transport) - }) - }) { - features |= REQUIRED_FEATURE_RECURSIVE_CANONICAL; - } - if self.signatures.iter().any(|signature| { - signature - .params - .iter() - .chain(std::iter::once(&signature.result)) - .any(|transport| self.transport_ref_contains_union(transport)) - }) { - features |= REQUIRED_FEATURE_UNION_TRANSPORT; - } - if self.signatures.iter().any(|signature| { - signature - .params - .iter() - .chain(std::iter::once(&signature.result)) - .any(|transport| self.transport_ref_contains_intersection(transport)) - }) { - features |= REQUIRED_FEATURE_INTERSECTION_TRANSPORT; - } - self.required_features = features; - } - - pub fn normalize_commutative_members(&mut self) -> anyhow::Result<()> { - let mut memo = vec![None; self.types.len()]; - for index in 0..self.types.len() { - let _ = self.compute_fingerprint(TypeId(index as u32), &mut memo)?; - } - - for node in &mut self.types { - match &mut node.kind { - AbiTypeKind::Union { members } => { - members.sort_by_key(|member| { - ( - memo.get(member.0 as usize) - .copied() - .flatten() - .unwrap_or(TypeFingerprint::ZERO), - *member, - ) - }); - } - AbiTypeKind::Intersection { members, .. } => { - members.sort_by_key(|member| { - ( - memo.get(member.0 as usize) - .copied() - .flatten() - .unwrap_or(TypeFingerprint::ZERO), - *member, - ) - }); - } - _ => {} - } - } - - Ok(()) - } - - fn visit_scc(&self, index: usize, state: &mut TarjanState) { - state.indices.insert(index, state.next_index); - state.lowlinks.insert(index, state.next_index); - state.next_index += 1; - state.stack.push(index); - state.on_stack.insert(index); - - for edge in self.type_edges(TypeId(index as u32)) { - let edge_index = edge.0 as usize; - if !state.indices.contains_key(&edge_index) { - self.visit_scc(edge_index, state); - let lowlink = state.lowlinks[&index].min(state.lowlinks[&edge_index]); - state.lowlinks.insert(index, lowlink); - } else if state.on_stack.contains(&edge_index) { - let lowlink = state.lowlinks[&index].min(state.indices[&edge_index]); - state.lowlinks.insert(index, lowlink); - } - } - - if state.lowlinks[&index] == state.indices[&index] { - let mut component = Vec::new(); - while let Some(member) = state.stack.pop() { - state.on_stack.remove(&member); - component.push(member); - if member == index { - break; - } - } - component.sort_unstable(); - state.components.push(component); - } - } - - fn compute_fingerprint( - &self, - id: TypeId, - memo: &mut [Option], - ) -> anyhow::Result { - let index = id.0 as usize; - if let Some(fingerprint) = memo.get(index).copied().flatten() { - return Ok(fingerprint); - } - let node = self.type_node(id)?; - if let Some(group_id) = node.recursive_group { - self.compute_group_fingerprints(group_id, memo)?; - return memo[index] - .ok_or_else(|| anyhow!("missing recursive-group fingerprint for type `{}`", id.0)); - } - - let fingerprint = self.encode_type_for_hash(id, None, memo)?; - memo[index] = Some(fingerprint); - Ok(fingerprint) - } - - fn compute_group_fingerprints( - &self, - group_id: RecursiveGroupId, - memo: &mut [Option], - ) -> anyhow::Result<()> { - let group = self - .recursive_groups - .get(group_id.0 as usize) - .ok_or_else(|| anyhow!("unknown recursive group `{}`", group_id.0))?; - if group - .members - .iter() - .all(|member| memo.get(member.0 as usize).copied().flatten().is_some()) - { - return Ok(()); - } - - let positions = group - .members - .iter() - .enumerate() - .map(|(index, member)| (*member, index as u32)) - .collect::>(); - let mut encodings = Vec::with_capacity(group.members.len()); - for &member in &group.members { - encodings.push(self.encode_type_bytes(member, Some(&positions), memo)?); - } - - for (index, member) in group.members.iter().enumerate() { - let mut hasher = blake3::Hasher::new(); - hasher.update(b"mitki.abi.type.group.v1"); - hasher.update(&(group.members.len() as u32).to_le_bytes()); - for encoding in &encodings { - hasher.update(&(encoding.len() as u32).to_le_bytes()); - hasher.update(encoding); - } - hasher.update(&(index as u32).to_le_bytes()); - let mut fingerprint = [0; 16]; - fingerprint.copy_from_slice(&hasher.finalize().as_bytes()[..16]); - memo[member.0 as usize] = Some(TypeFingerprint(fingerprint)); - } - Ok(()) - } - - fn transport_ref_uses_recursive_type(&self, transport: &TransportRef) -> bool { - Self::transport_ref_roots(transport) - .into_iter() - .any(|root| self.type_uses_recursive_group(root)) - } - - fn transport_ref_contains_union(&self, transport: &TransportRef) -> bool { - Self::transport_ref_roots(transport).into_iter().any(|root| self.type_contains_union(root)) - } - - fn transport_ref_contains_intersection(&self, transport: &TransportRef) -> bool { - Self::transport_ref_roots(transport) - .into_iter() - .any(|root| self.type_contains_intersection(root)) - } - - fn transport_ref_roots(transport: &TransportRef) -> Vec { - let mut roots = vec![transport.semantic_type]; - if let Some(transport_type) = transport.transport_type { - roots.push(transport_type); - } - roots - } - - fn type_uses_recursive_group(&self, root: TypeId) -> bool { - let mut stack = vec![root]; - let mut seen = std::collections::BTreeSet::new(); - while let Some(id) = stack.pop() { - if !seen.insert(id) { - continue; - } - if self.types.get(id.0 as usize).and_then(|node| node.recursive_group).is_some() { - return true; - } - stack.extend(self.type_edges(id)); - } - false - } - - fn type_contains_union(&self, root: TypeId) -> bool { - let mut stack = vec![root]; - let mut seen = std::collections::BTreeSet::new(); - while let Some(id) = stack.pop() { - if !seen.insert(id) { - continue; - } - if matches!( - self.types.get(id.0 as usize).map(|node| &node.kind), - Some(AbiTypeKind::Union { .. }) - ) { - return true; - } - stack.extend(self.type_edges(id)); - } - false - } - - fn type_contains_intersection(&self, root: TypeId) -> bool { - let mut stack = vec![root]; - let mut seen = std::collections::BTreeSet::new(); - while let Some(id) = stack.pop() { - if !seen.insert(id) { - continue; - } - if matches!( - self.types.get(id.0 as usize).map(|node| &node.kind), - Some(AbiTypeKind::Intersection { .. }) - ) { - return true; - } - stack.extend(self.type_edges(id)); - } - false - } -} - -pub fn encode_semantic_type_graph(graph: &SemanticTypeGraph) -> anyhow::Result> { - let mut writer = BinaryWriter::new(); - writer.bytes(&METADATA_V2_MAGIC); - writer.u16(graph.semantic_version_major); - writer.u16(graph.semantic_version_minor); - writer.u16(graph.encoding_version); - writer.u64(graph.required_features); - encode_string_id(&mut writer, graph.transport_profile); - writer.u16(graph.profile_version_major); - writer.u16(graph.profile_version_minor); - writer.u32(graph.boundary_memory.memory_index); - writer.option(graph.boundary_memory.export_name, encode_string_id); - - encode_strings(&mut writer, &graph.strings); - encode_ids(&mut writer, &graph.field_names, encode_string_id); - encode_ids(&mut writer, &graph.variant_names, encode_string_id); - encode_ids(&mut writer, &graph.nominal_symbols, encode_string_id); - encode_ids(&mut writer, &graph.debug_names, encode_string_id); - encode_type_nodes(&mut writer, &graph.types); - encode_recursive_groups(&mut writer, &graph.recursive_groups); - encode_signatures(&mut writer, &graph.signatures); - encode_generic_origins(&mut writer, &graph.generic_origins); - encode_facet_plans(&mut writer, &graph.facet_plans); - encode_function_instances(&mut writer, &graph.function_instances); - encode_raw_imports(&mut writer, &graph.raw_imports); - encode_raw_exports(&mut writer, &graph.raw_exports); - Ok(writer.into_bytes()) -} - -pub fn decode_semantic_type_graph(bytes: &[u8]) -> anyhow::Result { - let mut reader = BinaryReader::new(bytes); - let magic = reader.fixed::<8>()?; - if magic != METADATA_V2_MAGIC { - bail!("invalid mitki.abi.v2 magic header"); - } - let semantic_version_major = reader.u16()?; - let semantic_version_minor = reader.u16()?; - let encoding_version = reader.u16()?; - let required_features = reader.u64()?; - let transport_profile = decode_string_id(&mut reader)?; - let profile_version_major = reader.u16()?; - let profile_version_minor = reader.u16()?; - let memory_index = reader.u32()?; - let export_name = reader.option(decode_string_id)?; - - let strings = decode_strings(&mut reader)?; - let field_names = decode_ids(&mut reader, decode_string_id)?; - let variant_names = decode_ids(&mut reader, decode_string_id)?; - let nominal_symbols = decode_ids(&mut reader, decode_string_id)?; - let debug_names = decode_ids(&mut reader, decode_string_id)?; - let types = decode_type_nodes(&mut reader)?; - let recursive_groups = decode_recursive_groups(&mut reader)?; - let signatures = decode_signatures(&mut reader)?; - let generic_origins = decode_generic_origins(&mut reader)?; - let facet_plans = decode_facet_plans(&mut reader)?; - let function_instances = decode_function_instances(&mut reader)?; - let raw_imports = decode_raw_imports(&mut reader)?; - let raw_exports = decode_raw_exports(&mut reader)?; - reader.ensure_finished()?; - - Ok(SemanticTypeGraph { - semantic_version_major, - semantic_version_minor, - encoding_version, - required_features, - transport_profile, - profile_version_major, - profile_version_minor, - boundary_memory: BoundaryMemory { memory_index, export_name }, - strings, - field_names, - variant_names, - nominal_symbols, - debug_names, - types, - recursive_groups, - signatures, - generic_origins, - facet_plans, - function_instances, - raw_imports, - raw_exports, - }) -} - -#[derive(Default)] -struct TarjanState { - next_index: u32, - indices: std::collections::BTreeMap, - lowlinks: std::collections::BTreeMap, - stack: Vec, - on_stack: std::collections::BTreeSet, - components: Vec>, -} - -impl SemanticTypeGraph { - fn type_edges(&self, id: TypeId) -> Vec { - let node = &self.types[id.0 as usize]; - match &node.kind { - AbiTypeKind::Unit - | AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Float { .. } - | AbiTypeKind::Char - | AbiTypeKind::String - | AbiTypeKind::Opaque { .. } => Vec::new(), - AbiTypeKind::Array { elem } => vec![*elem], - AbiTypeKind::Tuple { elems } => elems.clone(), - AbiTypeKind::Record { fields } | AbiTypeKind::Struct { fields, .. } => { - fields.iter().map(|field| field.ty).collect() - } - AbiTypeKind::Enum { variants, .. } => { - variants.iter().flat_map(|variant| variant.fields.iter().copied()).collect() - } - AbiTypeKind::Union { members } => members.clone(), - AbiTypeKind::Intersection { members, carrier, .. } => { - let mut edges = members.clone(); - edges.push(*carrier); - edges - } - AbiTypeKind::Function { params, result, .. } => { - let mut edges = params.clone(); - edges.push(*result); - edges - } - } - } - - fn encode_type_for_hash( - &self, - id: TypeId, - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - ) -> anyhow::Result { - let bytes = self.encode_type_bytes(id, local_positions, memo)?; - let mut hasher = blake3::Hasher::new(); - hasher.update(b"mitki.abi.type.v1"); - hasher.update(&(bytes.len() as u32).to_le_bytes()); - hasher.update(&bytes); - let mut fingerprint = [0; 16]; - fingerprint.copy_from_slice(&hasher.finalize().as_bytes()[..16]); - Ok(TypeFingerprint(fingerprint)) - } - - fn encode_type_bytes( - &self, - id: TypeId, - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - ) -> anyhow::Result> { - let node = self.type_node(id)?; - let mut writer = BinaryWriter::new(); - match &node.kind { - AbiTypeKind::Unit => writer.u8(0), - AbiTypeKind::Bool => writer.u8(1), - AbiTypeKind::Int { signed, bits } => { - writer.u8(2); - writer.bool(*signed); - writer.u16(*bits); - } - AbiTypeKind::Float { bits } => { - writer.u8(3); - writer.u16(*bits); - } - AbiTypeKind::Char => writer.u8(4), - AbiTypeKind::String => writer.u8(5), - AbiTypeKind::Array { elem } => { - writer.u8(6); - self.encode_type_ref(&mut writer, *elem, local_positions, memo)?; - } - AbiTypeKind::Tuple { elems } => { - writer.u8(7); - encode_type_refs(&mut writer, elems, local_positions, memo, self)?; - } - AbiTypeKind::Record { fields } => { - writer.u8(8); - encode_record_fields(&mut writer, fields, local_positions, memo, self)?; - } - AbiTypeKind::Struct { nominal, fields } => { - writer.u8(9); - encode_symbol_id(&mut writer, *nominal); - encode_record_fields(&mut writer, fields, local_positions, memo, self)?; - } - AbiTypeKind::Enum { nominal, variants } => { - writer.u8(10); - encode_symbol_id(&mut writer, *nominal); - writer.len(variants.len()); - for variant in variants { - encode_variant_name_id(&mut writer, variant.name); - encode_type_refs(&mut writer, &variant.fields, local_positions, memo, self)?; - } - } - AbiTypeKind::Union { members } => { - writer.u8(11); - encode_normalized_members(&mut writer, members, local_positions, memo, self)?; - } - AbiTypeKind::Intersection { members, carrier, facet_plan } => { - writer.u8(12); - encode_normalized_members(&mut writer, members, local_positions, memo, self)?; - self.encode_type_ref(&mut writer, *carrier, local_positions, memo)?; - encode_facet_plan_fingerprint( - &mut writer, - *facet_plan, - local_positions, - memo, - self, - )?; - } - AbiTypeKind::Function { params, result, domain } => { - writer.u8(13); - encode_type_refs(&mut writer, params, local_positions, memo, self)?; - self.encode_type_ref(&mut writer, *result, local_positions, memo)?; - writer.u8(domain.as_bits()); - } - AbiTypeKind::Opaque { capability_id } => { - writer.u8(14); - encode_capability_id(&mut writer, *capability_id); - } - } - Ok(writer.into_bytes()) - } - - fn encode_type_ref( - &self, - writer: &mut BinaryWriter, - id: TypeId, - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - ) -> anyhow::Result<()> { - if let Some(position) = local_positions.and_then(|positions| positions.get(&id).copied()) { - writer.u8(0); - writer.u32(position); - return Ok(()); - } - writer.u8(1); - let fingerprint = self.compute_fingerprint(id, memo)?; - writer.bytes(&fingerprint.0); - Ok(()) - } -} - -fn encode_strings(writer: &mut BinaryWriter, values: &[String]) { - writer.len(values.len()); - for value in values { - writer.string(value); - } -} - -fn decode_strings(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - (0..len).map(|_| reader.string()).collect() -} - -fn encode_ids( - writer: &mut BinaryWriter, - values: &[T], - encode: impl Fn(&mut BinaryWriter, T), -) { - writer.len(values.len()); - for &value in values { - encode(writer, value); - } -} - -fn decode_ids( - reader: &mut BinaryReader<'_>, - decode: impl Fn(&mut BinaryReader<'_>) -> anyhow::Result, -) -> anyhow::Result> { - let len = reader.len()?; - (0..len).map(|_| decode(reader)).collect() -} - -fn encode_type_nodes(writer: &mut BinaryWriter, values: &[TypeNode]) { - writer.len(values.len()); - for value in values { - encode_type_id(writer, value.id); - writer.bytes(&value.fingerprint.0); - writer.u32(value.flags); - writer.option(value.recursive_group, encode_recursive_group_id); - writer.option(value.debug_name, encode_debug_name_id); - encode_type_kind(writer, &value.kind); - } -} - -fn decode_type_nodes(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(TypeNode { - id: decode_type_id(reader)?, - fingerprint: TypeFingerprint(reader.fixed::<16>()?), - flags: reader.u32()?, - recursive_group: reader.option(decode_recursive_group_id)?, - debug_name: reader.option(decode_debug_name_id)?, - kind: decode_type_kind(reader)?, - }); - } - Ok(values) -} - -fn encode_recursive_groups(writer: &mut BinaryWriter, values: &[RecursiveGroup]) { - writer.len(values.len()); - for value in values { - encode_recursive_group_id(writer, value.id); - encode_ids(writer, &value.members, encode_type_id); - } -} - -fn decode_recursive_groups(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(RecursiveGroup { - id: decode_recursive_group_id(reader)?, - members: decode_ids(reader, decode_type_id)?, - }); - } - Ok(values) -} - -fn encode_facet_plans(writer: &mut BinaryWriter, values: &[FacetPlan]) { - writer.len(values.len()); - for value in values { - encode_facet_plan_id(writer, value.id); - writer.len(value.entries.len()); - for entry in &value.entries { - encode_type_id(writer, entry.member); - writer.u8(match entry.kind { - FacetPlanEntryKind::Erased => 0, - FacetPlanEntryKind::ValueFacet => 1, - FacetPlanEntryKind::HandleFacet => 2, - }); - } - } -} - -fn decode_facet_plans(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - let id = decode_facet_plan_id(reader)?; - let entry_len = reader.len()?; - let mut entries = Vec::with_capacity(entry_len); - for _ in 0..entry_len { - entries.push(FacetPlanEntry { - member: decode_type_id(reader)?, - kind: decode_facet_plan_entry_kind(reader.u8()?)?, - }); - } - values.push(FacetPlan { id, entries }); - } - Ok(values) -} - -fn encode_signatures(writer: &mut BinaryWriter, values: &[FunctionSignature]) { - writer.len(values.len()); - for value in values { - encode_sig_id(writer, value.id); - encode_type_id(writer, value.function_type); - encode_transport_refs(writer, &value.params); - encode_transport_ref(writer, &value.result); - } -} - -fn decode_signatures(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(FunctionSignature { - id: decode_sig_id(reader)?, - function_type: decode_type_id(reader)?, - params: decode_transport_refs(reader)?, - result: decode_transport_ref(reader)?, - }); - } - Ok(values) -} - -fn encode_generic_origins(writer: &mut BinaryWriter, values: &[GenericOrigin]) { - writer.len(values.len()); - for value in values { - encode_generic_origin_id(writer, value.id); - encode_symbol_id(writer, value.symbol); - } -} - -fn decode_generic_origins(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(GenericOrigin { - id: decode_generic_origin_id(reader)?, - symbol: decode_symbol_id(reader)?, - }); - } - Ok(values) -} - -fn encode_function_instances(writer: &mut BinaryWriter, values: &[FunctionInstance]) { - writer.len(values.len()); - for value in values { - encode_instance_id(writer, value.id); - encode_symbol_id(writer, value.logical_symbol); - writer.option(value.generic_origin, encode_generic_origin_id); - encode_ids(writer, &value.type_args, encode_type_id); - encode_sig_id(writer, value.signature); - writer.u8(value.domain.as_bits()); - writer.u8(match value.linkage { - LinkageKind::WasmExport => 0, - LinkageKind::WasmImport => 1, - LinkageKind::StageEntry => 2, - LinkageKind::RawImport => 3, - }); - writer.option(value.wasm_module_name, encode_string_id); - writer.option(value.wasm_field_name, encode_string_id); - } -} - -fn decode_function_instances( - reader: &mut BinaryReader<'_>, -) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - let id = decode_instance_id(reader)?; - let logical_symbol = decode_symbol_id(reader)?; - let generic_origin = reader.option(decode_generic_origin_id)?; - let type_args = decode_ids(reader, decode_type_id)?; - let signature = decode_sig_id(reader)?; - let domain = ExecutionDomain::from_bits(reader.u8()?)?; - let linkage = match reader.u8()? { - 0 => LinkageKind::WasmExport, - 1 => LinkageKind::WasmImport, - 2 => LinkageKind::StageEntry, - 3 => LinkageKind::RawImport, - value => bail!("unknown linkage tag `{value}`"), - }; - values.push(FunctionInstance { - id, - logical_symbol, - generic_origin, - type_args, - signature, - domain, - linkage, - wasm_module_name: reader.option(decode_string_id)?, - wasm_field_name: reader.option(decode_string_id)?, - }); - } - Ok(values) -} - -fn encode_raw_imports(writer: &mut BinaryWriter, values: &[RawImportRecord]) { - writer.len(values.len()); - for value in values { - encode_string_id(writer, value.module); - encode_string_id(writer, value.field); - writer.option(value.symbol, encode_symbol_id); - writer.option(value.signature, encode_sig_id); - } -} - -fn decode_raw_imports(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(RawImportRecord { - module: decode_string_id(reader)?, - field: decode_string_id(reader)?, - symbol: reader.option(decode_symbol_id)?, - signature: reader.option(decode_sig_id)?, - }); - } - Ok(values) -} - -fn encode_raw_exports(writer: &mut BinaryWriter, values: &[RawExportRecord]) { - writer.len(values.len()); - for value in values { - encode_string_id(writer, value.name); - writer.option(value.symbol, encode_symbol_id); - writer.option(value.signature, encode_sig_id); - } -} - -fn decode_raw_exports(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values.push(RawExportRecord { - name: decode_string_id(reader)?, - symbol: reader.option(decode_symbol_id)?, - signature: reader.option(decode_sig_id)?, - }); - } - Ok(values) -} - -fn encode_transport_refs(writer: &mut BinaryWriter, values: &[TransportRef]) { - writer.len(values.len()); - for value in values { - encode_transport_ref(writer, value); - } -} - -fn decode_transport_refs(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - (0..len).map(|_| decode_transport_ref(reader)).collect() -} - -fn encode_transport_ref(writer: &mut BinaryWriter, value: &TransportRef) { - encode_type_id(writer, value.semantic_type); - writer.u8(match value.transport_class { - TransportClass::Immediate => 0, - TransportClass::CanonicalValue => 1, - TransportClass::CapabilityHandle => 2, - }); - writer.option(value.transport_type, encode_type_id); -} - -fn decode_transport_ref(reader: &mut BinaryReader<'_>) -> anyhow::Result { - let semantic_type = decode_type_id(reader)?; - let transport_class = match reader.u8()? { - 0 => TransportClass::Immediate, - 1 => TransportClass::CanonicalValue, - 2 => TransportClass::CapabilityHandle, - value => bail!("unknown transport class tag `{value}`"), - }; - Ok(TransportRef { - semantic_type, - transport_class, - transport_type: reader.option(decode_type_id)?, - }) -} - -fn decode_facet_plan_entry_kind(tag: u8) -> anyhow::Result { - Ok(match tag { - 0 => FacetPlanEntryKind::Erased, - 1 => FacetPlanEntryKind::ValueFacet, - 2 => FacetPlanEntryKind::HandleFacet, - value => bail!("unknown facet-plan entry tag `{value}`"), - }) -} - -fn encode_type_kind(writer: &mut BinaryWriter, value: &AbiTypeKind) { - match value { - AbiTypeKind::Unit => writer.u8(0), - AbiTypeKind::Bool => writer.u8(1), - AbiTypeKind::Int { signed, bits } => { - writer.u8(2); - writer.bool(*signed); - writer.u16(*bits); - } - AbiTypeKind::Float { bits } => { - writer.u8(3); - writer.u16(*bits); - } - AbiTypeKind::Char => writer.u8(4), - AbiTypeKind::String => writer.u8(5), - AbiTypeKind::Array { elem } => { - writer.u8(6); - encode_type_id(writer, *elem); - } - AbiTypeKind::Tuple { elems } => { - writer.u8(7); - encode_ids(writer, elems, encode_type_id); - } - AbiTypeKind::Record { fields } => { - writer.u8(8); - encode_field_list(writer, fields); - } - AbiTypeKind::Struct { nominal, fields } => { - writer.u8(9); - encode_symbol_id(writer, *nominal); - encode_field_list(writer, fields); - } - AbiTypeKind::Enum { nominal, variants } => { - writer.u8(10); - encode_symbol_id(writer, *nominal); - writer.len(variants.len()); - for variant in variants { - encode_variant_name_id(writer, variant.name); - encode_ids(writer, &variant.fields, encode_type_id); - } - } - AbiTypeKind::Union { members } => { - writer.u8(11); - encode_ids(writer, members, encode_type_id); - } - AbiTypeKind::Intersection { members, carrier, facet_plan } => { - writer.u8(12); - encode_ids(writer, members, encode_type_id); - encode_type_id(writer, *carrier); - writer.option(*facet_plan, encode_facet_plan_id); - } - AbiTypeKind::Function { params, result, domain } => { - writer.u8(13); - encode_ids(writer, params, encode_type_id); - encode_type_id(writer, *result); - writer.u8(domain.as_bits()); - } - AbiTypeKind::Opaque { capability_id } => { - writer.u8(14); - encode_capability_id(writer, *capability_id); - } - } -} - -fn decode_type_kind(reader: &mut BinaryReader<'_>) -> anyhow::Result { - Ok(match reader.u8()? { - 0 => AbiTypeKind::Unit, - 1 => AbiTypeKind::Bool, - 2 => AbiTypeKind::Int { signed: reader.bool()?, bits: reader.u16()? }, - 3 => AbiTypeKind::Float { bits: reader.u16()? }, - 4 => AbiTypeKind::Char, - 5 => AbiTypeKind::String, - 6 => AbiTypeKind::Array { elem: decode_type_id(reader)? }, - 7 => AbiTypeKind::Tuple { elems: decode_ids(reader, decode_type_id)? }, - 8 => AbiTypeKind::Record { fields: decode_field_list(reader)? }, - 9 => AbiTypeKind::Struct { - nominal: decode_symbol_id(reader)?, - fields: decode_field_list(reader)?, - }, - 10 => { - let nominal = decode_symbol_id(reader)?; - let len = reader.len()?; - let mut variants = Vec::with_capacity(len); - for _ in 0..len { - variants.push(EnumVariant { - name: decode_variant_name_id(reader)?, - fields: decode_ids(reader, decode_type_id)?, - }); - } - AbiTypeKind::Enum { nominal, variants } - } - 11 => AbiTypeKind::Union { members: decode_ids(reader, decode_type_id)? }, - 12 => AbiTypeKind::Intersection { - members: decode_ids(reader, decode_type_id)?, - carrier: decode_type_id(reader)?, - facet_plan: reader.option(decode_facet_plan_id)?, - }, - 13 => AbiTypeKind::Function { - params: decode_ids(reader, decode_type_id)?, - result: decode_type_id(reader)?, - domain: ExecutionDomain::from_bits(reader.u8()?)?, - }, - 14 => AbiTypeKind::Opaque { capability_id: decode_capability_id(reader)? }, - value => bail!("unknown abi type tag `{value}`"), - }) -} - -fn encode_facet_plan_fingerprint( - writer: &mut BinaryWriter, - plan_id: Option, - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - graph: &SemanticTypeGraph, -) -> anyhow::Result<()> { - match plan_id { - Some(plan_id) => { - writer.bool(true); - let plan = graph - .facet_plans - .get(plan_id.0 as usize) - .ok_or_else(|| anyhow!("unknown facet plan `{}`", plan_id.0))?; - writer.len(plan.entries.len()); - for entry in &plan.entries { - graph.encode_type_ref(writer, entry.member, local_positions, memo)?; - writer.u8(match entry.kind { - FacetPlanEntryKind::Erased => 0, - FacetPlanEntryKind::ValueFacet => 1, - FacetPlanEntryKind::HandleFacet => 2, - }); - } - } - None => writer.bool(false), - } - Ok(()) -} - -fn encode_field_list(writer: &mut BinaryWriter, values: &[RecordField]) { - writer.len(values.len()); - for value in values { - encode_field_name_id(writer, value.name); - encode_type_id(writer, value.ty); - } -} - -fn decode_field_list(reader: &mut BinaryReader<'_>) -> anyhow::Result> { - let len = reader.len()?; - let mut values = Vec::with_capacity(len); - for _ in 0..len { - values - .push(RecordField { name: decode_field_name_id(reader)?, ty: decode_type_id(reader)? }); - } - Ok(values) -} - -fn encode_record_fields( - writer: &mut BinaryWriter, - values: &[RecordField], - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - graph: &SemanticTypeGraph, -) -> anyhow::Result<()> { - writer.len(values.len()); - for value in values { - encode_field_name_id(writer, value.name); - graph.encode_type_ref(writer, value.ty, local_positions, memo)?; - } - Ok(()) -} - -fn encode_type_refs( - writer: &mut BinaryWriter, - values: &[TypeId], - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - graph: &SemanticTypeGraph, -) -> anyhow::Result<()> { - writer.len(values.len()); - for &value in values { - graph.encode_type_ref(writer, value, local_positions, memo)?; - } - Ok(()) -} - -fn encode_normalized_members( - writer: &mut BinaryWriter, - members: &[TypeId], - local_positions: Option<&std::collections::BTreeMap>, - memo: &mut [Option], - graph: &SemanticTypeGraph, -) -> anyhow::Result<()> { - let mut keyed = Vec::with_capacity(members.len()); - for &member in members { - let key = if let Some(position) = - local_positions.and_then(|positions| positions.get(&member).copied()) - { - (TypeFingerprint::ZERO, position) - } else { - (graph.compute_fingerprint(member, memo)?, u32::MAX) - }; - keyed.push((key, member)); - } - keyed.sort_by_key(|entry| entry.0); - writer.len(keyed.len()); - for (_, member) in keyed { - graph.encode_type_ref(writer, member, local_positions, memo)?; - } - Ok(()) -} - -macro_rules! encode_decode_id { - ($encode:ident, $decode:ident, $ty:ident) => { - fn $encode(writer: &mut BinaryWriter, value: $ty) { - writer.u32(value.0); - } - - fn $decode(reader: &mut BinaryReader<'_>) -> anyhow::Result<$ty> { - Ok($ty(reader.u32()?)) - } - }; -} - -encode_decode_id!(encode_string_id, decode_string_id, StringId); -encode_decode_id!(encode_field_name_id, decode_field_name_id, FieldNameId); -encode_decode_id!(encode_variant_name_id, decode_variant_name_id, VariantNameId); -encode_decode_id!(encode_symbol_id, decode_symbol_id, SymbolId); -encode_decode_id!(encode_debug_name_id, decode_debug_name_id, DebugNameId); -encode_decode_id!(encode_recursive_group_id, decode_recursive_group_id, RecursiveGroupId); -encode_decode_id!(encode_type_id, decode_type_id, TypeId); -encode_decode_id!(encode_sig_id, decode_sig_id, SigId); -encode_decode_id!(encode_instance_id, decode_instance_id, InstanceId); -encode_decode_id!(encode_generic_origin_id, decode_generic_origin_id, GenericOriginId); -encode_decode_id!(encode_facet_plan_id, decode_facet_plan_id, FacetPlanId); -encode_decode_id!(encode_capability_id, decode_capability_id, CapabilityId); - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_graph() -> SemanticTypeGraph { - let mut graph = SemanticTypeGraph { - strings: vec![ - "wasm-core-v2/m32".to_owned(), - "Point".to_owned(), - "x".to_owned(), - "y".to_owned(), - "main".to_owned(), - "memory".to_owned(), - ], - transport_profile: StringId(0), - profile_version_major: 1, - boundary_memory: BoundaryMemory { memory_index: 0, export_name: Some(StringId(5)) }, - field_names: vec![StringId(2), StringId(3)], - nominal_symbols: vec![StringId(1), StringId(4)], - ..SemanticTypeGraph::default() - }; - - let int = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let point = graph.push_type(AbiTypeKind::Struct { - nominal: SymbolId(0), - fields: vec![ - RecordField { name: FieldNameId(0), ty: int }, - RecordField { name: FieldNameId(1), ty: int }, - ], - }); - let function = graph.push_type(AbiTypeKind::Function { - params: vec![point], - result: point, - domain: ExecutionDomain::Runtime, - }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: function, - params: vec![TransportRef { - semantic_type: point, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }], - result: TransportRef { - semantic_type: point, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - }); - graph.function_instances.push(FunctionInstance { - id: InstanceId(0), - logical_symbol: SymbolId(1), - generic_origin: None, - type_args: Vec::new(), - signature: SigId(0), - domain: ExecutionDomain::Runtime, - linkage: LinkageKind::WasmExport, - wasm_module_name: None, - wasm_field_name: Some(StringId(4)), - }); - graph.raw_exports.push(RawExportRecord { - name: StringId(4), - symbol: Some(SymbolId(1)), - signature: Some(SigId(0)), - }); - graph - } - - #[test] - fn semantic_graph_binary_round_trip() { - let mut graph = sample_graph(); - graph.populate_recursive_groups(); - graph.recompute_required_features(); - graph.populate_fingerprints().expect("fingerprints should compute"); - let encoded = encode_semantic_type_graph(&graph).expect("graph should encode"); - let decoded = decode_semantic_type_graph(&encoded).expect("graph should decode"); - assert_eq!(decoded, graph); - } - - #[test] - fn facet_plan_binary_round_trip() { - let mut graph = SemanticTypeGraph::default(); - let carrier = graph.push_type(AbiTypeKind::Bool); - let handle = graph.push_type(AbiTypeKind::Opaque { capability_id: CapabilityId(0) }); - graph.facet_plans.push(FacetPlan { - id: FacetPlanId(0), - entries: vec![ - FacetPlanEntry { member: carrier, kind: FacetPlanEntryKind::Erased }, - FacetPlanEntry { member: handle, kind: FacetPlanEntryKind::HandleFacet }, - ], - }); - let intersection = graph.push_type(AbiTypeKind::Intersection { - members: vec![carrier, handle], - carrier, - facet_plan: Some(FacetPlanId(0)), - }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: intersection, - params: vec![TransportRef { - semantic_type: intersection, - transport_class: TransportClass::CanonicalValue, - transport_type: Some(carrier), - }], - result: TransportRef { - semantic_type: intersection, - transport_class: TransportClass::CanonicalValue, - transport_type: Some(carrier), - }, - }); - graph.populate_recursive_groups(); - graph.populate_fingerprints().expect("fingerprints should compute"); - - let decoded = decode_semantic_type_graph( - &encode_semantic_type_graph(&graph).expect("graph should encode"), - ) - .expect("graph should decode"); - - assert_eq!(decoded.facet_plans, graph.facet_plans); - assert_eq!(decoded.types[intersection.0 as usize], graph.types[intersection.0 as usize]); - } - - #[test] - fn fingerprints_are_stable_for_commutative_unions() { - let mut left = SemanticTypeGraph::default(); - let a = left.push_type(AbiTypeKind::Bool); - let b = left.push_type(AbiTypeKind::String); - let union = left.push_type(AbiTypeKind::Union { members: vec![a, b] }); - left.populate_recursive_groups(); - left.populate_fingerprints().expect("left fingerprints"); - - let mut right = SemanticTypeGraph::default(); - let a = right.push_type(AbiTypeKind::Bool); - let b = right.push_type(AbiTypeKind::String); - let union_other = right.push_type(AbiTypeKind::Union { members: vec![b, a] }); - right.populate_recursive_groups(); - right.populate_fingerprints().expect("right fingerprints"); - - assert_eq!( - left.types[union.0 as usize].fingerprint, - right.types[union_other.0 as usize].fingerprint - ); - } - - #[test] - fn fingerprints_ignore_module_local_type_ids() { - let mut left = SemanticTypeGraph::default(); - let bool_ty = left.push_type(AbiTypeKind::Bool); - let array = left.push_type(AbiTypeKind::Array { elem: bool_ty }); - left.populate_recursive_groups(); - left.populate_fingerprints().expect("left fingerprints"); - - let mut right = SemanticTypeGraph::default(); - let _padding = right.push_type(AbiTypeKind::String); - let bool_ty = right.push_type(AbiTypeKind::Bool); - let shifted = right.push_type(AbiTypeKind::Array { elem: bool_ty }); - right.populate_recursive_groups(); - right.populate_fingerprints().expect("right fingerprints"); - - assert_eq!( - left.types[array.0 as usize].fingerprint, - right.types[shifted.0 as usize].fingerprint - ); - } - - #[test] - fn recursive_groups_detect_self_cycles() { - let mut graph = SemanticTypeGraph::default(); - let list = graph.push_type(AbiTypeKind::Array { elem: TypeId(0) }); - assert_eq!(list, TypeId(0)); - graph.populate_recursive_groups(); - assert_eq!(graph.recursive_groups.len(), 1); - assert_eq!(graph.types[0].recursive_group, Some(RecursiveGroupId(0))); - } - - #[test] - fn required_features_follow_boundary_transports() { - let mut graph = SemanticTypeGraph::default(); - let int = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let recursive = graph.push_type(AbiTypeKind::Array { elem: TypeId(1) }); - let union = graph.push_type(AbiTypeKind::Union { members: vec![int, recursive] }); - let intersection = graph.push_type(AbiTypeKind::Intersection { - members: vec![int], - carrier: int, - facet_plan: None, - }); - graph.signatures.push(FunctionSignature { - id: SigId(0), - function_type: int, - params: vec![ - TransportRef { - semantic_type: int, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }, - TransportRef { - semantic_type: union, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - TransportRef { - semantic_type: intersection, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - ], - result: TransportRef { - semantic_type: int, - transport_class: TransportClass::Immediate, - transport_type: None, - }, - }); - graph.populate_recursive_groups(); - graph.recompute_required_features(); - - assert_eq!( - graph.required_features, - REQUIRED_FEATURE_HANDLES - | REQUIRED_FEATURE_RECURSIVE_CANONICAL - | REQUIRED_FEATURE_UNION_TRANSPORT - | REQUIRED_FEATURE_INTERSECTION_TRANSPORT - ); - } - - #[test] - fn normalize_commutative_members_sorts_union_members_by_fingerprint() { - let mut graph = SemanticTypeGraph::default(); - let string = graph.push_type(AbiTypeKind::String); - let boolean = graph.push_type(AbiTypeKind::Bool); - let union = graph.push_type(AbiTypeKind::Union { members: vec![string, boolean] }); - - graph.populate_recursive_groups(); - graph.populate_fingerprints().expect("fingerprints should compute"); - graph.normalize_commutative_members().expect("union members should normalize"); - - let AbiTypeKind::Union { members } = &graph.types[union.0 as usize].kind else { - panic!("expected union type"); - }; - let fingerprints = members - .iter() - .map(|member| graph.types[member.0 as usize].fingerprint) - .collect::>(); - let mut sorted = fingerprints.clone(); - sorted.sort_unstable(); - assert_eq!(fingerprints, sorted); - } - - #[test] - fn facet_plan_fingerprints_use_plan_contents_not_plan_ids() { - let mut left = SemanticTypeGraph::default(); - let carrier = left.push_type(AbiTypeKind::Bool); - let handle = left.push_type(AbiTypeKind::Opaque { capability_id: CapabilityId(0) }); - left.facet_plans.push(FacetPlan { - id: FacetPlanId(0), - entries: vec![ - FacetPlanEntry { member: carrier, kind: FacetPlanEntryKind::Erased }, - FacetPlanEntry { member: handle, kind: FacetPlanEntryKind::HandleFacet }, - ], - }); - let left_intersection = left.push_type(AbiTypeKind::Intersection { - members: vec![carrier, handle], - carrier, - facet_plan: Some(FacetPlanId(0)), - }); - left.populate_recursive_groups(); - left.populate_fingerprints().expect("left fingerprints"); - - let mut right = SemanticTypeGraph::default(); - let _padding = right.push_type(AbiTypeKind::String); - let carrier = right.push_type(AbiTypeKind::Bool); - let handle = right.push_type(AbiTypeKind::Opaque { capability_id: CapabilityId(0) }); - for index in 0..8 { - right.facet_plans.push(FacetPlan { - id: FacetPlanId(index), - entries: if index == 7 { - vec![ - FacetPlanEntry { member: carrier, kind: FacetPlanEntryKind::Erased }, - FacetPlanEntry { member: handle, kind: FacetPlanEntryKind::HandleFacet }, - ] - } else { - Vec::new() - }, - }); - } - let right_intersection = right.push_type(AbiTypeKind::Intersection { - members: vec![carrier, handle], - carrier, - facet_plan: Some(FacetPlanId(7)), - }); - right.populate_recursive_groups(); - right.populate_fingerprints().expect("right fingerprints"); - - assert_eq!( - left.types[left_intersection.0 as usize].fingerprint, - right.types[right_intersection.0 as usize].fingerprint - ); - } -} diff --git a/crates/mitki-analysis/Cargo.toml b/crates/mitki-analysis/Cargo.toml deleted file mode 100644 index ee86394..0000000 --- a/crates/mitki-analysis/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "mitki-analysis" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-errors.workspace = true -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-parse.workspace = true -mitki-resolve.workspace = true -mitki-span.workspace = true -mitki-typeck.workspace = true -mitki-yellow.workspace = true -patmat = "0.2.0" -rustc-hash = "2.0" -salsa.workspace = true -text-size.workspace = true - -[dev-dependencies] -mitki-db.workspace = true diff --git a/crates/mitki-analysis/src/lib.rs b/crates/mitki-analysis/src/lib.rs deleted file mode 100644 index 2d39ca9..0000000 --- a/crates/mitki-analysis/src/lib.rs +++ /dev/null @@ -1,945 +0,0 @@ -mod match_analysis; -pub mod ownership; -pub mod semantics; -pub mod wasm_boundary_legality; - -use std::collections::VecDeque; - -use mitki_errors::Diagnostic; -use mitki_hir::hir::{ExprId, Function, NodeKind, NodeStore, PatId, StmtId, WasmLinkage}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_inputs::PackageId; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{BoundaryInstanceKind, Declaration, FunctionLocation}; -use mitki_lower::{HasItemDecls as _, HasPackageDecls as _}; -use mitki_parse::FileParse as _; -use mitki_resolve::{ - BindingId, Resolver, is_reserved_compiler_name, lookup_runtime_function, - resolve_method_for_receiver, -}; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::ast::Node as _; -use rustc_hash::FxHashSet; -use salsa::plumbing::AsId as _; -pub use semantics::{ResolveIntent, Semantics}; - -#[salsa::tracked(returns(ref), no_eq)] -pub fn check_file(db: &dyn salsa::Database, file: mitki_inputs::File) -> Vec { - use mitki_span::IntoSymbol as _; - use mitki_yellow::ast::HasName as _; - - let mut diagnostics = file.parse(db).diagnostics().to_owned(); - let mut seen_boundary_instances = FxHashSet::default(); - - for declaration in PackageId::new(db, file).package_decls(db).declarations() { - match declaration { - Declaration::Function(func) => { - let source = func.source(db); - if let Some(name_node) = source.name() { - let symbol = name_node.as_str().into_symbol(db); - if let Some(runtime) = lookup_runtime_function(db, symbol) { - diagnostics.push(Diagnostic::error( - format!( - "`{}` is a reserved runtime function name", - runtime.source_name() - ), - name_node.text_range(), - )); - } - if is_reserved_compiler_name(db, symbol) { - diagnostics.push(Diagnostic::error( - format!( - "`{}` is a reserved compiler intrinsic name", - name_node.as_str() - ), - name_node.text_range(), - )); - } - } - diagnostics.extend(check_function(db, *func).iter().cloned()); - } - Declaration::BoundaryInstance(instance) => { - diagnostics.extend(wasm_boundary_legality::check_boundary_instance_legality( - db, *instance, - )); - if let Some(origin) = instance.origin(db) { - let key = ( - instance.kind(db), - origin.as_id().as_bits(), - instance.type_args(db).clone(), - ); - if !seen_boundary_instances.insert(key) { - diagnostics.push(Diagnostic::error( - format!( - "duplicate boundary instance declaration for `{}`", - instance.source(db).name().map_or("", |name| name.as_str()) - ), - mitki_yellow::SyntaxNodePtr::new(instance.source(db).syntax()).range, - )); - } - } - } - Declaration::Struct(struct_) => { - let source = struct_.source(db); - if let Some(name_node) = source.name() { - let symbol = name_node.as_str().into_symbol(db); - if is_reserved_compiler_name(db, symbol) { - diagnostics.push(Diagnostic::error( - format!( - "`{}` is a reserved compiler intrinsic name", - name_node.as_str() - ), - name_node.text_range(), - )); - } - } - diagnostics.extend(check_struct_destructor_legality(db, *struct_)); - } - Declaration::Enum(enum_) => { - let source = enum_.source(db); - if let Some(name_node) = source.name() { - let symbol = name_node.as_str().into_symbol(db); - if is_reserved_compiler_name(db, symbol) { - diagnostics.push(Diagnostic::error( - format!( - "`{}` is a reserved compiler intrinsic name", - name_node.as_str() - ), - name_node.text_range(), - )); - } - } - diagnostics.extend(check_enum_destructor_legality(db, *enum_)); - } - } - } - - diagnostics -} - -fn check_struct_destructor_legality( - db: &dyn salsa::Database, - location: mitki_lower::item::scope::StructLocation<'_>, -) -> Vec { - use mitki_yellow::ast::Node as _; - - let source = location.source(db); - let Some(field_list) = source.field_list() else { - return Vec::new(); - }; - let destructors = field_list.destructors().collect::>(); - let mut diagnostics = Vec::new(); - if destructors.len() > 1 { - for destructor in destructors.iter().skip(1) { - diagnostics.push(Diagnostic::error( - "duplicate destructor declaration", - mitki_yellow::SyntaxNodePtr::new(destructor.syntax()).range, - )); - } - } - if source.is_extern() && !destructors.is_empty() { - diagnostics.push(Diagnostic::error( - "`extern struct` cannot declare a destructor", - mitki_yellow::SyntaxNodePtr::new(destructors[0].syntax()).range, - )); - } - diagnostics.extend(destructors.iter().flat_map(check_destructor_signature)); - diagnostics -} - -fn check_enum_destructor_legality( - db: &dyn salsa::Database, - location: mitki_lower::item::scope::EnumLocation<'_>, -) -> Vec { - use mitki_yellow::ast::Node as _; - - let source = location.source(db); - let Some(variant_list) = source.variant_list() else { - return Vec::new(); - }; - let destructors = variant_list.destructors().collect::>(); - let mut diagnostics = Vec::new(); - if destructors.len() > 1 { - for destructor in destructors.iter().skip(1) { - diagnostics.push(Diagnostic::error( - "duplicate destructor declaration", - mitki_yellow::SyntaxNodePtr::new(destructor.syntax()).range, - )); - } - } - diagnostics.extend(destructors.iter().flat_map(check_destructor_signature)); - diagnostics -} - -fn check_destructor_signature( - destructor: &mitki_yellow::ast::DestructorDef<'_>, -) -> Vec { - use mitki_yellow::ast::{Node as _, Pattern}; - - let mut diagnostics = Vec::new(); - let params = - destructor.params().map(|params| params.iter().collect::>()).unwrap_or_default(); - let range = mitki_yellow::SyntaxNodePtr::new(destructor.syntax()).range; - if params.len() != 1 { - diagnostics.push(Diagnostic::error( - "destructor must declare exactly one parameter: `drop(var self) { ... }`", - range, - )); - return diagnostics; - } - - let param = ¶ms[0]; - if param.ty().is_some() { - diagnostics.push(Diagnostic::error( - "destructor parameter type is implicit; write `drop(var self) { ... }`", - mitki_yellow::SyntaxNodePtr::new(param.syntax()).range, - )); - } else if !param.is_mutable() { - diagnostics.push(Diagnostic::error( - "destructor parameter must be declared as `var self`", - mitki_yellow::SyntaxNodePtr::new(param.syntax()).range, - )); - } - match param.pattern() { - Some(Pattern::Binding(binding)) - if binding.name().is_some_and(|name| name.as_str() == "self") => {} - _ => diagnostics.push(Diagnostic::error( - "destructor parameter must be exactly `var self`", - mitki_yellow::SyntaxNodePtr::new(param.syntax()).range, - )), - } - - diagnostics -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn check_function(db: &dyn salsa::Database, func: FunctionLocation<'_>) -> Vec { - use mitki_hir::ty::TyKind; - use mitki_lower::hir::HasFunction as _; - use mitki_typeck::infer; - use mitki_typeck::infer::Inferable as _; - use mitki_yellow::ast::Node as _; - - let source = func.source(db); - let source_map = func.hir_function(db).source_map(db); - let function = func.hir_function(db).function(db); - let nodes = function.node_store(); - let fallback_range = mitki_yellow::SyntaxNodePtr::new(source.syntax()).range; - let mut diagnostics = wasm_boundary_legality::check_function_boundary_legality(db, func); - - let node_range = - |node_id| source_map.try_node_syntax(node_id).map_or(fallback_range, |ptr| ptr.range); - let pat_range = - |pat_id| source_map.try_pat_syntax(pat_id).map_or(fallback_range, |ptr| ptr.range); - let type_range = - |ty_id| source_map.try_type_syntax(ty_id).map_or(fallback_range, |ptr| ptr.range); - - let context_label = |node_id| match nodes.node_kind(node_id) { - NodeKind::Call => "call expression", - NodeKind::Closure => "closure", - NodeKind::If => "if expression", - NodeKind::Match => "match expression", - NodeKind::LoopExpr => "loop expression", - NodeKind::Tuple => "tuple expression", - NodeKind::StructExpr => "struct expression", - _ => "expression", - }; - - let inference = func.infer(db); - - for diagnostic in inference.diagnostics() { - let (message, range) = salsa::plumbing::attach(db, || match diagnostic.kind() { - infer::DiagnosticKind::UnresolvedIdent(node_id) => { - ("Unresolved identifier".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::UnresolvedType(ty_id, name) => { - (format!("Unknown type `{}`", name.text(db)), type_range(*ty_id)) - } - infer::DiagnosticKind::TypeMismatch(node_id, actual, expected) => ( - format!( - "expected `{expected}`, found `{actual}`", - expected = expected.display(db), - actual = actual.display(db) - ), - node_range(*node_id), - ), - infer::DiagnosticKind::PatternTypeMismatch(pattern_id, actual, expected) => ( - format!( - "expected `{expected}`, found `{actual}`", - expected = expected.display(db), - actual = actual.display(db) - ), - pat_range(*pattern_id), - ), - infer::DiagnosticKind::UnknownType(node_id) => { - ("cannot infer type".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::ExpectedValueFoundType(node_id, ty) => { - (format!("expected value, found type `{}`", ty.display(db)), node_range(*node_id)) - } - infer::DiagnosticKind::CallArityMismatch(node_id, expected, actual) => { - (format!("expected {expected} argument(s), found {actual}"), node_range(*node_id)) - } - infer::DiagnosticKind::CallNonFunction(node_id, ty) => { - (format!("expected function, found `{}`", ty.display(db)), node_range(*node_id)) - } - infer::DiagnosticKind::ClosureArityMismatch(node_id, actual, expected) => { - (format!("expected {expected} parameter(s), found {actual}"), node_range(*node_id)) - } - infer::DiagnosticKind::InvalidBinaryOp(node_id, op, lhs_ty, rhs_ty) => ( - format!( - "cannot apply `{}` to `{}` and `{}`", - op.text(db), - lhs_ty.display(db), - rhs_ty.display(db) - ), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidPrefixOp(node_id, op, ty) => ( - format!("cannot apply `{}` to `{}`", op.text(db), ty.display(db)), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidPostfixOp(node_id, op, ty) => ( - format!("cannot apply postfix `{}` to `{}`", op.text(db), ty.display(db)), - node_range(*node_id), - ), - infer::DiagnosticKind::MissingElseBranch(node_id) => { - ("missing `else` branch".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::MissingParameterType(node_id) => { - let message = inference.type_of_node(*node_id).map_or_else( - || "Parameter type annotation is required.".to_owned(), - |inferred| { - let inferred_kind = inferred.kind(db); - let is_unhelpful_hint = matches!(inferred_kind, TyKind::Unknown) - || matches!(inferred_kind, TyKind::Tuple(items) if items.is_empty()); - - if is_unhelpful_hint { - "Parameter type annotation is required.".to_owned() - } else { - format!( - "Parameter type annotation is required. Inferred `{}`.", - inferred.display(db) - ) - } - }, - ); - (message, node_range(*node_id)) - } - infer::DiagnosticKind::MissingInitializer(node_id) => { - ("missing initializer".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::TupleArityMismatch(node_id, expected, actual) => { - (format!("expected {expected} element(s), found {actual}"), node_range(*node_id)) - } - infer::DiagnosticKind::MissingStructField(node_id, name) => { - (format!("missing field `{}`", name.text(db)), node_range(*node_id)) - } - infer::DiagnosticKind::UnknownStructField(node_id, name) => { - (format!("unknown field `{}`", name.text(db)), node_range(*node_id)) - } - infer::DiagnosticKind::NotAStruct(node_id, ty) => { - (format!("`{}` is not a struct", ty.display(db)), node_range(*node_id)) - } - infer::DiagnosticKind::NotAnEnum(node_id, ty) => { - (format!("`{}` is not an enum", ty.display(db)), node_range(*node_id)) - } - infer::DiagnosticKind::DuplicatePatternBinding(node_id) => { - ("duplicate binding in pattern".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::AmbiguousUnionPattern(pattern_id, union_ty) => ( - format!("pattern matches multiple members of union `{}`", union_ty.display(db)), - pat_range(*pattern_id), - ), - infer::DiagnosticKind::RefutablePattern(node_id) => ( - "refutable patterns are only allowed in `match` arms".to_owned(), - node_range(*node_id), - ), - infer::DiagnosticKind::UnsupportedFloatPattern(node_id) => { - ("float patterns are not supported".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::BreakOutsideLoop(node_id) => { - ("`break` is only allowed inside `loop`".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::ContinueOutsideLoop(node_id) => { - ("`continue` is only allowed inside `loop`".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::CompilerIntrinsicMustBeCalled(node_id, intrinsic) => ( - format!("`{}` can only be used in call position", intrinsic.source_name()), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidComptimeCall(node_id) => ( - "comptime requires a direct call to a top-level `comptime fun`".to_owned(), - node_range(*node_id), - ), - infer::DiagnosticKind::ComptimeTargetMustBeZeroArg(node_id) => ( - "comptime requires the target function to have no parameters".to_owned(), - node_range(*node_id), - ), - infer::DiagnosticKind::ComptimeTargetMustNotBeGeneric(node_id) => { - ("comptime does not support generic functions".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::ComptimeTargetMustReturnSupportedType(node_id, ty) => ( - format!( - "comptime requires the target function to return a runtime-lowerable value, \ - found `{}`", - ty.display(db) - ), - node_range(*node_id), - ), - infer::DiagnosticKind::ReflectionOnlyInComptime(node_id, intrinsic) => ( - format!("`{}` is only allowed inside `comptime fun`", intrinsic.source_name()), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidReflectionTarget(node_id, intrinsic, expected) => ( - format!("`{}` requires {}", intrinsic.source_name(), expected), - node_range(*node_id), - ), - infer::DiagnosticKind::UnsafeOperationRequiresUnsafeContext(node_id) => ( - "unsafe operation requires an `unsafe` block or `unsafe fun`".to_owned(), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidUnsafeIntrinsicArgument(node_id, intrinsic, expected) => { - ( - format!("`{}` requires {}", intrinsic.source_name(), expected), - node_range(*node_id), - ) - } - infer::DiagnosticKind::InvalidUnsafeIntrinsicResult(node_id, intrinsic, expected) => ( - format!("`{}` requires {}", intrinsic.source_name(), expected), - node_range(*node_id), - ), - infer::DiagnosticKind::InvalidAssignmentTarget(node_id) => { - ("invalid assignment target".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::AssignmentRequiresMutable(node_id) => { - ("assignment requires a mutable binding".to_owned(), node_range(*node_id)) - } - infer::DiagnosticKind::MutableArgumentRequiresPlace(node_id) => { - ("mutable parameter requires a mutable place".to_owned(), node_range(*node_id)) - } - }); - - let message = if let Some(context) = diagnostic.context() { - format!("In {}: {message}", context_label(context)) - } else { - message - }; - - diagnostics.push(Diagnostic::error(message, range)) - } - - diagnostics.extend(match_analysis::check_function_matches(db, func)); - - diagnostics -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn check_runtime_file(db: &dyn salsa::Database, file: mitki_inputs::File) -> Vec { - let roots = file - .item_decls(db) - .declarations() - .iter() - .filter_map(|declaration| match declaration { - Declaration::Function(function) => { - let linkage = function.hir_function(db).function(db).linkage(); - matches!(linkage, WasmLinkage::ImplicitMainExport | WasmLinkage::Export) - .then_some(*function) - } - Declaration::BoundaryInstance(instance) => (instance.kind(db) - == BoundaryInstanceKind::Export) - .then(|| instance.origin(db)) - .flatten(), - Declaration::Struct(_) | Declaration::Enum(_) => None, - }) - .collect::>(); - - runtime_reachability_diagnostics(db, roots) -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn check_runtime_function( - db: &dyn salsa::Database, - root: FunctionLocation<'_>, -) -> Vec { - runtime_reachability_diagnostics(db, [root]) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum RuntimeWorkItem<'db> { - Function(FunctionLocation<'db>), - Closure { owner: FunctionLocation<'db>, expr: ExprId }, -} - -fn runtime_reachability_diagnostics<'db>( - db: &'db dyn salsa::Database, - roots: impl IntoIterator>, -) -> Vec { - let boundary_roots = roots.into_iter().collect::>(); - let mut checker = RuntimeReachabilityChecker { - db, - diagnostics: Vec::new(), - pending: boundary_roots.iter().copied().map(RuntimeWorkItem::Function).collect(), - boundary_roots: boundary_roots.iter().copied().collect(), - seen: FxHashSet::default(), - }; - - while let Some(item) = checker.pending.pop_front() { - if !checker.seen.insert(item) { - continue; - } - checker.visit(item); - } - - checker.diagnostics -} - -struct RuntimeReachabilityChecker<'db> { - db: &'db dyn salsa::Database, - diagnostics: Vec, - pending: VecDeque>, - boundary_roots: FxHashSet>, - seen: FxHashSet>, -} - -impl<'db> RuntimeReachabilityChecker<'db> { - fn visit(&mut self, item: RuntimeWorkItem<'db>) { - match item { - RuntimeWorkItem::Function(location) => { - let hir_function = location.hir_function(self.db); - let function = hir_function.function(self.db); - let source_map = hir_function.source_map(self.db); - let inference = location.infer(self.db); - self.validate_function_signature(location, function, source_map, inference); - if function.body() != ExprId::ZERO { - let resolver = Resolver::new(self.db, location); - let mut visitor = RuntimeBodyVisitor { - checker: self, - location, - function, - source_map, - inference, - resolver, - }; - visitor.expr(function.body()); - } - } - RuntimeWorkItem::Closure { owner, expr } => { - let hir_function = owner.hir_function(self.db); - let function = hir_function.function(self.db); - let source_map = hir_function.source_map(self.db); - let inference = owner.infer(self.db); - self.validate_closure_signature(owner, expr, function, source_map, inference); - let nodes = function.node_store(); - let Some(closure) = nodes.as_closure(expr) else { - return; - }; - let (_params, body) = nodes.closure_parts(closure); - if body != ExprId::ZERO { - let resolver = Resolver::new(self.db, owner); - let mut visitor = RuntimeBodyVisitor { - checker: self, - location: owner, - function, - source_map, - inference, - resolver, - }; - visitor.expr(body); - } - } - } - } - - fn validate_function_signature( - &mut self, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) { - let _crosses_wasm_boundary = !matches!(function.linkage(), WasmLinkage::Internal) - || self.boundary_roots.contains(&location); - let nodes = function.node_store(); - for ¶m in function.params() { - let (pattern, _) = nodes.param(param); - for name in nodes.pattern_binding_names(pattern) { - let name_id = name.into(); - if let Some(ty) = inference.type_of_node(name_id) { - let range = source_map - .try_node_syntax(name_id) - .map_or_else(|| function_range(self.db, location), |ptr| ptr.range); - self.validate_ty(ty, range); - } - } - } - - let return_ty = inference - .type_of_node(function.body()) - .unwrap_or_else(|| Ty::new(self.db, TyKind::Tuple(Vec::new()))); - let return_range = if function.ret_type().is_zero() { - function_range(self.db, location) - } else { - source_map - .try_type_syntax(function.ret_type()) - .map_or_else(|| function_range(self.db, location), |ptr| ptr.range) - }; - self.validate_ty(return_ty, return_range); - } - - fn validate_closure_signature( - &mut self, - location: FunctionLocation<'db>, - expr: ExprId, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) { - let nodes = function.node_store(); - let Some(closure) = nodes.as_closure(expr) else { - return; - }; - let (params, _body) = nodes.closure_parts(closure); - for param in params.iter() { - let (pattern, _) = nodes.param(param); - for name in nodes.pattern_binding_names(pattern) { - let name_id = name.into(); - if let Some(ty) = inference.type_of_node(name_id) { - self.validate_ty( - ty, - source_map - .try_node_syntax(name_id) - .map_or_else(|| function_range(self.db, location), |ptr| ptr.range), - ); - } - } - } - - if let Some(TyKind::Function { output, .. }) = - inference.type_of_node(expr).map(|ty| ty.kind(self.db)) - { - self.validate_ty( - *output, - source_map - .try_node_syntax(expr) - .map_or_else(|| function_range(self.db, location), |ptr| ptr.range), - ); - } - } - - #[allow(clippy::unused_self)] - fn validate_ty(&mut self, ty: Ty<'db>, range: mitki_errors::TextRange) { - let _ = (ty, range); - } -} - -struct RuntimeBodyVisitor<'a, 'db> { - checker: &'a mut RuntimeReachabilityChecker<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - resolver: Resolver<'db>, -} - -impl<'a, 'db> RuntimeBodyVisitor<'a, 'db> { - fn expr(&mut self, expr: ExprId) { - let nodes = self.function.node_store(); - let kind = nodes.node_kind(expr); - self.validate_expr_type(expr, kind); - - match kind { - NodeKind::Name => self.name(expr), - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.expr(item); - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block mismatch")); - for stmt in stmts.iter() { - self.stmt(stmt); - } - if tail != ExprId::ZERO { - self.expr(tail); - } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - if let Some(function) = self.resolve_method_call(callee) { - self.checker.pending.push_back(RuntimeWorkItem::Function(function)); - } else if matches!(nodes.node_kind(callee), NodeKind::Name) - && let Some(resolution) = self.resolve_name(callee) - { - match resolution { - BindingId::Function(function) => { - self.checker.pending.push_back(RuntimeWorkItem::Function(function)); - } - BindingId::RuntimeFunction(_) - | BindingId::CompilerIntrinsic(_) - | BindingId::Struct(_) - | BindingId::Enum(_) - | BindingId::BuiltinType(_) - | BindingId::EnumVariant(_) => {} - BindingId::Local(_) | BindingId::Param(_) => self.expr(callee), - } - } else if callee != ExprId::ZERO { - self.expr(callee); - } - - if let Some(field_id) = nodes.as_field(callee) { - let (receiver, _) = nodes.field(field_id); - if receiver != ExprId::ZERO && self.resolve_method_call(callee).is_some() { - self.expr(receiver); - } - } - for arg in args.iter() { - self.expr(arg); - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.expr(binary.lhs); - self.expr(binary.rhs); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.expr(prefix.expr); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.expr(if_expr.cond); - if if_expr.then_branch != ExprId::ZERO { - self.expr(if_expr.then_branch); - } - if if_expr.else_branch != ExprId::ZERO { - self.expr(if_expr.else_branch); - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.expr(scrutinee); - for arm in arms.iter() { - let (pattern, arm_expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - self.validate_pattern_bindings(pattern); - self.expr(arm_expr); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::LocalVar => { - let var = nodes.local_var(nodes.as_local_var(expr).expect("LocalVar mismatch")); - self.validate_pattern_bindings(var.pattern); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(expr).expect("Array node mismatch")); - for item in array.iter() { - self.expr(item); - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - self.expr(value); - self.expr(len); - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.expr(base); - } - } - NodeKind::Closure => { - self.checker - .pending - .push_back(RuntimeWorkItem::Closure { owner: self.location, expr }); - } - NodeKind::StructExpr => { - let items = nodes.struct_expr(nodes.as_struct_expr(expr).expect("Struct mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.expr(items.get(index).unwrap()); - index += 2; - } - } - NodeKind::True - | NodeKind::False - | NodeKind::Int - | NodeKind::Float - | NodeKind::String - | NodeKind::Char - | NodeKind::BreakExpr - | NodeKind::ContinueExpr - | NodeKind::Postfix - | NodeKind::Error => {} - _ => {} - } - } - - fn stmt(&mut self, stmt: StmtId) { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::LocalVar => { - let var = nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar mismatch")); - self.validate_pattern_bindings(var.pattern); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.expr(value); - } - } - _ => { - if let Some(expr) = stmt_as_expr(nodes, stmt) { - self.expr(expr); - } - } - } - } - - fn name(&mut self, expr: ExprId) { - match self.resolve_name(expr) { - Some(BindingId::Function(function)) => { - self.checker.pending.push_back(RuntimeWorkItem::Function(function)); - } - Some(BindingId::RuntimeFunction(function)) => { - self.checker.diagnostics.push(Diagnostic::error( - format!( - "runtime function `{}` can only be used in call position", - function.source_name() - ), - self.node_range(expr), - )); - } - _ => {} - } - } - - fn validate_expr_type(&mut self, expr: ExprId, kind: NodeKind) { - if matches!( - kind, - NodeKind::Name - | NodeKind::True - | NodeKind::False - | NodeKind::Int - | NodeKind::Float - | NodeKind::String - | NodeKind::Char - | NodeKind::BreakExpr - | NodeKind::ContinueExpr - | NodeKind::Error - ) { - return; - } - - let Some(ty) = self.inference.type_of_node(expr) else { - return; - }; - self.checker.validate_ty(ty, self.node_range(expr)); - } - - fn validate_local_binding(&mut self, node: ExprId) { - let Some(ty) = self.inference.type_of_node(node) else { - return; - }; - self.checker.validate_ty(ty, self.node_range(node)); - } - - fn validate_pattern_bindings(&mut self, pattern: PatId) { - for name in self.function.node_store().pattern_binding_names(pattern) { - self.validate_local_binding(name.into()); - } - } - - fn resolve_name(&mut self, expr: ExprId) -> Option> { - let nodes = self.function.node_store(); - let name = nodes.as_name(expr)?; - let symbol = nodes.name(name); - let guard = self.resolver.scopes_for_node(expr); - let resolution = self.resolver.resolve_value_binding(symbol); - self.resolver.reset(guard); - resolution - } - - fn resolve_method_call(&self, callee: ExprId) -> Option> { - let nodes = self.function.node_store(); - let field = nodes.as_field(callee)?; - let (receiver, field_name_expr) = nodes.field(field); - if receiver == ExprId::ZERO { - return None; - } - - let field_name = nodes.as_name(field_name_expr)?; - let receiver_ty = self.inference.type_of_node(receiver)?; - resolve_method_for_receiver(self.checker.db, receiver_ty, nodes.name(field_name)) - .map(|method| method.function) - } - - fn node_range(&self, expr: ExprId) -> mitki_errors::TextRange { - self.source_map - .try_node_syntax(expr) - .map_or_else(|| function_range(self.checker.db, self.location), |ptr| ptr.range) - } -} - -fn function_range( - db: &dyn salsa::Database, - location: FunctionLocation<'_>, -) -> mitki_errors::TextRange { - mitki_yellow::SyntaxNodePtr::new(location.source(db).syntax()).range -} - -fn stmt_as_expr(nodes: &NodeStore<'_>, stmt: StmtId) -> Option { - match nodes.node_kind(stmt) { - NodeKind::Name => nodes.as_name(stmt).map(Into::into), - NodeKind::True => nodes.as_true(stmt).map(Into::into), - NodeKind::False => nodes.as_false(stmt).map(Into::into), - NodeKind::Error => nodes.as_error(stmt).map(Into::into), - NodeKind::Int => nodes.as_int(stmt).map(Into::into), - NodeKind::Float => nodes.as_float(stmt).map(Into::into), - NodeKind::String => nodes.as_string(stmt).map(Into::into), - NodeKind::Char => nodes.as_char(stmt).map(Into::into), - NodeKind::Tuple => nodes.as_tuple(stmt).map(Into::into), - NodeKind::Array => nodes.as_array(stmt).map(Into::into), - NodeKind::ArrayRepeat => nodes.as_array_repeat(stmt).map(Into::into), - NodeKind::Call => nodes.as_call(stmt).map(Into::into), - NodeKind::Field => nodes.as_field(stmt).map(Into::into), - NodeKind::Binary => nodes.as_binary(stmt).map(Into::into), - NodeKind::Postfix => nodes.as_postfix(stmt).map(Into::into), - NodeKind::Prefix => nodes.as_prefix(stmt).map(Into::into), - NodeKind::LoopExpr => nodes.as_loop_expr(stmt).map(Into::into), - NodeKind::BreakExpr => nodes.as_break_expr(stmt).map(Into::into), - NodeKind::ContinueExpr => nodes.as_continue_expr(stmt).map(Into::into), - NodeKind::If => nodes.as_if(stmt).map(Into::into), - NodeKind::Match => nodes.as_match(stmt).map(Into::into), - NodeKind::Closure => nodes.as_closure(stmt).map(Into::into), - NodeKind::Block => nodes.as_block(stmt).map(Into::into), - NodeKind::UnsafeBlock => nodes.as_unsafe_block(stmt).map(Into::into), - NodeKind::StructExpr => nodes.as_struct_expr(stmt).map(Into::into), - _ => None, - } -} diff --git a/crates/mitki-analysis/src/match_analysis.rs b/crates/mitki-analysis/src/match_analysis.rs deleted file mode 100644 index 4f070f7..0000000 --- a/crates/mitki-analysis/src/match_analysis.rs +++ /dev/null @@ -1,887 +0,0 @@ -use std::fmt::Write as _; - -use mitki_errors::{Diagnostic, TextRange}; -use mitki_hir::hir::{ExprId, Function, NodeKind, NodeStore, PatId, StmtId}; -use mitki_hir::ty::{StructTy, Ty, TyKind}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{FunctionLocation, enum_variants, struct_fields}; -use mitki_span::Symbol; -use mitki_typeck::infer::{Inferable as _, Inference}; -use patmat::{ - MatchArm, MatchInput, ReachabilityWarning, Space, SpaceContext, SpaceKind, check_match, -}; -use salsa::plumbing::AsId as _; - -pub(super) fn check_function_matches( - db: &dyn salsa::Database, - func: FunctionLocation<'_>, -) -> Vec { - let hir_function = func.hir_function(db); - let function = hir_function.function(db); - if function.body() == ExprId::ZERO { - return Vec::new(); - } - - let mut visitor = MatchVisitor { - db, - function, - source_map: hir_function.source_map(db), - inference: func.infer(db), - diagnostics: Vec::new(), - }; - visitor.expr(function.body()); - visitor.diagnostics -} - -struct MatchVisitor<'db> { - db: &'db dyn salsa::Database, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db Inference<'db>, - diagnostics: Vec, -} - -impl<'db> MatchVisitor<'db> { - fn expr(&mut self, expr: ExprId) { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Tuple => { - for item in nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")).iter() { - self.expr(item); - } - } - NodeKind::Array => { - for item in nodes.array(nodes.as_array(expr).expect("Array node mismatch")).iter() { - self.expr(item); - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - self.expr(value); - self.expr(len); - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block mismatch")); - for stmt in stmts.iter() { - self.stmt(stmt); - } - if tail != ExprId::ZERO { - self.expr(tail); - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - if callee != ExprId::ZERO { - self.expr(callee); - } - for arg in args.iter() { - self.expr(arg); - } - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.expr(base); - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.expr(binary.lhs); - self.expr(binary.rhs); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.expr(prefix.expr); - } - NodeKind::Postfix => { - let postfix = nodes.postfix(nodes.as_postfix(expr).expect("Postfix mismatch")); - self.expr(postfix.expr); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.expr(if_expr.cond); - if if_expr.then_branch != ExprId::ZERO { - self.expr(if_expr.then_branch); - } - if if_expr.else_branch != ExprId::ZERO { - self.expr(if_expr.else_branch); - } - } - NodeKind::Match => { - self.analyze_match(expr); - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.expr(scrutinee); - for arm in arms.iter() { - let (_, arm_expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - self.expr(arm_expr); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::LocalVar => { - let var = nodes.local_var(nodes.as_local_var(expr).expect("LocalVar mismatch")); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::Closure => { - let (_, body) = - nodes.closure_parts(nodes.as_closure(expr).expect("Closure mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::StructExpr => { - let items = nodes.struct_expr(nodes.as_struct_expr(expr).expect("Struct mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.expr(items.get(index).expect("struct field expr")); - index += 2; - } - } - _ => {} - } - } - - fn stmt(&mut self, stmt: StmtId) { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::LocalVar => { - let var = nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar mismatch")); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.expr(value); - } - } - _ => {} - } - } - - fn analyze_match(&mut self, match_expr: ExprId) { - let nodes = self.function.node_store(); - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(match_expr).expect("Match node mismatch")); - let Some(scrutinee_ty) = self.inference.type_of_node(scrutinee) else { - return; - }; - - let mut arm_patterns = Vec::with_capacity(arms.len()); - for arm in arms.iter() { - let (pattern, _) = nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - arm_patterns.push(pattern); - } - - let ops = MatchOperations { - db: self.db, - int_literals: collect_int_literals(self.db, nodes, &arm_patterns), - }; - let mut context = SpaceContext::new(); - let scrutinee_space = context.of_type(SpaceTy::Base(scrutinee_ty)); - let lowerer = - PatternLowerer { db: self.db, function: self.function, inference: self.inference }; - - let mut match_arms = Vec::with_capacity(arm_patterns.len()); - for &pattern in &arm_patterns { - let Some(pattern_space) = - lowerer.lower_pattern_space(&mut context, pattern, scrutinee_ty) - else { - return; - }; - let arm = if is_wildcard_pattern(nodes, pattern) { - MatchArm::wildcard(pattern_space) - } else { - MatchArm::new(pattern_space) - }; - match_arms.push(arm); - } - - let analysis = - check_match(&ops, &mut context, &MatchInput::new(scrutinee_space, match_arms)); - if let Some(example) = analysis.uncovered_spaces.first().copied() { - self.diagnostics.push(Diagnostic::error( - format!( - "non-exhaustive match; missing case for `{}`", - render_space(self.db, &context, example) - ), - self.node_range(match_expr), - )); - } - - for warning in analysis.reachability_warnings { - let ReachabilityWarning::Unreachable { arm_index, .. } = warning else { - continue; - }; - let Some(&pattern) = arm_patterns.get(arm_index) else { - continue; - }; - self.diagnostics - .push(Diagnostic::error("unreachable match arm", self.pat_range(pattern))); - } - } - - fn node_range(&self, expr: ExprId) -> TextRange { - self.source_map.try_node_syntax(expr).map_or_else(|| self.body_range(), |ptr| ptr.range) - } - - fn pat_range(&self, pattern: PatId) -> TextRange { - self.source_map.try_pat_syntax(pattern).map_or_else(|| self.body_range(), |ptr| ptr.range) - } - - fn body_range(&self) -> TextRange { - self.source_map - .try_node_syntax(self.function.body()) - .expect("function body should have syntax") - .range - } -} - -fn is_wildcard_pattern(nodes: &NodeStore<'_>, pattern: PatId) -> bool { - match nodes.node_kind(pattern) { - NodeKind::PatWildcard => true, - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - inner != PatId::ZERO && is_wildcard_pattern(nodes, inner) - } - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - inner != PatId::ZERO && is_wildcard_pattern(nodes, inner) - } - _ => false, - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum SpaceTy<'db> { - Base(Ty<'db>), - Bool(bool), - IntLiteral(i32), - IntRest(Vec), - UnionMember { parent: Ty<'db>, index: usize, member: Ty<'db> }, - EnumVariant { parent: Ty<'db>, variant: Symbol<'db> }, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum SpaceExtractor<'db> { - Tuple(usize), - StructFields { nominal: Option>, fields: Vec> }, - EnumVariant(Symbol<'db>), - UnionMember { parent: Ty<'db>, index: usize, member: Ty<'db> }, -} - -fn collect_int_literals<'db>( - db: &dyn salsa::Database, - nodes: &NodeStore<'db>, - patterns: &[PatId], -) -> Vec { - let mut values = Vec::new(); - for &pattern in patterns { - collect_pattern_int_literals(db, nodes, pattern, &mut values); - } - values.sort_unstable(); - values.dedup(); - values -} - -fn collect_pattern_int_literals<'db>( - db: &dyn salsa::Database, - nodes: &NodeStore<'db>, - pattern: PatId, - values: &mut Vec, -) { - if pattern == PatId::ZERO { - return; - } - - match nodes.node_kind(pattern) { - NodeKind::PatInt => { - if let Ok(value) = - parse_int_literal(nodes.pat_int(nodes.as_pat_int(pattern).expect("PatInt")), db) - { - values.push(value); - } - } - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - collect_pattern_int_literals(db, nodes, inner, values); - } - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - collect_pattern_int_literals(db, nodes, inner, values); - } - NodeKind::PatTuple => { - for item in nodes.pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")).iter() { - collect_pattern_int_literals(db, nodes, item, values); - } - } - NodeKind::PatVariant => { - let (_, args) = nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - for arg in args.iter() { - collect_pattern_int_literals(db, nodes, arg, values); - } - } - NodeKind::PatStruct => { - let (_, fields) = nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - for field in fields.iter() { - let (_, nested) = nodes - .pat_struct_field(nodes.as_pat_struct_field(field).expect("PatStructField")); - collect_pattern_int_literals(db, nodes, nested, values); - } - } - _ => {} - } -} - -struct PatternLowerer<'db> { - db: &'db dyn salsa::Database, - function: &'db Function<'db>, - inference: &'db Inference<'db>, -} - -impl<'db> PatternLowerer<'db> { - fn lower_pattern_space( - &self, - context: &mut SpaceContext, SpaceExtractor<'db>>, - pattern: PatId, - expected_ty: Ty<'db>, - ) -> Option, SpaceExtractor<'db>>> { - if let Some(space) = self.lower_selected_union_space(context, pattern, expected_ty) { - return Some(space); - } - self.lower_pattern_space_inner(context, pattern, expected_ty) - } - - fn lower_selected_union_space( - &self, - context: &mut SpaceContext, SpaceExtractor<'db>>, - pattern: PatId, - expected_ty: Ty<'db>, - ) -> Option, SpaceExtractor<'db>>> { - let TyKind::Union(members) = expected_ty.kind(self.db) else { - return None; - }; - let selected_member = self.inference.selected_union_member(pattern)?; - let arm_index = members.iter().position(|member| *member == selected_member)?; - let inner = self.lower_pattern_space_inner(context, pattern, selected_member)?; - Some(context.product( - SpaceTy::Base(expected_ty), - SpaceExtractor::UnionMember { - parent: expected_ty, - index: arm_index, - member: selected_member, - }, - vec![inner], - )) - } - - fn lower_pattern_space_inner( - &self, - context: &mut SpaceContext, SpaceExtractor<'db>>, - pattern: PatId, - expected_ty: Ty<'db>, - ) -> Option, SpaceExtractor<'db>>> { - if pattern == PatId::ZERO { - return Some(context.of_type(SpaceTy::Base(expected_ty))); - } - - let nodes = self.function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatBinding | NodeKind::PatWildcard => { - Some(context.of_type(SpaceTy::Base(expected_ty))) - } - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - if self.inference.matched_typed_pattern(pattern) != Some(expected_ty) { - return None; - } - self.lower_pattern_space(context, inner, expected_ty) - } - NodeKind::PatTrue if matches!(expected_ty.kind(self.db), TyKind::Bool) => { - Some(context.atomic_type(SpaceTy::Bool(true))) - } - NodeKind::PatFalse if matches!(expected_ty.kind(self.db), TyKind::Bool) => { - Some(context.atomic_type(SpaceTy::Bool(false))) - } - NodeKind::PatTrue | NodeKind::PatFalse => None, - NodeKind::PatInt => Some(context.atomic_type(SpaceTy::IntLiteral( - if matches!(expected_ty.kind(self.db), TyKind::Int) { - parse_int_literal( - nodes.pat_int(nodes.as_pat_int(pattern).expect("PatInt")), - self.db, - ) - .ok()? - } else { - return None; - }, - ))), - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.lower_pattern_space(context, inner, expected_ty) - } - NodeKind::PatTuple => { - let TyKind::Tuple(item_tys) = expected_ty.kind(self.db) else { - return None; - }; - let items = nodes - .pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")) - .iter() - .collect::>(); - if items.len() != item_tys.len() { - return None; - } - let parameters = items - .iter() - .zip(item_tys.iter()) - .map(|(&item, item_ty)| self.lower_pattern_space(context, item, *item_ty)) - .collect::>>()?; - Some(context.product( - SpaceTy::Base(expected_ty), - SpaceExtractor::Tuple(parameters.len()), - parameters, - )) - } - NodeKind::PatStruct => self.lower_struct_pattern_space(context, pattern, expected_ty), - NodeKind::PatVariant => self.lower_variant_pattern_space(context, pattern, expected_ty), - _ => None, - } - } - - fn lower_struct_pattern_space( - &self, - context: &mut SpaceContext, SpaceExtractor<'db>>, - pattern: PatId, - expected_ty: Ty<'db>, - ) -> Option, SpaceExtractor<'db>>> { - let nominal = match expected_ty.kind(self.db) { - TyKind::Struct(struct_ty) => Some(*struct_ty), - TyKind::Record(_) => None, - _ => return None, - }; - let nodes = self.function.node_store(); - let (_, fields) = nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - let mut lowered_fields = fields - .iter() - .map(|field| { - let (name, nested) = nodes - .pat_struct_field(nodes.as_pat_struct_field(field).expect("PatStructField")); - let field_name = nodes.name(name); - let field_ty = struct_or_record_field_ty(self.db, expected_ty, field_name)?; - let parameter = if nested != PatId::ZERO { - self.lower_pattern_space(context, nested, field_ty)? - } else { - context.of_type(SpaceTy::Base(field_ty)) - }; - Some((field_name, parameter)) - }) - .collect::>>()?; - lowered_fields.sort_by_key(|(name, _)| name.as_id().as_bits()); - let field_names = lowered_fields.iter().map(|(name, _)| *name).collect::>(); - let parameters = lowered_fields.into_iter().map(|(_, parameter)| parameter).collect(); - Some(context.product( - SpaceTy::Base(expected_ty), - SpaceExtractor::StructFields { nominal, fields: field_names }, - parameters, - )) - } - - fn lower_variant_pattern_space( - &self, - context: &mut SpaceContext, SpaceExtractor<'db>>, - pattern: PatId, - expected_ty: Ty<'db>, - ) -> Option, SpaceExtractor<'db>>> { - let TyKind::Enum(enum_ty) = expected_ty.kind(self.db) else { - return None; - }; - let nodes = self.function.node_store(); - let (path, args) = nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - let field_id = nodes.as_field(path)?; - let (_, variant_name_expr) = nodes.field(field_id); - let variant_name = nodes.as_name(variant_name_expr)?; - let variant_sym = nodes.name(variant_name); - let payload_tys = enum_variants(self.db, *enum_ty) - .iter() - .find(|(name, _)| *name == variant_sym) - .map(|(_, payload)| payload.clone())?; - let args = args.iter().collect::>(); - if args.len() != payload_tys.len() { - return None; - } - let parameters = args - .iter() - .zip(payload_tys.iter()) - .map(|(&arg, payload_ty)| self.lower_pattern_space(context, arg, *payload_ty)) - .collect::>>()?; - Some(context.product( - SpaceTy::Base(expected_ty), - SpaceExtractor::EnumVariant(variant_sym), - parameters, - )) - } -} - -struct MatchOperations<'db> { - db: &'db dyn salsa::Database, - int_literals: Vec, -} - -impl<'db> patmat::SpaceOperations for MatchOperations<'db> { - type Type = SpaceTy<'db>; - type Extractor = SpaceExtractor<'db>; - - fn decompose_type(&self, value_type: &Self::Type) -> patmat::Decomposition { - match value_type { - SpaceTy::Base(ty) => match ty.kind(self.db) { - TyKind::Bool => { - patmat::Decomposition::parts(vec![SpaceTy::Bool(false), SpaceTy::Bool(true)]) - } - TyKind::Int => { - if self.int_literals.is_empty() { - patmat::Decomposition::NotDecomposable - } else { - let mut parts = self - .int_literals - .iter() - .copied() - .map(SpaceTy::IntLiteral) - .collect::>(); - parts.push(SpaceTy::IntRest(self.int_literals.clone())); - patmat::Decomposition::Parts(parts) - } - } - TyKind::Union(members) => patmat::Decomposition::parts( - members - .iter() - .enumerate() - .map(|(index, member)| SpaceTy::UnionMember { - parent: *ty, - index, - member: *member, - }) - .collect(), - ), - TyKind::Enum(enum_ty) => patmat::Decomposition::parts( - enum_variants(self.db, *enum_ty) - .iter() - .map(|(variant, _)| SpaceTy::EnumVariant { parent: *ty, variant: *variant }) - .collect(), - ), - _ => patmat::Decomposition::NotDecomposable, - }, - _ => patmat::Decomposition::NotDecomposable, - } - } - - fn is_subtype(&self, left: &Self::Type, right: &Self::Type) -> bool { - if left == right { - return true; - } - - match (left, right) { - (SpaceTy::Bool(_), SpaceTy::Base(ty)) => matches!(ty.kind(self.db), TyKind::Bool), - (SpaceTy::IntLiteral(_) | SpaceTy::IntRest(_), SpaceTy::Base(ty)) => { - matches!(ty.kind(self.db), TyKind::Int) - } - (SpaceTy::UnionMember { parent, .. }, SpaceTy::Base(ty)) => *parent == *ty, - (SpaceTy::EnumVariant { parent, .. }, SpaceTy::Base(ty)) => *parent == *ty, - (SpaceTy::Base(left_ty), SpaceTy::Base(right_ty)) => { - semantic_is_subtype(self.db, *left_ty, *right_ty) - } - _ => false, - } - } - - fn extractors_are_equivalent(&self, left: &Self::Extractor, right: &Self::Extractor) -> bool { - left == right - } - - fn extractor_parameter_types( - &self, - extractor: &Self::Extractor, - scrutinee_type: &Self::Type, - arity: usize, - ) -> Vec { - let parameters = match extractor { - SpaceExtractor::Tuple(expected_arity) => match scrutinee_type { - SpaceTy::Base(ty) => match ty.kind(self.db) { - TyKind::Tuple(items) if items.len() == *expected_arity => { - items.iter().copied().map(SpaceTy::Base).collect() - } - _ => Vec::new(), - }, - _ => Vec::new(), - }, - SpaceExtractor::StructFields { fields, .. } => { - struct_field_types(self.db, scrutinee_type, fields) - .into_iter() - .map(SpaceTy::Base) - .collect() - } - SpaceExtractor::EnumVariant(variant) => match scrutinee_type { - SpaceTy::EnumVariant { parent, variant: actual } if actual == variant => { - enum_variant_payload_types(self.db, *parent, *variant) - .into_iter() - .map(SpaceTy::Base) - .collect() - } - _ => Vec::new(), - }, - SpaceExtractor::UnionMember { member, .. } => match scrutinee_type { - SpaceTy::UnionMember { member: actual, .. } if actual == member => { - vec![SpaceTy::Base(*member)] - } - _ => Vec::new(), - }, - }; - debug_assert_eq!(parameters.len(), arity); - parameters - } - - fn extractor_covers_type( - &self, - extractor: &Self::Extractor, - scrutinee_type: &Self::Type, - arity: usize, - ) -> bool { - match extractor { - SpaceExtractor::Tuple(expected_arity) => match scrutinee_type { - SpaceTy::Base(ty) => { - matches!(ty.kind(self.db), TyKind::Tuple(items) if items.len() == *expected_arity && items.len() == arity) - } - _ => false, - }, - SpaceExtractor::StructFields { nominal, fields } => match scrutinee_type { - SpaceTy::Base(ty) => match (nominal, ty.kind(self.db)) { - (Some(expected), TyKind::Struct(actual)) if expected == actual => { - struct_field_types(self.db, scrutinee_type, fields).len() == arity - } - (None, TyKind::Record(_)) => { - struct_field_types(self.db, scrutinee_type, fields).len() == arity - } - _ => false, - }, - _ => false, - }, - SpaceExtractor::EnumVariant(variant) => match scrutinee_type { - SpaceTy::EnumVariant { parent, variant: actual } if actual == variant => { - enum_variant_payload_types(self.db, *parent, *variant).len() == arity - } - _ => false, - }, - SpaceExtractor::UnionMember { parent, index, member } => match scrutinee_type { - SpaceTy::UnionMember { - parent: actual_parent, - index: actual_index, - member: actual_member, - } => { - actual_parent == parent - && actual_index == index - && actual_member == member - && arity == 1 - } - _ => false, - }, - } - } - - fn intersect_atomic_types( - &self, - left: &Self::Type, - right: &Self::Type, - ) -> patmat::AtomicIntersection { - let intersection = match (left, right) { - (SpaceTy::IntLiteral(value), SpaceTy::IntRest(excluded)) - | (SpaceTy::IntRest(excluded), SpaceTy::IntLiteral(value)) => { - (!excluded.contains(value)).then_some(SpaceTy::IntLiteral(*value)) - } - (SpaceTy::Base(left_ty), SpaceTy::Base(right_ty)) => { - intersect_semantic_types(self.db, *left_ty, *right_ty).map(SpaceTy::Base) - } - _ => None, - }; - match intersection { - Some(ty) => patmat::AtomicIntersection::Type(ty), - None => patmat::AtomicIntersection::Empty, - } - } -} - -fn semantic_is_subtype(db: &dyn salsa::Database, left: Ty<'_>, right: Ty<'_>) -> bool { - if left == right { - return true; - } - match right.kind(db) { - TyKind::Union(members) => { - members.iter().any(|member| semantic_is_subtype(db, left, *member)) - } - _ => false, - } -} - -fn intersect_semantic_types<'db>( - db: &'db dyn salsa::Database, - left: Ty<'db>, - right: Ty<'db>, -) -> Option> { - if semantic_is_subtype(db, left, right) { - Some(left) - } else if semantic_is_subtype(db, right, left) { - Some(right) - } else { - None - } -} - -fn struct_or_record_field_ty<'db>( - db: &'db dyn salsa::Database, - expected_ty: Ty<'db>, - field_name: Symbol<'db>, -) -> Option> { - match expected_ty.kind(db) { - TyKind::Struct(struct_ty) => struct_fields(db, *struct_ty) - .iter() - .find(|(name, _)| *name == field_name) - .map(|(_, ty)| *ty), - TyKind::Record(fields) => { - fields.iter().find(|(name, _)| *name == field_name).map(|(_, ty)| *ty) - } - _ => None, - } -} - -fn struct_field_types<'db>( - db: &'db dyn salsa::Database, - scrutinee_type: &SpaceTy<'db>, - fields: &[Symbol<'db>], -) -> Vec> { - let SpaceTy::Base(ty) = scrutinee_type else { - return Vec::new(); - }; - fields.iter().filter_map(|field| struct_or_record_field_ty(db, *ty, *field)).collect() -} - -fn enum_variant_payload_types<'db>( - db: &'db dyn salsa::Database, - enum_ty: Ty<'db>, - variant: Symbol<'db>, -) -> Vec> { - let TyKind::Enum(enum_ty_id) = enum_ty.kind(db) else { - return Vec::new(); - }; - enum_variants(db, *enum_ty_id) - .iter() - .find(|(name, _)| *name == variant) - .map(|(_, payload)| payload.clone()) - .unwrap_or_default() -} - -fn render_space<'db>( - db: &'db dyn salsa::Database, - context: &SpaceContext, SpaceExtractor<'db>>, - space: Space, SpaceExtractor<'db>>, -) -> String { - match context.kind(space) { - SpaceKind::Empty => "never".to_owned(), - SpaceKind::Type(type_space) => match type_space.value_type { - SpaceTy::Base(ty) => ty.display(db).to_string(), - SpaceTy::Bool(value) => value.to_string(), - SpaceTy::IntLiteral(value) => value.to_string(), - SpaceTy::IntRest(_) => "int".to_owned(), - SpaceTy::UnionMember { member, .. } => member.display(db).to_string(), - SpaceTy::EnumVariant { variant, .. } => format!(".{}", variant.text(db)), - }, - SpaceKind::Union(spaces) => spaces - .iter() - .map(|space| render_space(db, context, *space)) - .collect::>() - .join(" | "), - SpaceKind::Product(product) => match product.extractor { - SpaceExtractor::Tuple(_) => format!( - "({})", - product - .parameters - .iter() - .map(|space| render_space(db, context, *space)) - .collect::>() - .join(", ") - ), - SpaceExtractor::StructFields { nominal, fields } => { - let mut rendered = String::new(); - if let Some(nominal) = nominal { - let _ = write!(rendered, "{} ", nominal.name(db).text(db)); - } - rendered.push('{'); - for (index, (field, parameter)) in - fields.iter().zip(product.parameters.iter()).enumerate() - { - if index > 0 { - rendered.push_str(", "); - } - let _ = write!( - rendered, - "{}: {}", - field.text(db), - render_space(db, context, *parameter) - ); - } - rendered.push('}'); - rendered - } - SpaceExtractor::EnumVariant(variant) => { - if product.parameters.is_empty() { - format!(".{}", variant.text(db)) - } else { - format!( - ".{}({})", - variant.text(db), - product - .parameters - .iter() - .map(|space| render_space(db, context, *space)) - .collect::>() - .join(", ") - ) - } - } - SpaceExtractor::UnionMember { .. } => product - .parameters - .first() - .map_or_else(|| "unknown".to_owned(), |space| render_space(db, context, *space)), - }, - } -} - -fn parse_int_literal(literal: Option>, db: &dyn salsa::Database) -> Result { - let Some(literal) = literal else { - return Err("missing integer literal".to_owned()); - }; - - let text = literal.text(db).replace('_', ""); - let (radix, digits) = if let Some(rest) = text.strip_prefix("0b") { - (2, rest) - } else if let Some(rest) = text.strip_prefix("0o") { - (8, rest) - } else if let Some(rest) = text.strip_prefix("0x") { - (16, rest) - } else { - (10, text.as_str()) - }; - - let value = i64::from_str_radix(digits, radix).map_err(|error| error.to_string())?; - i32::try_from(value).map_err(|_overflow| "integer literal does not fit in i32".to_owned()) -} diff --git a/crates/mitki-analysis/src/ownership.rs b/crates/mitki-analysis/src/ownership.rs deleted file mode 100644 index a5d2a01..0000000 --- a/crates/mitki-analysis/src/ownership.rs +++ /dev/null @@ -1,154 +0,0 @@ -use mitki_hir::ty::{EnumTy, StructTy, Ty, TyKind}; -use mitki_lower::item::scope::{ - HasVisibleItems as _, TypeDeclaration, enum_variants, struct_fields, -}; -use rustc_hash::FxHashSet; -use salsa::plumbing::AsId as _; - -#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] -pub struct TypeOwnership { - copyable: bool, - needs_drop: bool, - explicit_destructor: bool, -} - -impl TypeOwnership { - pub fn is_copyable(&self) -> bool { - self.copyable - } - - pub fn needs_drop(&self) -> bool { - self.needs_drop - } - - pub fn has_explicit_destructor(&self) -> bool { - self.explicit_destructor - } -} - -#[salsa::tracked(returns(ref))] -pub fn type_ownership<'db>(db: &'db dyn salsa::Database, ty: Ty<'db>) -> TypeOwnership { - compute_type_ownership(db, ty, &mut FxHashSet::default()) -} - -pub fn is_copyable(db: &dyn salsa::Database, ty: Ty<'_>) -> bool { - type_ownership(db, ty).is_copyable() -} - -pub fn needs_drop(db: &dyn salsa::Database, ty: Ty<'_>) -> bool { - type_ownership(db, ty).needs_drop() -} - -pub fn has_explicit_destructor(db: &dyn salsa::Database, ty: Ty<'_>) -> bool { - type_ownership(db, ty).has_explicit_destructor() -} - -fn compute_type_ownership( - db: &dyn salsa::Database, - ty: Ty<'_>, - seen: &mut FxHashSet, -) -> TypeOwnership { - let bits = ty.as_id().as_bits(); - if !seen.insert(bits) { - return TypeOwnership { copyable: false, needs_drop: false, explicit_destructor: false }; - } - - let ownership = match ty.kind(db) { - TyKind::Bool - | TyKind::Float - | TyKind::Int - | TyKind::ExactInt(_) - | TyKind::String - | TyKind::Char - | TyKind::Pointer { .. } - | TyKind::Function { .. } - | TyKind::Unknown - | TyKind::Var(_) => { - TypeOwnership { copyable: true, needs_drop: false, explicit_destructor: false } - } - TyKind::Array(item) => inherited_ownership([compute_type_ownership(db, *item, seen)]), - TyKind::Tuple(items) => { - inherited_ownership(items.iter().map(|item| compute_type_ownership(db, *item, seen))) - } - TyKind::Record(fields) => inherited_ownership( - fields.iter().map(|(_, field_ty)| compute_type_ownership(db, *field_ty, seen)), - ), - TyKind::Struct(struct_ty) => nominal_ownership( - db, - struct_ty.destructor(db).is_some(), - struct_fields(db, *struct_ty) - .iter() - .map(|(_, field_ty)| compute_type_ownership(db, *field_ty, seen)), - ), - TyKind::Enum(enum_ty) => { - let mut field_ownership = Vec::new(); - for (_, field_tys) in enum_variants(db, *enum_ty).iter() { - for field_ty in field_tys { - field_ownership.push(compute_type_ownership(db, *field_ty, seen)); - } - } - nominal_ownership(db, enum_ty.destructor(db).is_some(), field_ownership) - } - TyKind::Union(items) | TyKind::Inter(items) => { - inherited_ownership(items.iter().map(|item| compute_type_ownership(db, *item, seen))) - } - TyKind::ExternStruct(_) => { - TypeOwnership { copyable: true, needs_drop: false, explicit_destructor: false } - } - TyKind::Rec(_, body) => compute_type_ownership(db, *body, seen), - }; - - seen.remove(&bits); - ownership -} - -fn inherited_ownership(items: impl IntoIterator) -> TypeOwnership { - let mut copyable = true; - let mut needs_drop = false; - let mut explicit_destructor = false; - - for item in items { - copyable &= item.copyable; - needs_drop |= item.needs_drop; - explicit_destructor |= item.explicit_destructor; - } - - TypeOwnership { copyable, needs_drop, explicit_destructor } -} - -fn nominal_ownership( - _db: &dyn salsa::Database, - explicit_destructor: bool, - fields: impl IntoIterator, -) -> TypeOwnership { - let inherited = inherited_ownership(fields); - TypeOwnership { - copyable: !explicit_destructor && inherited.copyable, - needs_drop: explicit_destructor || inherited.needs_drop, - explicit_destructor, - } -} - -pub trait NominalDestructorLookup<'db> { - fn destructor(self, db: &'db dyn salsa::Database) -> Option<()>; -} - -impl<'db> NominalDestructorLookup<'db> for StructTy<'db> { - fn destructor(self, db: &'db dyn salsa::Database) -> Option<()> { - let source = self.module(db).visible_items(db).get_type_declaration(&self.name(db))?; - let TypeDeclaration::Struct(location) = source else { - return None; - }; - location.destructor(db).map(|_| ()) - } -} - -impl<'db> NominalDestructorLookup<'db> for EnumTy<'db> { - fn destructor(self, db: &'db dyn salsa::Database) -> Option<()> { - let source = self.module(db).visible_items(db).get_type_declaration(&self.name(db))?; - let TypeDeclaration::Enum(location) = source else { - return None; - }; - location.destructor(db).map(|_| ()) - } -} diff --git a/crates/mitki-analysis/src/semantics.rs b/crates/mitki-analysis/src/semantics.rs deleted file mode 100644 index 330c609..0000000 --- a/crates/mitki-analysis/src/semantics.rs +++ /dev/null @@ -1,739 +0,0 @@ -use mitki_inputs::File; -use mitki_lower::ast_map::HasAstMap as _; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::package::{HasPackage as _, root_module}; -use mitki_lower::item::scope::{ - Declaration, EnumVariantLocation, FunctionLocation, HasItemDecls as _, HasVisibleItems as _, - ItemDecls, VisibleItems, -}; -use mitki_lower::item::tree::HasItemTree as _; -use mitki_resolve::scope::HasExprScopes as _; -use mitki_resolve::{ - BindingId, Namespace, Resolution, ResolveStatus, Resolver, TargetId, VisibleBinding, - lookup_builtin_type, resolve_method_for_receiver, -}; -use mitki_span::IntoSymbol as _; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::ast::{HasName as _, Node as _}; -use mitki_yellow::{SyntaxKind, SyntaxNode, SyntaxNodePtr, ast}; -use rustc_hash::FxHashMap; -use salsa::Database; -use text_size::TextRange; - -#[derive(Clone, Copy)] -pub enum ResolveIntent { - Any, - Value, - Type, - EnumVariant, -} - -pub struct Semantics<'db> { - file: File, - source_map: SourceMap<'db>, -} - -impl<'db> Semantics<'db> { - pub fn new(db: &'db dyn Database, file: File) -> Self { - let mut source_map = SourceMap { functions: FxHashMap::default() }; - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - - for &declaration in file.item_decls(db).declarations() { - match declaration { - Declaration::Function(func) => { - let id = item_tree[func.index(db)].id; - let &ptr = ast_map.find_node(id); - source_map.functions.insert(ptr, func); - } - Declaration::BoundaryInstance(_) - | Declaration::Struct(_) - | Declaration::Enum(_) => {} - } - } - - Self { file, source_map } - } - - pub fn function(&self, function: &SyntaxNode) -> FunctionLocation<'db> { - self.source_map.functions[&SyntaxNodePtr::new(function)] - } - - pub fn resolver( - &self, - db: &'db dyn Database, - location: FunctionLocation<'db>, - current_node: &SyntaxNode, - ) -> Resolver<'db> { - let source_map = location.hir_function(db).source_map(db); - let scopes = location.expr_scopes(db); - let scope = current_node - .ancestors() - .filter_map(ast::Expr::cast) - .find_map(|expr| source_map.syntax_expr(expr.syntax())) - .and_then(|expr| scopes.scope_by_node(expr.into())); - - Resolver::for_scope( - db, - location.module(db), - location.module(db).visible_items(db), - location.module(db).item_decls(db), - scopes, - scope, - ) - } - - pub fn resolve_at( - &self, - db: &'db dyn Database, - current_node: &SyntaxNode, - intent: ResolveIntent, - ) -> Resolution<'db> { - if let Some(binding) = self.binding_for_declaration(db, current_node) { - return Resolution { - binding: Some(binding), - target: binding.target(), - namespace: binding.namespace(), - status: ResolveStatus::Resolved, - }; - } - - let symbol = Self::symbol_for_node(db, current_node); - let root_module = root_module(db, self.file.package(db)); - if let Some(location) = self.enclosing_function(current_node) { - let resolver = self.resolver(db, location, current_node); - return self.resolve_with_resolver( - db, - &resolver, - location, - current_node, - symbol, - intent, - ); - } - - Self::resolve_file_level( - db, - root_module.visible_items(db), - root_module.item_decls(db), - symbol, - intent, - ) - } - - pub fn binding_at( - &self, - db: &'db dyn Database, - current_node: &SyntaxNode, - intent: ResolveIntent, - ) -> Option> { - self.resolve_at(db, current_node, intent).binding - } - - pub fn declaration_target( - &self, - db: &'db dyn Database, - binding: BindingId<'db>, - ) -> Option { - match binding { - BindingId::Local(name) | BindingId::Param(name) => { - let source_map = self.function_source_map_for_name(db, name)?; - Some(source_map.node_syntax(name.into()).range) - } - BindingId::Function(location) => Some(location.source(db).name()?.text_range()), - BindingId::Struct(location) => Some(location.source(db).name()?.text_range()), - BindingId::Enum(location) => Some(location.source(db).name()?.text_range()), - BindingId::EnumVariant(location) => Some(location.source(db).name()?.text_range()), - BindingId::RuntimeFunction(_) - | BindingId::CompilerIntrinsic(_) - | BindingId::BuiltinType(_) => None, - } - } - - pub fn definition_target( - &self, - db: &'db dyn Database, - target: TargetId<'db>, - ) -> Option { - match target { - TargetId::Local(name) | TargetId::Param(name) => { - let source_map = self.function_source_map_for_name(db, name)?; - Some(source_map.node_syntax(name.into()).range) - } - TargetId::Function(location) => Some(location.source(db).name()?.text_range()), - TargetId::Struct(location) => Some(location.source(db).name()?.text_range()), - TargetId::Enum(location) => Some(location.source(db).name()?.text_range()), - TargetId::EnumVariant(location) => Some(location.source(db).name()?.text_range()), - } - } - - pub fn definition_target_at( - &self, - db: &'db dyn Database, - current_node: &SyntaxNode, - target: TargetId<'db>, - ) -> Option { - match target { - TargetId::Local(name) | TargetId::Param(name) => { - let location = self.enclosing_function(current_node)?; - let source_map = location.hir_function(db).source_map(db); - Some(source_map.node_syntax(name.into()).range) - } - other => self.definition_target(db, other), - } - } - - pub fn visible_bindings_at( - &self, - db: &'db dyn Database, - current_node: &SyntaxNode, - ) -> Vec> { - if let Some(location) = self.enclosing_function(current_node) { - return self.resolver(db, location, current_node).visible_bindings(); - } - - let mut bindings = Vec::new(); - let visible_items = root_module(db, self.file.package(db)).visible_items(db); - bindings.extend(visible_items.values().map(|(name, function)| VisibleBinding { - name: *name, - binding: BindingId::Function(*function), - namespace: Namespace::Value, - })); - bindings.extend(visible_items.types().filter_map(|(name, _)| { - let declaration = visible_items.get_type_declaration(name)?; - let binding = match declaration { - mitki_lower::item::scope::TypeDeclaration::Struct(location) => { - BindingId::Struct(location) - } - mitki_lower::item::scope::TypeDeclaration::Enum(location) => { - BindingId::Enum(location) - } - }; - Some(VisibleBinding { name: *name, binding, namespace: Namespace::Type }) - })); - bindings - } - - fn resolve_with_resolver( - &self, - db: &'db dyn Database, - resolver: &Resolver<'db>, - location: FunctionLocation<'db>, - current_node: &SyntaxNode, - symbol: mitki_span::Symbol<'db>, - intent: ResolveIntent, - ) -> Resolution<'db> { - match intent { - ResolveIntent::Value => { - Self::resolve_method_call_at(db, location, current_node, symbol) - .unwrap_or_else(|| resolver.resolve_name(symbol, Namespace::Value)) - } - ResolveIntent::Type => resolver.resolve_name(symbol, Namespace::Type), - ResolveIntent::EnumVariant => { - self.resolve_enum_variant_at(db, resolver, current_node, symbol) - } - ResolveIntent::Any => { - if current_node.kind() == SyntaxKind::PATH_TYPE { - resolver.resolve_name(symbol, Namespace::Type) - } else { - if Self::is_enum_variant_site(current_node) { - let enum_variant = - self.resolve_enum_variant_at(db, resolver, current_node, symbol); - if enum_variant.status != ResolveStatus::Unresolved { - return enum_variant; - } - } - if let Some(method) = - Self::resolve_method_call_at(db, location, current_node, symbol) - { - return method; - } - let value = resolver.resolve_name(symbol, Namespace::Value); - if value.status == ResolveStatus::Resolved { - value - } else { - resolver.resolve_name(symbol, Namespace::Type) - } - } - } - } - } - - fn resolve_enum_variant_at( - &self, - db: &'db dyn Database, - resolver: &Resolver<'db>, - current_node: &SyntaxNode, - symbol: mitki_span::Symbol<'db>, - ) -> Resolution<'db> { - let Some(field_expr) = current_node.ancestors().find_map(ast::FieldExpr::cast) else { - return resolver.resolve_enum_variant_binding(symbol); - }; - let Some(field_name) = field_expr.name() else { - return resolver.resolve_enum_variant_binding(symbol); - }; - if field_name.syntax().text_range() != current_node.text_range() { - return resolver.resolve_enum_variant_binding(symbol); - } - - let matching = if let Some(base_expr) = field_expr.expr() { - let ast::Expr::Path(path) = base_expr else { - return Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }; - }; - let Some(base_name) = path.name() else { - return Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }; - }; - let base_symbol = base_name.as_str().into_symbol(db); - let enum_location = match self.file.visible_items(db).get_type_declaration(&base_symbol) - { - Some(mitki_lower::item::scope::TypeDeclaration::Enum(location)) => location, - _ => match resolver.resolve_name(base_symbol, Namespace::Type).binding { - Some(BindingId::Enum(location)) => location, - _ => { - return Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }; - } - }, - }; - self.file - .item_decls(db) - .enum_variants_by_name(&symbol) - .iter() - .copied() - .filter(|variant| { - let parent = variant.parent(db); - parent.file(db) == enum_location.file(db) - && parent.index(db) == enum_location.index(db) - }) - .collect::>() - } else { - self.file.item_decls(db).enum_variants_by_name(&symbol).to_vec() - }; - - match matching.as_slice() { - [] => Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }, - [variant] => Resolution { - binding: Some(BindingId::EnumVariant(*variant)), - target: Some(TargetId::EnumVariant(*variant)), - namespace: Namespace::Value, - status: ResolveStatus::Resolved, - }, - _ => Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Ambiguous, - }, - } - } - - fn resolve_method_call_at( - db: &'db dyn Database, - location: FunctionLocation<'db>, - current_node: &SyntaxNode, - symbol: mitki_span::Symbol<'db>, - ) -> Option> { - let field_expr = current_node.ancestors().find_map(ast::FieldExpr::cast)?; - let field_name = field_expr.name()?; - if field_name.syntax().text_range() != current_node.text_range() { - return None; - } - - let call_expr = current_node.ancestors().find_map(ast::CallExpr::cast)?; - if call_expr.callee()?.syntax().text_range() != field_expr.syntax().text_range() { - return None; - } - - let receiver_expr = field_expr.expr()?; - let source_map = location.hir_function(db).source_map(db); - let receiver = source_map.syntax_expr(receiver_expr.syntax())?; - let receiver_ty = location.infer(db).type_of_node(receiver)?; - let method = resolve_method_for_receiver(db, receiver_ty, symbol)?; - - Some(Resolution { - binding: Some(BindingId::Function(method.function)), - target: Some(TargetId::Function(method.function)), - namespace: Namespace::Value, - status: ResolveStatus::Resolved, - }) - } - - fn resolve_file_level( - db: &'db dyn Database, - visible_items: &VisibleItems<'db>, - item_decls: &ItemDecls<'db>, - symbol: mitki_span::Symbol<'db>, - intent: ResolveIntent, - ) -> Resolution<'db> { - match intent { - ResolveIntent::Value => visible_items.get_value(&symbol).map_or( - Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }, - |function| Resolution { - binding: Some(BindingId::Function(function)), - target: Some(TargetId::Function(function)), - namespace: Namespace::Value, - status: ResolveStatus::Resolved, - }, - ), - ResolveIntent::Type | ResolveIntent::Any => { - if let Some(declaration) = visible_items.get_type_declaration(&symbol) { - let (binding, target) = match declaration { - mitki_lower::item::scope::TypeDeclaration::Struct(location) => { - (BindingId::Struct(location), TargetId::Struct(location)) - } - mitki_lower::item::scope::TypeDeclaration::Enum(location) => { - (BindingId::Enum(location), TargetId::Enum(location)) - } - }; - return Resolution { - binding: Some(binding), - target: Some(target), - namespace: Namespace::Type, - status: ResolveStatus::Resolved, - }; - } - if let Some(ty) = lookup_builtin_type(db, symbol) { - return Resolution { - binding: Some(BindingId::BuiltinType(ty)), - target: None, - namespace: Namespace::Type, - status: ResolveStatus::Resolved, - }; - } - Resolution { - binding: None, - target: None, - namespace: Namespace::Type, - status: ResolveStatus::Unresolved, - } - } - ResolveIntent::EnumVariant => { - let variants = item_decls.enum_variants_by_name(&symbol); - match variants { - [] => Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Unresolved, - }, - [variant] => Resolution { - binding: Some(BindingId::EnumVariant(*variant)), - target: Some(TargetId::EnumVariant(*variant)), - namespace: Namespace::Value, - status: ResolveStatus::Resolved, - }, - _ => Resolution { - binding: None, - target: None, - namespace: Namespace::Value, - status: ResolveStatus::Ambiguous, - }, - } - } - } - } - - fn binding_for_declaration( - &self, - db: &'db dyn Database, - current_node: &SyntaxNode, - ) -> Option> { - let symbol = Self::symbol_for_node(db, current_node); - - if let Some(function) = current_node.ancestors().find_map(ast::Function::cast) - && function - .name() - .is_some_and(|name| name.syntax().text_range() == current_node.text_range()) - { - return Some(BindingId::Function(self.function(function.syntax()))); - } - - if let Some(struct_def) = current_node.ancestors().find_map(ast::StructDef::cast) - && struct_def - .name() - .is_some_and(|name| name.syntax().text_range() == current_node.text_range()) - { - let declaration = self.file.visible_items(db).get_type_declaration(&symbol)?; - return match declaration { - mitki_lower::item::scope::TypeDeclaration::Struct(location) => { - Some(BindingId::Struct(location)) - } - mitki_lower::item::scope::TypeDeclaration::Enum(_) => None, - }; - } - - if let Some(enum_def) = current_node.ancestors().find_map(ast::EnumDef::cast) - && enum_def - .name() - .is_some_and(|name| name.syntax().text_range() == current_node.text_range()) - { - let declaration = self.file.visible_items(db).get_type_declaration(&symbol)?; - return match declaration { - mitki_lower::item::scope::TypeDeclaration::Enum(location) => { - Some(BindingId::Enum(location)) - } - mitki_lower::item::scope::TypeDeclaration::Struct(_) => None, - }; - } - - if let Some(variant) = current_node.ancestors().find_map(ast::EnumVariant::cast) - && variant - .name() - .is_some_and(|name| name.syntax().text_range() == current_node.text_range()) - { - return self - .find_enum_variant_by_range(db, current_node.text_range()) - .map(BindingId::EnumVariant); - } - - None - } - - fn find_enum_variant_by_range( - &self, - db: &'db dyn Database, - range: TextRange, - ) -> Option> { - self.file.item_decls(db).enum_variants().iter().copied().find(|variant| { - variant.source(db).name().is_some_and(|name| name.text_range() == range) - }) - } - - fn enclosing_function(&self, current_node: &SyntaxNode) -> Option> { - current_node - .ancestors() - .find_map(ast::Function::cast) - .map(|function| self.function(function.syntax())) - } - - fn function_source_map_for_name( - &self, - db: &'db dyn Database, - name: mitki_hir::hir::NameId, - ) -> Option<&mitki_lower::hir::FunctionSourceMap> { - self.source_map.functions.values().copied().find_map(|location| { - let source_map = location.hir_function(db).source_map(db); - source_map.try_node_syntax(name.into())?; - Some(source_map) - }) - } - - fn is_enum_variant_site(current_node: &SyntaxNode) -> bool { - current_node.ancestors().find_map(ast::FieldExpr::cast).is_some() - } - - fn symbol_for_node( - db: &'db dyn Database, - current_node: &SyntaxNode, - ) -> mitki_span::Symbol<'db> { - if current_node.kind() == SyntaxKind::PATH_EXPR { - return current_node.text_trimmed().into_symbol(db); - } - - if current_node.kind() == SyntaxKind::PATH_TYPE { - return ast::PathType::cast(*current_node).map_or_else( - || current_node.text_trimmed().into_symbol(db), - |path| path.path_text().into_symbol(db), - ); - } - - current_node - .children_with_tokens() - .find_map(|element| { - let token = element.into_token()?; - if token.is_trivia() { None } else { Some(token.text_trimmed().into_symbol(db)) } - }) - .unwrap_or_else(|| current_node.text_trimmed().into_symbol(db)) - } -} - -struct SourceMap<'db> { - functions: FxHashMap>, -} - -#[cfg(test)] -mod tests { - use mitki_db::RootDatabase; - use mitki_inputs::File; - use mitki_parse::FileParse as _; - use mitki_resolve::{BindingId, ResolveStatus}; - use mitki_yellow::SyntaxKind; - use mitki_yellow::ast::HasName as _; - use text_size::{TextRange, TextSize}; - - use super::{ResolveIntent, Semantics}; - - const DEF_MARKER: &str = "$def$"; - - fn extract_cursor_offset(text: &str) -> (TextSize, String) { - let marker = "$0"; - let cursor = text.find(marker).expect("cursor marker"); - let mut new_text = String::with_capacity(text.len() - marker.len()); - new_text.push_str(&text[..cursor]); - new_text.push_str(&text[cursor + marker.len()..]); - (TextSize::from(cursor as u32), new_text) - } - - fn extract_definition_range(text: &str) -> (TextRange, String) { - let mut text = text.to_owned(); - let def = text.find(DEF_MARKER).expect("definition marker"); - text.replace_range(def..def + DEF_MARKER.len(), ""); - let len = - text[def..].chars().take_while(|ch| ch.is_ascii_alphanumeric() || *ch == '_').count(); - (TextRange::at(TextSize::from(def as u32), TextSize::from(len as u32)), text) - } - - fn name_node_at<'db>( - db: &'db RootDatabase, - file: File, - offset: TextSize, - ) -> mitki_yellow::SyntaxNode<'db> { - let root = file.parse(db).syntax_node(); - let token = root - .token_at_offset(offset) - .filter(|token| !token.is_trivia()) - .max_by_key(|token| usize::from(token.kind() == SyntaxKind::NAME)) - .expect("name token"); - token.parent() - } - - #[test] - fn resolve_at_reports_unresolved_status() { - let db = RootDatabase::default(); - let (offset, text) = extract_cursor_offset( - r#" -fun main() { - $0missing -} -"#, - ); - let file = File::new(&db, "test.mitki".into(), text); - let semantics = Semantics::new(&db, file); - let node = name_node_at(&db, file, offset); - - let resolution = semantics.resolve_at(&db, &node, ResolveIntent::Any); - assert_eq!(resolution.status, ResolveStatus::Unresolved); - assert!(resolution.binding.is_none()); - } - - #[test] - fn resolve_at_reports_ambiguous_enum_variant_status() { - let db = RootDatabase::default(); - let (offset, text) = extract_cursor_offset( - r#" -enum Color { Red } -enum Light { Red } - -fun main() { - .R$0ed -} -"#, - ); - let file = File::new(&db, "test.mitki".into(), text); - let semantics = Semantics::new(&db, file); - let node = name_node_at(&db, file, offset); - - let resolution = semantics.resolve_at(&db, &node, ResolveIntent::Any); - assert_eq!(resolution.status, ResolveStatus::Ambiguous); - assert!(resolution.target.is_none()); - } - - #[test] - fn declaration_and_definition_targets_match_for_locals() { - let db = RootDatabase::default(); - let (expected, text) = extract_definition_range( - r#" -fun main() { - val $def$x = 1 - x -} -"#, - ); - let (offset, text) = extract_cursor_offset(&text.replace(" x", " $0x")); - let file = File::new(&db, "test.mitki".into(), text); - let semantics = Semantics::new(&db, file); - let node = name_node_at(&db, file, offset); - let resolution = semantics.resolve_at(&db, &node, ResolveIntent::Any); - let binding = resolution.binding.expect("binding"); - let target = resolution.target.expect("target"); - - assert_eq!(semantics.declaration_target(&db, binding), Some(expected)); - assert_eq!(semantics.definition_target(&db, target), Some(expected)); - } - - #[test] - fn visible_bindings_are_ordered_from_inner_to_outer() { - let db = RootDatabase::default(); - let (offset, text) = extract_cursor_offset( - r#" -struct Point { x: int } - -fun main(x: int) { - val x = 1 - $0x -} -"#, - ); - let file = File::new(&db, "test.mitki".into(), text); - let semantics = Semantics::new(&db, file); - let node = name_node_at(&db, file, offset); - let visible = semantics.visible_bindings_at(&db, &node); - - assert!(matches!( - visible.first().map(|binding| binding.binding), - Some(BindingId::Local(_)) - )); - assert!(visible.iter().any(|binding| matches!(binding.binding, BindingId::Param(_)))); - assert!(visible.iter().any(|binding| matches!(binding.binding, BindingId::Struct(_)))); - } - - #[test] - fn resolve_at_maps_method_names_to_module_functions() { - let db = RootDatabase::default(); - let (offset, text) = extract_cursor_offset( - r#" -use std::vec::int as vec; - -fun main() { - val xs: vec::Vec = vec::new() - xs.pu$0sh(1) -} -"#, - ); - let file = File::new(&db, "test.mitki".into(), text); - let semantics = Semantics::new(&db, file); - let node = name_node_at(&db, file, offset); - - let resolution = semantics.resolve_at(&db, &node, ResolveIntent::Any); - let BindingId::Function(function) = resolution.binding.expect("binding") else { - panic!("expected method name to resolve to a function"); - }; - - assert_eq!(resolution.status, ResolveStatus::Resolved); - assert_eq!(function.source(&db).name().expect("function name").as_str(), "push"); - } -} diff --git a/crates/mitki-analysis/src/wasm_boundary_legality.rs b/crates/mitki-analysis/src/wasm_boundary_legality.rs deleted file mode 100644 index b60613a..0000000 --- a/crates/mitki-analysis/src/wasm_boundary_legality.rs +++ /dev/null @@ -1,351 +0,0 @@ -use mitki_errors::Diagnostic; -use mitki_hir::hir::{Function, WasmLinkage}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{ - BoundaryInstanceKind, BoundaryInstanceLocation, FunctionLocation, Signature, enum_variants, - struct_fields, -}; -use mitki_resolve::SignatureTypeResolver; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::SyntaxNodePtr; -use mitki_yellow::ast::{HasName as _, Node as _}; -use rustc_hash::FxHashSet; - -use crate::ownership; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BoundaryLegalityFailure<'db> { - ExactInt(Ty<'db>), - Pointer(Ty<'db>), - ExternStruct(Ty<'db>), - NonCopy(Ty<'db>), -} - -pub fn typed_wasm_boundary_failure<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Option> { - let mut seen = FxHashSet::default(); - typed_wasm_boundary_failure_inner(db, ty, &mut seen) -} - -pub fn typed_wasm_boundary_message( - db: &dyn salsa::Database, - failure: BoundaryLegalityFailure<'_>, -) -> String { - match failure { - BoundaryLegalityFailure::ExactInt(ty) => format!( - "typed Wasm imports/exports do not allow exact-width integer types like `{}`; raw \ - unsafe Wasm imports remain available for exact-width integers", - ty.display(db) - ), - BoundaryLegalityFailure::Pointer(ty) => format!( - "typed Wasm imports/exports do not allow pointer types like `{}`; raw unsafe Wasm \ - imports remain available for pointers", - ty.display(db) - ), - BoundaryLegalityFailure::ExternStruct(ty) => format!( - "typed Wasm imports/exports do not allow extern structs like `{}`; raw unsafe Wasm \ - imports remain available for extern structs", - ty.display(db) - ), - BoundaryLegalityFailure::NonCopy(ty) => format!( - "typed Wasm imports/exports do not allow non-copy types like `{}` because destructor \ - ownership is not supported across the Wasm boundary yet", - ty.display(db) - ), - } -} - -pub(crate) fn check_function_boundary_legality( - db: &dyn salsa::Database, - func: FunctionLocation<'_>, -) -> Vec { - let source = func.source(db); - let source_map = func.hir_function(db).source_map(db); - let function = func.hir_function(db).function(db); - let nodes = function.node_store(); - let fallback_range = SyntaxNodePtr::new(source.syntax()).range; - let mut diagnostics = Vec::new(); - - match function.linkage() { - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } => { - if source.body().is_some() { - diagnostics.push(Diagnostic::error( - "Imported Wasm functions cannot have a body", - fallback_range, - )); - } - } - WasmLinkage::Internal | WasmLinkage::ImplicitMainExport | WasmLinkage::Export => { - if source.body().is_none() { - diagnostics.push(Diagnostic::error("Function body is required", fallback_range)); - } - } - } - - if function.is_comptime() && !matches!(function.linkage(), WasmLinkage::Internal) { - diagnostics.push(Diagnostic::error( - "`comptime fun` cannot be combined with Wasm import/export modifiers", - fallback_range, - )); - } - - if matches!(function.linkage(), WasmLinkage::ImplicitMainExport | WasmLinkage::Export) - && !function.type_params().is_empty() - { - diagnostics.push(Diagnostic::error( - "Wasm imports and exports do not support generic functions", - fallback_range, - )); - } - - if !function.type_params().is_empty() || !is_direct_typed_wasm_boundary(function) { - return diagnostics; - } - - let Some((param_tys, result_ty)) = function_signature_types(db, func, function, &[]) else { - return diagnostics; - }; - - for (¶m, ty) in function.params().iter().zip(param_tys) { - let (pattern, ty_id) = nodes.param(param); - let range = if ty_id != mitki_hir::hir::TyId::ZERO { - source_map.try_type_syntax(ty_id).map_or_else(|| fallback_range, |ptr| ptr.range) - } else { - source_map.try_pat_syntax(pattern).map_or_else(|| fallback_range, |ptr| ptr.range) - }; - if let Some(failure) = typed_wasm_boundary_failure(db, ty) { - diagnostics.push(Diagnostic::error(typed_wasm_boundary_message(db, failure), range)); - } - } - - if let Some(failure) = typed_wasm_boundary_failure(db, result_ty) { - diagnostics.push(Diagnostic::error( - typed_wasm_boundary_message(db, failure), - return_range(db, func, function, fallback_range), - )); - } - - diagnostics -} - -pub(crate) fn check_boundary_instance_legality( - db: &dyn salsa::Database, - instance: BoundaryInstanceLocation<'_>, -) -> Vec { - let source = instance.source(db); - let range = SyntaxNodePtr::new(source.syntax()).range; - let mut diagnostics = Vec::new(); - let name = source.name().map_or("", |name| name.as_str()); - - let Some(origin) = instance.origin(db) else { - diagnostics - .push(Diagnostic::error(format!("unknown boundary instance target `{name}`"), range)); - return diagnostics; - }; - - let function = origin.hir_function(db).function(db); - let type_param_count = function.type_params().len(); - if type_param_count == 0 { - diagnostics.push(Diagnostic::error( - format!("boundary instance target `{name}` must be a generic function"), - range, - )); - return diagnostics; - } - - let type_args = instance.type_args(db); - if type_args.len() != type_param_count { - diagnostics.push(Diagnostic::error( - format!( - "boundary instance target `{name}` expects {type_param_count} type argument(s), \ - found {}", - type_args.len() - ), - range, - )); - } - - match instance.kind(db) { - BoundaryInstanceKind::Import => { - if !matches!(function.linkage(), WasmLinkage::Import { .. }) { - diagnostics.push(Diagnostic::error( - format!("import instance target `{name}` must be an imported generic function"), - range, - )); - } - } - BoundaryInstanceKind::Export => { - if !matches!(function.linkage(), WasmLinkage::Internal) { - diagnostics.push(Diagnostic::error( - format!( - "export instance target `{name}` must be a non-import generic function" - ), - range, - )); - } - } - } - - if !diagnostics.is_empty() { - return diagnostics; - } - - let Some((param_tys, result_ty)) = function_signature_types(db, origin, function, type_args) - else { - return diagnostics; - }; - - for ty in param_tys.into_iter().chain(std::iter::once(result_ty)) { - if let Some(failure) = typed_wasm_boundary_failure(db, ty) { - diagnostics.push(Diagnostic::error(typed_wasm_boundary_message(db, failure), range)); - } - } - - diagnostics -} - -fn typed_wasm_boundary_failure_inner<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - seen: &mut FxHashSet>, -) -> Option> { - if !seen.insert(ty) { - return None; - } - - match ty.kind(db) { - _ if !ownership::is_copyable(db, ty) => Some(BoundaryLegalityFailure::NonCopy(ty)), - TyKind::ExactInt(_) => Some(BoundaryLegalityFailure::ExactInt(ty)), - TyKind::Pointer { .. } => Some(BoundaryLegalityFailure::Pointer(ty)), - TyKind::ExternStruct(_) => Some(BoundaryLegalityFailure::ExternStruct(ty)), - TyKind::Array(item) => typed_wasm_boundary_failure_inner(db, *item, seen), - TyKind::Tuple(items) | TyKind::Union(items) | TyKind::Inter(items) => { - items.iter().find_map(|&item| typed_wasm_boundary_failure_inner(db, item, seen)) - } - TyKind::Record(fields) => fields - .iter() - .find_map(|(_, field_ty)| typed_wasm_boundary_failure_inner(db, *field_ty, seen)), - TyKind::Function { inputs, output } => inputs - .iter() - .find_map(|&input| typed_wasm_boundary_failure_inner(db, input, seen)) - .or_else(|| typed_wasm_boundary_failure_inner(db, *output, seen)), - TyKind::Rec(_, body) => typed_wasm_boundary_failure_inner(db, *body, seen), - TyKind::Struct(struct_ty) => struct_fields(db, *struct_ty) - .iter() - .find_map(|(_, field_ty)| typed_wasm_boundary_failure_inner(db, *field_ty, seen)), - TyKind::Enum(enum_ty) => enum_variants(db, *enum_ty).iter().find_map(|(_, fields)| { - fields - .iter() - .find_map(|&field_ty| typed_wasm_boundary_failure_inner(db, field_ty, seen)) - }), - TyKind::Bool - | TyKind::Float - | TyKind::Int - | TyKind::String - | TyKind::Char - | TyKind::Unknown - | TyKind::Var(_) => None, - } -} - -fn is_direct_typed_wasm_boundary(function: &Function<'_>) -> bool { - matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::ImplicitMainExport | WasmLinkage::Export - ) -} - -fn function_signature_types<'db>( - db: &'db dyn salsa::Database, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - type_args: &[Ty<'db>], -) -> Option<(Vec>, Ty<'db>)> { - let signature = location.signature(db); - let params = resolve_param_types(db, location, signature)? - .into_iter() - .map(|ty| specialize_ty(db, ty, type_args)) - .collect::>(); - let result = location - .infer(db) - .type_of_node(function.body()) - .unwrap_or_else(|| Ty::new(db, TyKind::Tuple(Vec::new()))); - Some((params, specialize_ty(db, result, type_args))) -} - -fn resolve_param_types<'db>( - db: &'db dyn salsa::Database, - location: FunctionLocation<'db>, - signature: &'db Signature<'db>, -) -> Option>> { - let resolver = SignatureTypeResolver::new(db, location, signature); - let nodes = signature.nodes(db); - signature - .params(db) - .iter() - .map(|¶m| { - let (_, ty) = nodes.param(param); - resolver.resolve(ty).ok() - }) - .collect() -} - -fn return_range( - db: &dyn salsa::Database, - location: FunctionLocation<'_>, - function: &Function<'_>, - fallback_range: mitki_errors::TextRange, -) -> mitki_errors::TextRange { - let source_map = location.hir_function(db).source_map(db); - if function.ret_type().is_zero() { - fallback_range - } else { - source_map.try_type_syntax(function.ret_type()).map_or(fallback_range, |ptr| ptr.range) - } -} - -fn specialize_ty<'db>(db: &'db dyn salsa::Database, ty: Ty<'db>, type_args: &[Ty<'db>]) -> Ty<'db> { - match ty.kind(db) { - TyKind::Var(id) => type_args.get(*id as usize).copied().unwrap_or(ty), - TyKind::Array(item) => { - let item = specialize_ty(db, *item, type_args); - Ty::new(db, TyKind::Array(item)) - } - TyKind::Tuple(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Tuple(items)) - } - TyKind::Record(fields) => { - let fields = fields - .iter() - .map(|(name, field_ty)| (*name, specialize_ty(db, *field_ty, type_args))) - .collect(); - Ty::new(db, TyKind::Record(fields)) - } - TyKind::Function { inputs, output } => { - let inputs = inputs.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - let output = specialize_ty(db, *output, type_args); - Ty::new(db, TyKind::Function { inputs, output }) - } - TyKind::Union(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Union(items)) - } - TyKind::Inter(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Inter(items)) - } - TyKind::Pointer { mutable, pointee } => { - let pointee = specialize_ty(db, *pointee, type_args); - Ty::new(db, TyKind::Pointer { mutable: *mutable, pointee }) - } - TyKind::Rec(id, body) => { - let body = specialize_ty(db, *body, type_args); - Ty::new(db, TyKind::Rec(*id, body)) - } - _ => ty, - } -} diff --git a/crates/mitki-backend-wasm/Cargo.toml b/crates/mitki-backend-wasm/Cargo.toml deleted file mode 100644 index 6987a30..0000000 --- a/crates/mitki-backend-wasm/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "mitki-backend-wasm" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -mitki-abi.workspace = true -mitki-abi-lower.workspace = true -mitki-analysis.workspace = true -mitki-codegen-core.workspace = true -mitki-errors.workspace = true -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-parse.workspace = true -mitki-resolve.workspace = true -mitki-span.workspace = true -mitki-typeck.workspace = true -mitki-yellow.workspace = true -rustc-hash.workspace = true -serde.workspace = true -salsa.workspace = true -wasm-encoder.workspace = true -wasmparser.workspace = true - -[dev-dependencies] -expect-test = "1.5" -mitki-abi.workspace = true -mitki-comptime-wasm.workspace = true -mitki-db.workspace = true -mitki-inputs.workspace = true -mitki-wasm-runtime.workspace = true -wasm-encoder.workspace = true -wasmparser.workspace = true -wasmtime.workspace = true -wasmprinter = "0.245.1" diff --git a/crates/mitki-backend-wasm/src/abi.rs b/crates/mitki-backend-wasm/src/abi.rs deleted file mode 100644 index 185c818..0000000 --- a/crates/mitki-backend-wasm/src/abi.rs +++ /dev/null @@ -1,44 +0,0 @@ -#[allow(unused_imports)] -pub use mitki_codegen_core::classify::{ - AbiTy, ArrayShape, BackendTy, EnumShape, FieldsShape, FunctionSignature, FunctionValueShape, - RefKind, UnionShape, ValueShape, ValueShapeFailure, ValueShapeField, ValueShapeKind, - ValueShapeVariant, WasmBlockType, WasmValType, abi_ty, array_ty_bits, classify_ty, - exact_int_backend_ty, function_value_abi_ty, function_value_layout, nominal_ty_bits, - runtime_function_signature, runtime_ty_to_backend_ty, stage_intrinsic_signature, - supported_value_shape, ty_bits, value_shape, -}; -use wasm_encoder::{BlockType, ValType}; - -pub fn backend_ty_value_type(ty: BackendTy) -> Option { - match ty.value_type() { - Some(WasmValType::I32) => Some(ValType::I32), - Some(WasmValType::I64) => Some(ValType::I64), - Some(WasmValType::F32) => Some(ValType::F32), - Some(WasmValType::F64) => Some(ValType::F64), - Some(WasmValType::V128) => Some(ValType::V128), - Some(WasmValType::Ref) => Some(ValType::EXTERNREF), - None => None, - } -} - -pub fn backend_ty_block_type(ty: BackendTy) -> BlockType { - wasm_block_type(ty.block_type()) -} - -pub fn wasm_block_type(block_type: WasmBlockType) -> BlockType { - match block_type { - WasmBlockType::Empty => BlockType::Empty, - WasmBlockType::Result(value_type) => BlockType::Result(wasm_val_type(value_type)), - } -} - -pub fn wasm_val_type(value_type: WasmValType) -> ValType { - match value_type { - WasmValType::I32 => ValType::I32, - WasmValType::I64 => ValType::I64, - WasmValType::F32 => ValType::F32, - WasmValType::F64 => ValType::F64, - WasmValType::V128 => ValType::V128, - WasmValType::Ref => ValType::EXTERNREF, - } -} diff --git a/crates/mitki-backend-wasm/src/api.rs b/crates/mitki-backend-wasm/src/api.rs deleted file mode 100644 index 6d5477f..0000000 --- a/crates/mitki-backend-wasm/src/api.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::sync::Arc; - -use mitki_errors::Diagnostic; -use mitki_inputs::File; -use mitki_lower::item::scope::FunctionLocation; - -use crate::{Backend, BoundaryLegalityValidator, CapabilityValidator}; - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct CompileOptions; - -pub trait ComptimeEvaluator: Send + Sync { - fn eval_comptime_function( - &self, - db: &dyn salsa::Database, - function: FunctionLocation<'_>, - ) -> Result; -} - -#[derive(Default)] -pub struct NoopComptimeEvaluator; - -impl ComptimeEvaluator for NoopComptimeEvaluator { - fn eval_comptime_function( - &self, - _db: &dyn salsa::Database, - _function: FunctionLocation<'_>, - ) -> Result { - Err("comptime evaluation requires the `mitki-comptime-wasm` integration layer".to_owned()) - } -} - -pub struct CompileConfig { - pub options: CompileOptions, - pub comptime_evaluator: Arc, -} - -impl CompileConfig { - pub fn with_options(options: CompileOptions) -> Self { - Self { options, ..Self::default() } - } -} - -impl Default for CompileConfig { - fn default() -> Self { - Self { options: CompileOptions, comptime_evaluator: Arc::new(NoopComptimeEvaluator) } - } -} - -fn format_internal_wasm_validation_error(error: &wasmparser::BinaryReaderError) -> String { - let message = error.to_string(); - message.split_once(" (at offset ").map_or(message.clone(), |(prefix, _)| prefix.to_owned()) -} - -fn validate_final_wasm(bytes: &[u8], range: mitki_errors::TextRange) -> Result<(), Diagnostic> { - wasmparser::Validator::new().validate_all(bytes).map(|_| ()).map_err(|error| { - Diagnostic::error( - format!( - "internal Wasm validation failed: {}", - format_internal_wasm_validation_error(&error) - ), - range, - ) - }) -} - -fn compile_checked_module<'db>( - mut backend: Backend<'db>, - collect_stage_diagnostics: bool, -) -> Result, Vec> { - backend.collect_reachable_program(); - if collect_stage_diagnostics { - backend.collect_stage_diagnostics(); - } - if !backend.diagnostics.is_empty() { - return Err(backend.diagnostics); - } - let legality_diagnostics = BoundaryLegalityValidator::check(&backend); - if !legality_diagnostics.is_empty() { - return Err(legality_diagnostics); - } - if let Err(diagnostic) = CapabilityValidator::check(&backend) { - return Err(vec![diagnostic]); - } - - let bytes = match backend.emit_module() { - Ok(bytes) => bytes, - Err(diagnostic) => return Err(vec![diagnostic]), - }; - validate_final_wasm(&bytes, backend.file_range()).map_err(|diagnostic| vec![diagnostic])?; - Ok(bytes) -} - -pub fn compile_file( - db: &dyn salsa::Database, - file: File, - config: CompileConfig, -) -> Result, Vec> { - let diagnostics = mitki_analysis::check_file(db, file); - if !diagnostics.is_empty() { - return Err(diagnostics.to_owned()); - } - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - if !runtime_diagnostics.is_empty() { - return Err(runtime_diagnostics.to_owned()); - } - - compile_checked_module( - Backend::new_file_with_options(db, file, config.options, config.comptime_evaluator), - false, - ) -} - -pub fn compile_file_with_options( - db: &dyn salsa::Database, - file: File, - options: CompileOptions, -) -> Result, Vec> { - compile_file(db, file, CompileConfig::with_options(options)) -} - -pub fn compile_function( - db: &dyn salsa::Database, - function: FunctionLocation<'_>, - config: CompileConfig, -) -> Result, Vec> { - let diagnostics = mitki_analysis::check_function(db, function); - if !diagnostics.is_empty() { - return Err(diagnostics.to_owned()); - } - let runtime_diagnostics = mitki_analysis::check_runtime_function(db, function); - if !runtime_diagnostics.is_empty() { - return Err(runtime_diagnostics.to_owned()); - } - - compile_checked_module( - Backend::new_stage_with_options( - db, - function.file(db), - function, - config.options, - config.comptime_evaluator, - ), - true, - ) -} - -pub fn compile_function_with_options( - db: &dyn salsa::Database, - function: FunctionLocation<'_>, - options: CompileOptions, -) -> Result, Vec> { - compile_function(db, function, CompileConfig::with_options(options)) -} diff --git a/crates/mitki-backend-wasm/src/backend.rs b/crates/mitki-backend-wasm/src/backend.rs deleted file mode 100644 index 1606f36..0000000 --- a/crates/mitki-backend-wasm/src/backend.rs +++ /dev/null @@ -1,647 +0,0 @@ -#[path = "boundary.rs"] -pub mod boundary; -#[path = "comptime.rs"] -mod comptime; -#[path = "emit/mod.rs"] -mod emit; -#[path = "lowering/mod.rs"] -pub(super) mod lowering; -#[path = "model.rs"] -mod model; -#[path = "obligations.rs"] -mod obligations; -#[path = "planning.rs"] -pub mod planning; -#[path = "reachability.rs"] -mod reachability; -#[path = "reachability_graph.rs"] -mod reachability_graph; -#[path = "registry.rs"] -pub mod registry; -#[path = "storage.rs"] -mod storage; -#[path = "target.rs"] -mod target; -#[path = "validation/mod.rs"] -pub mod validation; - -use std::collections::VecDeque; -use std::sync::Arc; - -pub(crate) use mitki_codegen_core::StageIntrinsic; -use mitki_errors::Diagnostic; -use mitki_hir::hir::{ExprId, Function, NameId, NodeKind, NodeStore, PatId, StmtId, WasmLinkage}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_inputs::{File, PackageId}; -use mitki_lower::HasPackageDecls as _; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{ - BoundaryInstanceKind, Declaration, FunctionLocation, enum_variants, struct_fields, -}; -use mitki_parse::FileParse as _; -use mitki_resolve::{ - BindingId, CompilerIntrinsic, Resolver, RuntimeFunction, SignatureTypeResolver, -}; -use mitki_span::Symbol; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::SyntaxNodePtr; -use mitki_yellow::ast::{HasName as _, Node as _}; -use rustc_hash::{FxHashMap, FxHashSet}; -use salsa::plumbing::AsId as _; -use wasm_encoder::{BlockType, ValType}; - -use self::comptime::ComptimeValueKey; -pub(in crate::backend) use self::lowering::{ - function_kernel, function_legalize, function_ownership, function_wasm_ir, wrapper_mir, -}; -use self::model::*; -pub(in crate::backend) use self::planning as plan; -#[allow(unused_imports)] -use self::storage::{ - Dest, DestBase, FramePlan, FramePlanBuilder, FrameSlot, FrameSlotId, FrameSlotPurpose, - FunctionLayout, FunctionLayoutLookups, LocalPlan, LocalPlanBuilder, LocalPurpose, LocalSlot, - MemAccess, MemAccessKind, ScratchLocalKind, TempSlot, -}; -pub(crate) use self::target::{ - BoundaryTransportProfile, CallableLoweringStrategy, CapabilityMatrix, CompilationMode, - ControlFlowStrategy, ImportProvider, MemoryModelStrategy, PhysicalWasmSignature, - ReferenceRepresentationStrategy, ResultLoweringMode, SignatureStrategy, TargetDecisionSnapshot, - TargetPolicies, TargetProfile, -}; -pub use self::validation::{BoundaryLegalityValidator, CapabilityValidator}; -use crate::abi::{ - AbiTy, BackendTy, FunctionSignature, RefKind, array_ty_bits, nominal_ty_bits, - runtime_function_signature, stage_intrinsic_signature, -}; -use crate::layout::{ - ARC_ALIGN, ARC_HEADER_SIZE, ARC_IMMORTAL_REFCNT, ARRAY_CAPACITY_OFFSET, ARRAY_LEN_OFFSET, - AggregateKind, AggregateLayout, ArrayRuntimeLayout, EnumLayout, FieldLayout, align_to, - layout_fields, symbol_bits, -}; - -pub struct Backend<'db> { - db: &'db dyn salsa::Database, - file: File, - mode: CompilationMode, - stage_root: Option>, - pub(crate) comptime_evaluator: Arc, - target: TargetPolicies, - pub(crate) diagnostics: Vec, - shadow_reachability: Option>, - shadow_obligations: Option>, -} - -#[derive(Clone, Default)] -struct EmissionObligationScratch<'db> { - reachable_arrays: FxHashSet>, - reachable_nominals: FxHashSet>, - used_runtime_functions: FxHashSet, - used_stage_intrinsics: FxHashSet, - used_helpers: FxHashSet, -} - -impl<'db> EmissionObligationScratch<'db> { - fn into_plan(self, backend: &Backend<'db>) -> plan::EmissionObligations<'db> { - let db = backend.db; - let import_provider = backend.import_provider(); - let mut runtime_imports = self.used_runtime_functions.into_iter().collect::>(); - runtime_imports.sort_by_key(|runtime| import_provider.runtime_import(*runtime)); - - let mut stage_intrinsics = self.used_stage_intrinsics.into_iter().collect::>(); - stage_intrinsics - .sort_by_key(|intrinsic| import_provider.stage_intrinsic_import(*intrinsic)); - - let mut helpers = self.used_helpers.into_iter().collect::>(); - helpers.sort_by_key(|helper| helper.name()); - - let mut reachable_arrays = self.reachable_arrays.into_iter().collect::>(); - reachable_arrays.sort_by_key(|ty| { - crate::capability::supported_type_runtime_descriptor(db, *ty) - .map_or_else(|| array_ty_bits(*ty), |descriptor| descriptor.graph_node().0) - }); - - let mut reachable_nominals = self.reachable_nominals.into_iter().collect::>(); - reachable_nominals.sort_by_key(|ty| { - crate::capability::supported_type_runtime_descriptor(db, *ty) - .map_or_else(|| nominal_ty_bits(*ty), |descriptor| descriptor.graph_node().0) - }); - let runtime_import_needs = - runtime_imports.iter().copied().map(plan::RuntimeImportNeed::Runtime).collect(); - let layout_needs = reachable_arrays - .iter() - .copied() - .map(plan::LayoutNeed::Array) - .chain(reachable_nominals.iter().copied().map(plan::LayoutNeed::Nominal)) - .collect(); - - plan::EmissionObligations { - runtime_imports, - stage_intrinsics, - helpers, - reachable_arrays, - reachable_nominals, - runtime_import_needs, - callable_adapters: Vec::new(), - boundary_wrappers: Vec::new(), - canonical_support: Vec::new(), - layout_needs, - } - } - - fn register_nominals_in_ty(&mut self, db: &'db dyn salsa::Database, ty: Ty<'db>) { - match ty.kind(db) { - TyKind::String => { - self.used_runtime_functions.insert(RuntimeFunction::Dealloc); - } - TyKind::Array(item) => { - if !self.reachable_arrays.insert(ty) { - return; - } - self.used_runtime_functions.insert(RuntimeFunction::Alloc); - self.used_runtime_functions.insert(RuntimeFunction::Dealloc); - self.used_helpers.insert(HelperFunction::ArcRetain); - self.used_helpers.insert(HelperFunction::ArcRelease); - self.register_nominals_in_ty(db, *item); - } - TyKind::Struct(struct_ty) => { - if !self.reachable_nominals.insert(ty) { - return; - } - self.used_runtime_functions.insert(RuntimeFunction::Alloc); - self.used_runtime_functions.insert(RuntimeFunction::Dealloc); - self.used_helpers.insert(HelperFunction::ArcRetain); - self.used_helpers.insert(HelperFunction::ArcRelease); - for (_, field_ty) in struct_fields(db, *struct_ty) { - self.register_nominals_in_ty(db, *field_ty); - } - } - TyKind::Enum(enum_ty) => { - if !self.reachable_nominals.insert(ty) { - return; - } - self.used_runtime_functions.insert(RuntimeFunction::Alloc); - self.used_runtime_functions.insert(RuntimeFunction::Dealloc); - self.used_helpers.insert(HelperFunction::ArcRetain); - self.used_helpers.insert(HelperFunction::ArcRelease); - for (_, fields) in enum_variants(db, *enum_ty) { - for field_ty in fields { - self.register_nominals_in_ty(db, *field_ty); - } - } - } - TyKind::Tuple(items) | TyKind::Union(items) | TyKind::Inter(items) => { - for &item in items { - self.register_nominals_in_ty(db, item); - } - } - TyKind::Record(fields) => { - for (_, field_ty) in fields { - self.register_nominals_in_ty(db, *field_ty); - } - } - TyKind::ExternStruct(struct_ty) => { - for (_, field_ty) in struct_fields(db, *struct_ty) { - self.register_nominals_in_ty(db, *field_ty); - } - } - TyKind::Pointer { pointee, .. } => { - self.register_nominals_in_ty(db, *pointee); - } - TyKind::Function { inputs, output } => { - for &input in inputs { - self.register_nominals_in_ty(db, input); - } - self.register_nominals_in_ty(db, *output); - } - _ => {} - } - } -} - -impl<'db> Backend<'db> { - pub fn new_file_with_options( - db: &'db dyn salsa::Database, - file: File, - options: crate::CompileOptions, - comptime_evaluator: Arc, - ) -> Self { - Self::new_file_with_profile( - db, - file, - TargetProfile::wasm_core_v2_m32(), - options, - comptime_evaluator, - ) - } - - pub fn new_stage_with_options( - db: &'db dyn salsa::Database, - file: File, - root: FunctionLocation<'db>, - options: crate::CompileOptions, - comptime_evaluator: Arc, - ) -> Self { - Self::new_stage_with_profile( - db, - file, - root, - TargetProfile::wasm_core_v2_m32(), - options, - comptime_evaluator, - ) - } - - pub(crate) fn new_file_with_profile( - db: &'db dyn salsa::Database, - file: File, - profile: TargetProfile, - _options: crate::CompileOptions, - comptime_evaluator: Arc, - ) -> Self { - Self::new(db, file, CompilationMode::Runtime, None, profile, comptime_evaluator) - } - - pub(crate) fn new_stage_with_profile( - db: &'db dyn salsa::Database, - file: File, - root: FunctionLocation<'db>, - profile: TargetProfile, - _options: crate::CompileOptions, - comptime_evaluator: Arc, - ) -> Self { - Self::new(db, file, CompilationMode::Stage, Some(root), profile, comptime_evaluator) - } - - fn new( - db: &'db dyn salsa::Database, - file: File, - mode: CompilationMode, - stage_root: Option>, - profile: TargetProfile, - comptime_evaluator: Arc, - ) -> Self { - Self { - db, - file, - mode, - stage_root, - comptime_evaluator, - target: TargetPolicies::for_profile(profile), - diagnostics: Vec::new(), - shadow_reachability: None, - shadow_obligations: None, - } - } - - fn boundary_instance_key( - &self, - kind: BoundaryInstanceKind, - instance: &mitki_lower::item::scope::BoundaryInstanceLocation<'db>, - ) -> Option> { - (instance.kind(self.db) == kind).then_some(())?; - let origin = instance.origin(self.db)?; - Some(InstanceKey { location: origin, type_args: instance.type_args(self.db).clone() }) - } - - fn has_declared_boundary_instance( - &self, - kind: BoundaryInstanceKind, - expected: &InstanceKey<'db>, - ) -> bool { - PackageId::new(self.db, self.file).package_decls(self.db).declarations().iter().any( - |declaration| { - let Declaration::BoundaryInstance(instance) = declaration else { - return false; - }; - self.boundary_instance_key(kind, instance) - .is_some_and(|instance| instance == *expected) - }, - ) - } - fn function_return_ty( - &self, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) -> Ty<'db> { - if !function.ret_type().is_zero() { - let signature = location.signature(self.db); - let resolver = SignatureTypeResolver::new(self.db, location, signature); - return resolver - .resolve(signature.ret_type(self.db)) - .unwrap_or_else(|_| Ty::new(self.db, TyKind::Tuple(Vec::new()))); - } - inference - .type_of_node(function.body()) - .unwrap_or_else(|| Ty::new(self.db, TyKind::Tuple(Vec::new()))) - } - - fn specialize_ty(&self, instance: &InstanceKey<'db>, ty: Ty<'db>) -> Ty<'db> { - specialize_ty(self.db, ty, &instance.type_args) - } - - fn set_shadow_state( - &mut self, - reachability: plan::ReachabilityGraph<'db>, - obligations: plan::EmissionObligations<'db>, - ) { - self.shadow_reachability = Some(reachability); - self.shadow_obligations = Some(obligations); - } - - fn specialized_expr_ty( - &self, - instance: &InstanceKey<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - expr: ExprId, - ) -> Option> { - let function = instance.location.hir_function(self.db).function(self.db); - let nodes = function.node_store(); - if nodes.node_kind(expr) == NodeKind::Name - && let Some(name_id) = nodes.as_name(expr) - { - let symbol = nodes.name(name_id); - let mut resolver = Resolver::new(self.db, instance.location); - let guard = resolver.scopes_for_node(expr); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - if let Some(BindingId::Local(binding) | BindingId::Param(binding)) = resolution { - return inference - .type_of_node(binding.into()) - .map(|ty| self.specialize_ty(instance, ty)); - } - } - - inference.type_of_node(expr).map(|ty| self.specialize_ty(instance, ty)) - } - - pub fn file_range(&self) -> mitki_errors::TextRange { - self.file.parse(self.db).syntax_node().trimmed_range() - } - - pub fn diagnostics(&self) -> &[Diagnostic] { - &self.diagnostics - } - - pub(in crate::backend) fn compilation_mode(&self) -> CompilationMode { - self.mode - } - - pub(in crate::backend) fn diagnostic_at_function( - &self, - location: FunctionLocation<'db>, - message: impl Into, - range: mitki_errors::TextRange, - ) -> Diagnostic { - Diagnostic::error(message, range) - .with_file(location.file(self.db).path(self.db).to_string()) - } - - pub(in crate::backend) fn stage_root(&self) -> Option> { - self.stage_root - } - - pub(in crate::backend) fn target_profile(&self) -> TargetProfile { - self.target.profile() - } - - pub(in crate::backend) fn target_policies(&self) -> TargetPolicies { - self.target - } - - pub(in crate::backend) fn import_provider(&self) -> ImportProvider { - self.target.import_provider() - } - - pub(in crate::backend) fn boundary_transport_profile(&self) -> BoundaryTransportProfile { - self.target.boundary_transport() - } - - pub(in crate::backend) fn callable_lowering_strategy(&self) -> CallableLoweringStrategy { - self.target.callable_lowering() - } - - pub(in crate::backend) fn memory_model_strategy(&self) -> MemoryModelStrategy { - self.target.memory_model() - } - - pub(in crate::backend) fn signature_strategy(&self) -> SignatureStrategy { - self.target.signature() - } - - pub(in crate::backend) fn control_flow_strategy(&self) -> ControlFlowStrategy { - self.target.control_flow() - } - - pub(in crate::backend) fn reference_representation_strategy( - &self, - ) -> ReferenceRepresentationStrategy { - self.target.reference_representation() - } - - pub(in crate::backend) fn capability_matrix(&self) -> CapabilityMatrix { - self.target.capability_matrix() - } - - pub(in crate::backend) fn target_decision_snapshot(&self) -> TargetDecisionSnapshot { - self.target.decision_snapshot() - } - - fn function_range(&self, location: FunctionLocation<'db>) -> mitki_errors::TextRange { - SyntaxNodePtr::new(location.source(self.db).syntax()).range - } -} - -fn node_range( - backend: &Backend<'_>, - location: FunctionLocation<'_>, - source_map: &mitki_lower::hir::FunctionSourceMap, - expr: ExprId, -) -> mitki_errors::TextRange { - source_map - .try_node_syntax(expr) - .map_or_else(|| backend.function_range(location), |ptr| ptr.range) -} - -fn ranges_intersect(a: mitki_errors::TextRange, b: mitki_errors::TextRange) -> bool { - a.start() < b.end() && b.start() < a.end() -} - -fn stmt_as_expr(nodes: &NodeStore<'_>, stmt: StmtId) -> Option { - match nodes.node_kind(stmt) { - NodeKind::Name => nodes.as_name(stmt).map(Into::into), - NodeKind::True => nodes.as_true(stmt).map(Into::into), - NodeKind::False => nodes.as_false(stmt).map(Into::into), - NodeKind::Error => nodes.as_error(stmt).map(Into::into), - NodeKind::Int => nodes.as_int(stmt).map(Into::into), - NodeKind::Float => nodes.as_float(stmt).map(Into::into), - NodeKind::String => nodes.as_string(stmt).map(Into::into), - NodeKind::Char => nodes.as_char(stmt).map(Into::into), - NodeKind::Tuple => nodes.as_tuple(stmt).map(Into::into), - NodeKind::Array => nodes.as_array(stmt).map(Into::into), - NodeKind::ArrayRepeat => nodes.as_array_repeat(stmt).map(Into::into), - NodeKind::Call => nodes.as_call(stmt).map(Into::into), - NodeKind::Field => nodes.as_field(stmt).map(Into::into), - NodeKind::Binary => nodes.as_binary(stmt).map(Into::into), - NodeKind::Postfix => nodes.as_postfix(stmt).map(Into::into), - NodeKind::Prefix => nodes.as_prefix(stmt).map(Into::into), - NodeKind::LoopExpr => nodes.as_loop_expr(stmt).map(Into::into), - NodeKind::BreakExpr => nodes.as_break_expr(stmt).map(Into::into), - NodeKind::ContinueExpr => nodes.as_continue_expr(stmt).map(Into::into), - NodeKind::If => nodes.as_if(stmt).map(Into::into), - NodeKind::Match => nodes.as_match(stmt).map(Into::into), - NodeKind::Closure => nodes.as_closure(stmt).map(Into::into), - NodeKind::Block => nodes.as_block(stmt).map(Into::into), - NodeKind::UnsafeBlock => nodes.as_unsafe_block(stmt).map(Into::into), - NodeKind::StructExpr => nodes.as_struct_expr(stmt).map(Into::into), - _ => None, - } -} - -fn specialize_ty<'db>(db: &'db dyn salsa::Database, ty: Ty<'db>, type_args: &[Ty<'db>]) -> Ty<'db> { - match ty.kind(db) { - TyKind::Var(id) => type_args.get(*id as usize).copied().unwrap_or(ty), - TyKind::Array(item) => { - let item = specialize_ty(db, *item, type_args); - Ty::new(db, TyKind::Array(item)) - } - TyKind::Tuple(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Tuple(items)) - } - TyKind::Record(fields) => { - let fields = fields - .iter() - .map(|(name, field_ty)| (*name, specialize_ty(db, *field_ty, type_args))) - .collect(); - Ty::new(db, TyKind::Record(fields)) - } - TyKind::Function { inputs, output } => { - let inputs = inputs.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - let output = specialize_ty(db, *output, type_args); - Ty::new(db, TyKind::Function { inputs, output }) - } - TyKind::Union(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Union(items)) - } - TyKind::Inter(items) => { - let items = items.iter().map(|&item| specialize_ty(db, item, type_args)).collect(); - Ty::new(db, TyKind::Inter(items)) - } - TyKind::Pointer { mutable, pointee } => { - let pointee = specialize_ty(db, *pointee, type_args); - Ty::new(db, TyKind::Pointer { mutable: *mutable, pointee }) - } - _ => ty, - } -} - -fn parse_int_literal(literal: Option>, db: &dyn salsa::Database) -> Result { - let Some(literal) = literal else { - return Err("Wasm backend could not read integer literal".to_owned()); - }; - - let text = literal.text(db).replace('_', ""); - let (radix, digits) = if let Some(rest) = text.strip_prefix("0b") { - (2, rest) - } else if let Some(rest) = text.strip_prefix("0o") { - (8, rest) - } else if let Some(rest) = text.strip_prefix("0x") { - (16, rest) - } else { - (10, text.as_str()) - }; - - i64::from_str_radix(digits, radix) - .map_err(|error| format!("Wasm backend could not parse integer literal `{text}`: {error}")) -} - -fn parse_float_literal( - literal: Option>, - db: &dyn salsa::Database, -) -> Result { - let Some(literal) = literal else { - return Err("Wasm backend could not read float literal".to_owned()); - }; - - let text = literal.text(db).replace('_', ""); - text.parse::() - .map_err(|error| format!("Wasm backend could not parse float literal `{text}`: {error}")) -} - -fn decode_char_literal( - literal: Option>, - db: &dyn salsa::Database, -) -> Result { - let Some(literal) = literal else { - return Err("Wasm backend could not read char literal".to_owned()); - }; - - let text = literal.text(db); - let Some(content) = text.strip_prefix('\'').and_then(|text| text.strip_suffix('\'')) else { - return Err(format!("Wasm backend could not decode char literal `{text}`")); - }; - - let mut chars = content.chars(); - let ch = match chars.next() { - Some('\\') => match chars.next() { - Some('n') => '\n', - Some('r') => '\r', - Some('t') => '\t', - Some('0') => '\0', - Some('\\') => '\\', - Some('"') => '"', - Some('\'') => '\'', - Some(other) => other, - None => return Err(format!("Wasm backend found an incomplete escape in `{text}`")), - }, - Some(ch) => ch, - None => return Err(format!("Wasm backend could not decode empty char literal `{text}`")), - }; - - if chars.next().is_some() { - return Err(format!("Wasm backend only supports single char literals, found `{text}`")); - } - - Ok(ch) -} - -fn decode_string_literal( - literal: Option>, - db: &dyn salsa::Database, -) -> Result, String> { - let Some(literal) = literal else { - return Err("Wasm backend could not read string literal".to_owned()); - }; - - let text = literal.text(db); - let Some(content) = text.strip_prefix('"').and_then(|text| text.strip_suffix('"')) else { - return Err(format!("Wasm backend could not decode string literal `{text}`")); - }; - - let mut decoded = String::new(); - let mut chars = content.chars(); - while let Some(ch) = chars.next() { - if ch != '\\' { - decoded.push(ch); - continue; - } - - let escaped = chars - .next() - .ok_or_else(|| format!("Wasm backend found an incomplete escape in `{text}`"))?; - match escaped { - 'n' => decoded.push('\n'), - 'r' => decoded.push('\r'), - 't' => decoded.push('\t'), - '0' => decoded.push('\0'), - '\\' => decoded.push('\\'), - '"' => decoded.push('"'), - '\'' => decoded.push('\''), - other => decoded.push(other), - } - } - - Ok(decoded.into_bytes()) -} diff --git a/crates/mitki-backend-wasm/src/boundary.rs b/crates/mitki-backend-wasm/src/boundary.rs deleted file mode 100644 index 7b1d295..0000000 --- a/crates/mitki-backend-wasm/src/boundary.rs +++ /dev/null @@ -1,643 +0,0 @@ -use std::collections::BTreeSet; - -use mitki_abi::{ - AbiTypeKind as AbiV2TypeKind, ExecutionDomain, LinkageKind as AbiV2LinkageKind, - SemanticTypeGraph, TransportClass, TypeId, -}; -use mitki_abi_lower::{BoundaryFunctionMetadata, BuiltAbiV2, build_module_abi_v2}; -use mitki_errors::Diagnostic; -use mitki_hir::ty::{Ty, TyKind}; -use rustc_hash::FxHashMap; - -use super::plan::FunctionInstanceId; -use super::{BoundaryTransportPlan, *}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct InternalSig { - pub(in crate::backend) params: Vec, - pub(in crate::backend) results: Vec, -} - -impl InternalSig { - pub(in crate::backend) fn from_function_signature(signature: FunctionSignature) -> Self { - let FunctionSignature { params, result } = signature; - Self { params, results: unit_result_to_vec(result) } - } - - pub(in crate::backend) fn as_function_signature(&self) -> FunctionSignature { - assert!( - self.results.len() <= 1, - "shadow internal signatures only support 0 or 1 result in Step 1", - ); - FunctionSignature { - params: self.params.clone(), - result: self.results.first().cloned().unwrap_or(AbiTy::Scalar(BackendTy::Unit)), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct BoundarySlot<'db> { - pub(in crate::backend) semantic_ty: Ty<'db>, - pub(in crate::backend) runtime_abi: AbiTy, - pub(in crate::backend) transport: BoundaryTransportPlan, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct BoundarySig<'db> { - pub(in crate::backend) internal: InternalSig, - pub(in crate::backend) params: Vec>, - pub(in crate::backend) results: Vec>, -} - -impl<'db> BoundarySig<'db> { - pub(in crate::backend) fn from_boundary_signatures( - signatures: &BoundaryFunctionSignatures<'db>, - ) -> Self { - let params = signatures - .param_tys - .iter() - .copied() - .zip(signatures.param_transport_plans.iter().cloned()) - .zip(signatures.param_runtime_abis.iter().cloned()) - .map(|((semantic_ty, transport), runtime_abi)| BoundarySlot { - semantic_ty, - runtime_abi, - transport, - }) - .collect::>(); - let results = unit_boundary_result_to_vec( - signatures.result_ty, - signatures.result_runtime_abi.clone(), - signatures.result_transport_plan, - ); - Self { - internal: InternalSig::from_function_signature(signatures.internal_signature()), - params, - results, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum WrapperDirection { - ImportThunk, - ExportWrapper, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum TransportOp<'db> { - ReadLane { lane: u32, ty: Ty<'db> }, - NormalizeBool { lane: u32 }, - DecodeCanonical { lane: u32, ty: Ty<'db> }, - EncodeCanonical { lane: u32, ty: Ty<'db> }, - HandleToFunction { lane: u32, ty: Ty<'db> }, - FunctionToHandle { lane: u32, ty: Ty<'db> }, - RetainNestedHandles { ty: Ty<'db> }, - ReleaseCanonicalTemp { lane: u32 }, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct BoundaryWrapperPlan<'db> { - pub(in crate::backend) direction: WrapperDirection, - pub(in crate::backend) signature: BoundarySig<'db>, - pub(in crate::backend) param_ops: Vec>, - pub(in crate::backend) result_ops: Vec>, -} - -impl<'db> BoundaryWrapperPlan<'db> { - fn import(signature: BoundarySig<'db>, db: &'db dyn salsa::Database) -> Self { - let (param_ops, result_ops) = - transport_ops_for_direction(db, WrapperDirection::ImportThunk, &signature); - Self { direction: WrapperDirection::ImportThunk, signature, param_ops, result_ops } - } - - fn export(signature: BoundarySig<'db>, db: &'db dyn salsa::Database) -> Self { - let (param_ops, result_ops) = - transport_ops_for_direction(db, WrapperDirection::ExportWrapper, &signature); - Self { direction: WrapperDirection::ExportWrapper, signature, param_ops, result_ops } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct ImportBoundaryPlan<'db> { - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) module_name: String, - pub(in crate::backend) field_name: String, - pub(in crate::backend) wrapper: BoundaryWrapperPlan<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct ExportBoundaryPlan<'db> { - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) export_name: String, - pub(in crate::backend) wrapper: BoundaryWrapperPlan<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct BoundaryAliasPlan<'db> { - pub(in crate::backend) alias: String, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct BoundaryInstancePlan<'db> { - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) generic_origin_name: Option, - pub(in crate::backend) wasm_module_name: Option, - pub(in crate::backend) wasm_field_name: String, - pub(in crate::backend) domain: ExecutionDomain, - pub(in crate::backend) linkage: AbiV2LinkageKind, - pub(in crate::backend) signature: BoundarySig<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct AbiBoundaryMetadataPlan<'db> { - pub(in crate::backend) functions: Vec>, -} - -impl<'db> AbiBoundaryMetadataPlan<'db> { - pub(in crate::backend) fn build_preview( - &self, - db: &'db dyn salsa::Database, - ) -> Result { - build_module_abi_v2(db, &self.functions) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BoundaryPlan<'db> { - pub(in crate::backend) imports: Vec>, - pub(in crate::backend) exports: Vec>, - pub(in crate::backend) instances: Vec>, - pub(in crate::backend) aliases: Vec>, - pub(in crate::backend) metadata: AbiBoundaryMetadataPlan<'db>, - pub(in crate::backend) instance_indices: FxHashMap, usize>, - pub(in crate::backend) import_indices: FxHashMap, usize>, - pub(in crate::backend) export_indices: FxHashMap, usize>, -} - -impl<'db> BoundaryPlan<'db> {} - -pub(in crate::backend) struct BoundaryPlanner; - -impl BoundaryPlanner { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - reachability: &plan::ReachabilityGraph<'db>, - function_ids: &FxHashMap, FunctionInstanceId>, - ) -> Result, Diagnostic> { - let reachable_functions = &reachability.functions; - let mut imports = Vec::new(); - let mut exports = Vec::new(); - let mut instances = Vec::new(); - let mut aliases = Vec::with_capacity(reachability.exports.len()); - let mut metadata_functions = Vec::new(); - let export_names = reachability - .roots - .iter() - .map(|root| (root.instance.clone(), root.logical_name.clone())) - .collect::>(); - - for instance in reachable_functions { - let function = instance.location.hir_function(backend.db).function(backend.db); - if !matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) { - continue; - } - let inference = instance.location.infer(backend.db); - let source = instance.location.source(backend.db); - let name = source.name().expect("reachable import should have a name"); - let WasmLinkage::Import { module } = function.linkage() else { - continue; - }; - let signatures = backend.boundary_function_signatures(instance, function, inference)?; - let signature = BoundarySig::from_boundary_signatures(&signatures); - let metadata_index = metadata_functions.len(); - let wasm_field_name = mitki_abi::typed_boundary_wasm_name(metadata_index); - let generic_origin_name = - (!instance.type_args.is_empty()).then(|| name.as_str().to_owned()); - let function_id = *function_ids - .get(instance) - .expect("reachable boundary import should have a function id"); - - metadata_functions.push(BoundaryFunctionMetadata { - logical_name: name.as_str().to_owned(), - generic_origin_name: generic_origin_name.clone(), - wasm_module_name: Some(module.text(backend.db).to_owned()), - wasm_field_name: wasm_field_name.clone(), - param_tys: signature.params.iter().map(|slot| slot.semantic_ty).collect(), - result_ty: signatures.result_ty, - type_args: instance.type_args.clone(), - domain: ExecutionDomain::Runtime, - linkage: AbiV2LinkageKind::WasmImport, - }); - - instances.push(BoundaryInstancePlan { - function_id, - instance: instance.clone(), - metadata_index, - logical_name: name.as_str().to_owned(), - generic_origin_name, - wasm_module_name: Some(module.text(backend.db).to_owned()), - wasm_field_name: wasm_field_name.clone(), - domain: ExecutionDomain::Runtime, - linkage: AbiV2LinkageKind::WasmImport, - signature: signature.clone(), - }); - - imports.push(ImportBoundaryPlan { - function_id, - instance: instance.clone(), - metadata_index, - logical_name: name.as_str().to_owned(), - module_name: module.text(backend.db).to_owned(), - field_name: wasm_field_name, - wrapper: BoundaryWrapperPlan::import(signature, backend.db), - }); - } - - for instance in &reachability.exports { - let logical_name = export_names.get(instance).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: reachable export was missing its logical-name root", - backend.function_range(instance.location), - ) - })?; - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let signatures = backend.boundary_function_signatures(instance, function, inference)?; - let signature = BoundarySig::from_boundary_signatures(&signatures); - let emit_start_alias = matches!(function.linkage(), WasmLinkage::ImplicitMainExport) - && signature.params.is_empty() - && signature.results.is_empty(); - let metadata_index = metadata_functions.len(); - let wasm_field_name = mitki_abi::typed_boundary_wasm_name(metadata_index); - let generic_origin_name = - (!instance.type_args.is_empty()).then_some(logical_name.clone()); - let function_id = *function_ids - .get(instance) - .expect("reachable boundary export should have a function id"); - let domain = if backend.is_stage_mode() { - ExecutionDomain::Stage - } else { - ExecutionDomain::Runtime - }; - - metadata_functions.push(BoundaryFunctionMetadata { - logical_name: logical_name.clone(), - generic_origin_name: generic_origin_name.clone(), - wasm_module_name: None, - wasm_field_name: wasm_field_name.clone(), - param_tys: signature.params.iter().map(|slot| slot.semantic_ty).collect(), - result_ty: signatures.result_ty, - type_args: instance.type_args.clone(), - domain, - linkage: AbiV2LinkageKind::WasmExport, - }); - - instances.push(BoundaryInstancePlan { - function_id, - instance: instance.clone(), - metadata_index, - logical_name: logical_name.clone(), - generic_origin_name, - wasm_module_name: None, - wasm_field_name: wasm_field_name.clone(), - domain, - linkage: AbiV2LinkageKind::WasmExport, - signature: signature.clone(), - }); - - exports.push(ExportBoundaryPlan { - function_id, - instance: instance.clone(), - metadata_index, - logical_name: logical_name.clone(), - export_name: wasm_field_name.clone(), - wrapper: BoundaryWrapperPlan::export(signature, backend.db), - }); - - aliases.push(BoundaryAliasPlan { - alias: wasm_field_name, - instance: instance.clone(), - function_id, - metadata_index, - logical_name, - }); - if emit_start_alias { - aliases.push(BoundaryAliasPlan { - alias: "_start".to_owned(), - instance: instance.clone(), - function_id, - metadata_index, - logical_name: "main".to_owned(), - }); - } - } - - let instance_indices = instances - .iter() - .enumerate() - .map(|(index, entry)| (entry.instance.clone(), index)) - .collect::>(); - let import_indices = imports - .iter() - .map(|plan| (plan.instance.clone(), plan.metadata_index)) - .collect::>(); - let export_indices = exports - .iter() - .map(|plan| (plan.instance.clone(), plan.metadata_index)) - .collect::>(); - - Ok(BoundaryPlan { - imports, - exports, - instances, - aliases, - metadata: AbiBoundaryMetadataPlan { functions: metadata_functions }, - instance_indices, - import_indices, - export_indices, - }) - } -} - -impl<'db> Backend<'db> { - pub(super) fn boundary_function_signatures( - &self, - instance: &InstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) -> Result, Diagnostic> { - let (param_tys, result_ty) = - self.function_signature_types(instance, function, inference)?; - let transport_profile = self.boundary_transport_profile(); - let signature_context = transport_profile.boundary_signature_context(); - let param_transport_plans = param_tys - .iter() - .copied() - .map(|ty| { - transport_profile.plan_or_message(self.db, ty, &signature_context).map_err( - |message| Diagnostic::error(message, self.function_range(instance.location)), - ) - }) - .collect::, _>>()?; - let param_runtime_abis = param_tys - .iter() - .copied() - .map(|ty| { - crate::capability::supported_value_abi_or_message(self.db, ty, &signature_context) - .map_err(|message| { - Diagnostic::error(message, self.function_range(instance.location)) - }) - }) - .collect::, _>>()?; - let result_transport_plan = - transport_profile.plan_or_message(self.db, result_ty, &signature_context).map_err( - |message| Diagnostic::error(message, self.function_range(instance.location)), - )?; - let result_runtime_abi = crate::capability::supported_value_abi_or_message( - self.db, - result_ty, - &signature_context, - ) - .map_err(|message| Diagnostic::error(message, self.function_range(instance.location)))?; - Ok(BoundaryFunctionSignatures { - param_tys, - result_ty, - param_transport_plans, - result_transport_plan, - param_runtime_abis, - result_runtime_abi, - }) - } -} - -pub(in crate::backend) fn semantic_type_kind( - graph: &SemanticTypeGraph, - ty: TypeId, -) -> Result<&AbiV2TypeKind, Diagnostic> { - graph.types.get(ty.0 as usize).map(|node| &node.kind).ok_or_else(|| { - Diagnostic::error( - format!("internal error: unknown ABI v2 type `{}`", ty.0), - mitki_errors::TextRange::default(), - ) - }) -} - -fn semantic_type_edges(kind: &AbiV2TypeKind) -> Vec { - match kind { - AbiV2TypeKind::Array { elem } => vec![*elem], - AbiV2TypeKind::Tuple { elems } => elems.clone(), - AbiV2TypeKind::Record { fields } | AbiV2TypeKind::Struct { fields, .. } => { - fields.iter().map(|field| field.ty).collect() - } - AbiV2TypeKind::Enum { variants, .. } => { - variants.iter().flat_map(|variant| variant.fields.iter().copied()).collect() - } - AbiV2TypeKind::Union { members } => members.clone(), - AbiV2TypeKind::Intersection { members, carrier, .. } => { - let mut edges = members.clone(); - edges.push(*carrier); - edges - } - AbiV2TypeKind::Function { params, result, .. } => { - let mut edges = params.clone(); - edges.push(*result); - edges - } - AbiV2TypeKind::Unit - | AbiV2TypeKind::Bool - | AbiV2TypeKind::Int { .. } - | AbiV2TypeKind::Float { .. } - | AbiV2TypeKind::Char - | AbiV2TypeKind::String - | AbiV2TypeKind::Opaque { .. } => Vec::new(), - } -} - -pub(in crate::backend) fn semantic_type_uses_recursive_group( - graph: &SemanticTypeGraph, - root: TypeId, -) -> Result { - let mut stack = vec![root]; - let mut seen = BTreeSet::new(); - while let Some(ty) = stack.pop() { - if !seen.insert(ty) { - continue; - } - let node = graph.types.get(ty.0 as usize).ok_or_else(|| { - Diagnostic::error( - format!("internal error: unknown ABI v2 type `{}`", ty.0), - mitki_errors::TextRange::default(), - ) - })?; - if node.recursive_group.is_some() { - return Ok(true); - } - stack.extend(semantic_type_edges(&node.kind)); - } - Ok(false) -} - -pub(in crate::backend) fn semantic_type_is_immediate( - graph: &SemanticTypeGraph, - ty: TypeId, -) -> Result { - Ok(match semantic_type_kind(graph, ty)? { - AbiV2TypeKind::Unit - | AbiV2TypeKind::Bool - | AbiV2TypeKind::Int { .. } - | AbiV2TypeKind::Float { .. } - | AbiV2TypeKind::Char => true, - AbiV2TypeKind::Enum { variants, .. } => { - variants.iter().all(|variant| variant.fields.is_empty()) - } - AbiV2TypeKind::Function { .. } | AbiV2TypeKind::Opaque { .. } => false, - AbiV2TypeKind::String - | AbiV2TypeKind::Array { .. } - | AbiV2TypeKind::Tuple { .. } - | AbiV2TypeKind::Record { .. } - | AbiV2TypeKind::Struct { .. } - | AbiV2TypeKind::Union { .. } - | AbiV2TypeKind::Intersection { .. } => false, - }) -} - -pub(in crate::backend) fn transport_has_wasm_lane( - graph: &SemanticTypeGraph, - transport: &mitki_abi::TransportRef, -) -> Result { - Ok( - match (transport.transport_class, semantic_type_kind(graph, transport_type_id(transport))?) - { - (TransportClass::Immediate, AbiV2TypeKind::Unit) => false, - (TransportClass::Immediate, AbiV2TypeKind::Tuple { elems }) if elems.is_empty() => { - false - } - _ => true, - }, - ) -} - -pub(in crate::backend) fn transport_type_id(transport: &mitki_abi::TransportRef) -> TypeId { - transport.transport_type.unwrap_or(transport.semantic_type) -} - -fn unit_result_to_vec(result: AbiTy) -> Vec { - match result { - AbiTy::Scalar(BackendTy::Unit) => Vec::new(), - other => vec![other], - } -} - -fn unit_boundary_result_to_vec<'db>( - semantic_ty: Ty<'db>, - runtime_abi: AbiTy, - transport: BoundaryTransportPlan, -) -> Vec> { - if matches!(runtime_abi, AbiTy::Scalar(BackendTy::Unit)) { - Vec::new() - } else { - vec![BoundarySlot { semantic_ty, runtime_abi, transport }] - } -} - -fn boundary_slot_lane_count(slot: &BoundarySlot<'_>) -> u32 { - if matches!(slot.runtime_abi, AbiTy::Scalar(BackendTy::Unit)) { 0 } else { 1 } -} - -fn transport_ops_for_direction<'db>( - db: &'db dyn salsa::Database, - direction: WrapperDirection, - signature: &BoundarySig<'db>, -) -> (Vec>, Vec>) { - let mut param_lane = 0u32; - let param_ops = signature - .params - .iter() - .flat_map(|slot| { - let lane = param_lane; - param_lane += boundary_slot_lane_count(slot); - slot_transport_ops(db, direction, true, lane, slot) - }) - .collect::>(); - - let mut result_lane = 0u32; - let result_ops = signature - .results - .iter() - .flat_map(|slot| { - let lane = result_lane; - result_lane += boundary_slot_lane_count(slot); - slot_transport_ops(db, direction, false, lane, slot) - }) - .collect::>(); - - (param_ops, result_ops) -} - -fn slot_transport_ops<'db>( - db: &'db dyn salsa::Database, - direction: WrapperDirection, - is_param: bool, - lane: u32, - slot: &BoundarySlot<'db>, -) -> Vec> { - let mut ops = Vec::new(); - match slot.transport.transport_class { - TransportClass::Immediate => { - if boundary_slot_lane_count(slot) != 0 { - ops.push(TransportOp::ReadLane { lane, ty: slot.semantic_ty }); - if matches!(slot.semantic_ty.kind(db), TyKind::Bool) { - ops.push(TransportOp::NormalizeBool { lane }); - } - } - } - TransportClass::CanonicalValue => { - let encode = match (direction, is_param) { - (WrapperDirection::ImportThunk, true) => true, - (WrapperDirection::ImportThunk, false) => false, - (WrapperDirection::ExportWrapper, true) => false, - (WrapperDirection::ExportWrapper, false) => true, - }; - if encode { - ops.push(TransportOp::EncodeCanonical { lane, ty: slot.semantic_ty }); - ops.push(TransportOp::RetainNestedHandles { ty: slot.semantic_ty }); - } else { - ops.push(TransportOp::DecodeCanonical { lane, ty: slot.semantic_ty }); - } - ops.push(TransportOp::ReleaseCanonicalTemp { lane }); - } - TransportClass::CapabilityHandle => { - let to_handle = match (direction, is_param) { - (WrapperDirection::ImportThunk, true) => true, - (WrapperDirection::ImportThunk, false) => false, - (WrapperDirection::ExportWrapper, true) => false, - (WrapperDirection::ExportWrapper, false) => true, - }; - if to_handle { - ops.push(TransportOp::FunctionToHandle { lane, ty: slot.semantic_ty }); - } else { - ops.push(TransportOp::HandleToFunction { lane, ty: slot.semantic_ty }); - } - } - } - ops -} diff --git a/crates/mitki-backend-wasm/src/capability.rs b/crates/mitki-backend-wasm/src/capability.rs deleted file mode 100644 index b7181d0..0000000 --- a/crates/mitki-backend-wasm/src/capability.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[allow(unused_imports)] -pub use mitki_codegen_core::capability::{ - BoundaryCapabilityFailure, ValueSupportFailure, ensure_boundary_capability, - supported_array_runtime_layout, supported_array_runtime_layout_or_message, - supported_function_signature, supported_function_signature_or_message, - supported_internal_nominal_payload_layout, - supported_internal_nominal_payload_layout_or_message, supported_type_runtime_descriptor, - supported_value_abi, supported_value_abi_or_message, supports_boundary, -}; diff --git a/crates/mitki-backend-wasm/src/comptime.rs b/crates/mitki-backend-wasm/src/comptime.rs deleted file mode 100644 index c33e05a..0000000 --- a/crates/mitki-backend-wasm/src/comptime.rs +++ /dev/null @@ -1,30 +0,0 @@ -use mitki_hir::hir::ExprId; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::FunctionLocation; -use mitki_typeck::infer::Inferable as _; - -pub(crate) fn classify_comptime_result<'db>( - db: &'db dyn salsa::Database, - function: FunctionLocation<'db>, -) -> Result, String> { - let hir_function = function.hir_function(db).function(db); - let inference = function.infer(db); - let return_ty = inference - .type_of_node(hir_function.body()) - .unwrap_or_else(|| Ty::new(db, TyKind::Tuple(Vec::new()))); - if crate::capability::supports_boundary(db, return_ty) { - Ok(return_ty) - } else { - Err(format!( - "comptime requires the target function to return a runtime-lowerable value, found `{}`", - return_ty.display(db) - )) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) struct ComptimeValueKey<'db> { - pub(crate) location: FunctionLocation<'db>, - pub(crate) expr: ExprId, -} diff --git a/crates/mitki-backend-wasm/src/emit/boundary.rs b/crates/mitki-backend-wasm/src/emit/boundary.rs deleted file mode 100644 index 653b9da..0000000 --- a/crates/mitki-backend-wasm/src/emit/boundary.rs +++ /dev/null @@ -1,4785 +0,0 @@ -use std::collections::BTreeMap; - -use mitki_abi::{ - AbiTypeKind as AbiV2TypeKind, CANONICAL_BLOB_ENCODING_VERSION, - FunctionSignature as AbiV2FunctionSignature, SemanticTypeGraph, TypeId, -}; -use mitki_abi_lower::{ - LoweringMode, MitkiAggregateKindAbi, MitkiAggregateLayoutAbi, MitkiArrayLayoutAbi, - MitkiEnumLayoutAbi, MitkiFunctionAbi, MitkiLoweringAbi, MitkiPointeeAbi, MitkiValueAbi, - MitkiValueKind, -}; -use wasm_encoder::{Function as WasmFunction, Instruction}; - -use super::super::boundary::{semantic_type_is_immediate, semantic_type_kind, transport_type_id}; -use super::super::wrapper_mir::{ - Operand as WrapperOperand, PlaceId, PlaceKind as WrapperPlaceKind, RValue as WrapperRValue, - Region as WrapperRegion, Stmt as WrapperStmt, ValueId, WrapperCallTarget, WrapperHandleField, - WrapperKind, WrapperMirFunction, WrapperMirSignature, WrapperPlaceTy, WrapperScratchKind, -}; -use super::*; -use crate::backend::emit::backend_ir; -use crate::layout::memarg; -#[derive(Clone, Copy)] -enum BoundaryValueSource { - Local(u32), - Memory { base_local: u32, offset: u32 }, -} - -#[derive(Clone, Copy)] -enum BoundaryValueDest { - Local(u32), - Memory { base_local: u32, offset: u32 }, -} - -const CANONICAL_BLOB_FIXED_HEADER_LEN: u32 = 34; -const CANONICAL_BLOB_ROOT_OFFSET: u32 = CANONICAL_BLOB_FIXED_HEADER_LEN; -const CANONICAL_BLOB_NODE_TABLE_OFFSET: u32 = 39; -const CANONICAL_BLOB_TOTAL_LEN_OFFSET: u32 = 22; -const CANONICAL_BLOB_NODE_COUNT_OFFSET: u32 = 26; -const CANONICAL_BLOB_HANDLE_COUNT_OFFSET: u32 = 30; - -const CANONICAL_VALUE_REF_NODE_ID_OFFSET: u32 = 1; -const CANONICAL_NODE_HEADER_LEN: u32 = 21; -const CANONICAL_NODE_KIND_OFFSET: u32 = 4; -const CANONICAL_NODE_AUX0_OFFSET: u32 = 5; -const CANONICAL_NODE_AUX1_OFFSET: u32 = 9; -const CANONICAL_NODE_CHILD_COUNT_OFFSET: u32 = 13; -const CANONICAL_NODE_PAYLOAD_LEN_OFFSET: u32 = 17; -const CANONICAL_NODE_CHILDREN_OFFSET: u32 = 21; -const CANONICAL_ARRAY_VALUES_SENTINEL: u32 = u32::MAX; -const CANONICAL_BLOB_ALIGN: u32 = 4; - -const CANONICAL_INLINE_UNIT_SIZE: u32 = 2; -const CANONICAL_INLINE_BOOL_SIZE: u32 = 3; -const CANONICAL_INLINE_INT_SIZE: u32 = 13; -const CANONICAL_INLINE_FLOAT_SIZE: u32 = 12; -const CANONICAL_INLINE_CHAR_SIZE: u32 = 6; -const CANONICAL_INLINE_ENUM_TAG_SIZE: u32 = 10; -const CANONICAL_NODE_REF_SIZE: u32 = 5; -const CANONICAL_HANDLE_TABLE_LEN_SIZE: u32 = 4; -const ABI_V2_HANDLE_KIND_OFFSET: u32 = 4; -const ABI_V2_HANDLE_FIELD0_OFFSET: u32 = 0; -const ABI_V2_HANDLE_FIELD1_OFFSET: u32 = 4; -const ABI_V2_HANDLE_PAYLOAD_SIZE: u32 = 8; -const ABI_V2_HANDLE_TOTAL_SIZE: u32 = ARC_HEADER_SIZE + ABI_V2_HANDLE_PAYLOAD_SIZE; -const ABI_V2_HANDLE_KIND_OPAQUE: i32 = 0; -const ABI_V2_HANDLE_KIND_FUNCTION: i32 = 1; -#[derive(Clone, Copy)] -struct CanonicalWrapperLocals { - node_offset: u32, - cursor: u32, - index: u32, - len: u32, - count: u32, - bytes: u32, - base_id: u32, - temp_ptr: u32, - temp_ptr_aux: u32, - child_count: u32, - child_bytes: u32, - handle_count: u32, - handle_index: u32, - handle_cursor: u32, - f64_temp: u32, -} - -fn value_abi_is_recursive_placeholder(abi: &MitkiValueAbi) -> bool { - matches!(abi.kind, MitkiValueKind::Array | MitkiValueKind::Struct | MitkiValueKind::Enum) - && abi.pointee.is_none() -} - -fn resolved_semantic_value_abi<'a>( - value_abis: &'a BTreeMap, - semantic_type: TypeId, - fallback: &'a MitkiValueAbi, -) -> &'a MitkiValueAbi { - if value_abi_is_recursive_placeholder(fallback) { - value_abis.get(&semantic_type).unwrap_or(fallback) - } else { - fallback - } -} - -fn live_intersection_members( - semantic_graph: &SemanticTypeGraph, - semantic_type: TypeId, -) -> Result<(TypeId, Vec), Diagnostic> { - let AbiV2TypeKind::Intersection { carrier, facet_plan, .. } = - semantic_type_kind(semantic_graph, semantic_type)? - else { - return Err(Diagnostic::error( - "internal error: expected intersection semantic type", - mitki_errors::TextRange::default(), - )); - }; - let members = facet_plan - .and_then(|plan_id| semantic_graph.facet_plans.get(plan_id.0 as usize)) - .map(|plan| { - plan.entries - .iter() - .filter(|entry| entry.kind != mitki_abi::FacetPlanEntryKind::Erased) - .map(|entry| entry.member) - .collect::>() - }) - .unwrap_or_default(); - Ok((*carrier, members)) -} - -fn canonical_semantic_leaf_type( - semantic_graph: &SemanticTypeGraph, - semantic_type: TypeId, -) -> Result { - if matches!( - semantic_type_kind(semantic_graph, semantic_type)?, - AbiV2TypeKind::Intersection { .. } - ) { - let (carrier, live_facets) = live_intersection_members(semantic_graph, semantic_type)?; - if live_facets.is_empty() { - return canonical_semantic_leaf_type(semantic_graph, carrier); - } - } - Ok(semantic_type) -} - -fn canonical_value_uses_handle_slot( - semantic_graph: &SemanticTypeGraph, - semantic_type: TypeId, -) -> Result { - Ok(matches!( - semantic_type_kind( - semantic_graph, - canonical_semantic_leaf_type(semantic_graph, semantic_type)? - )?, - AbiV2TypeKind::Function { .. } | AbiV2TypeKind::Opaque { .. } - )) -} - -fn internal_value_source(abi: &AbiTy, local: u32) -> BoundaryValueSource { - match abi { - AbiTy::Aggregate(_) => BoundaryValueSource::Memory { base_local: local, offset: 0 }, - AbiTy::Scalar(_) => BoundaryValueSource::Local(local), - } -} - -fn inline_scalar_ref_size(kind: &AbiV2TypeKind) -> u32 { - match kind { - AbiV2TypeKind::Unit => CANONICAL_INLINE_UNIT_SIZE, - AbiV2TypeKind::Bool => CANONICAL_INLINE_BOOL_SIZE, - AbiV2TypeKind::Int { .. } => CANONICAL_INLINE_INT_SIZE, - AbiV2TypeKind::Float { .. } => CANONICAL_INLINE_FLOAT_SIZE, - AbiV2TypeKind::Char => CANONICAL_INLINE_CHAR_SIZE, - AbiV2TypeKind::Enum { .. } => CANONICAL_INLINE_ENUM_TAG_SIZE, - _ => CANONICAL_NODE_REF_SIZE, - } -} - -fn aggregate_layout_for_mode( - abi: &MitkiValueAbi, - mode: LoweringMode, -) -> Result<&MitkiAggregateLayoutAbi, Diagnostic> { - match abi.lowering(mode) { - MitkiLoweringAbi::Aggregate(layout) => Ok(layout), - MitkiLoweringAbi::Pointer => match abi.pointee.as_ref() { - Some(MitkiPointeeAbi::Aggregate(layout)) => Ok(layout), - _ => Err(Diagnostic::error( - "internal error: aggregate-lowered ABI v2 value is missing pointee metadata", - mitki_errors::TextRange::default(), - )), - }, - _ => Err(Diagnostic::error( - "internal error: expected aggregate ABI v2 lowering", - mitki_errors::TextRange::default(), - )), - } -} - -fn enum_layout_for_mode( - abi: &MitkiValueAbi, - mode: LoweringMode, -) -> Result<&MitkiEnumLayoutAbi, Diagnostic> { - let layout = aggregate_layout_for_mode(abi, mode)?; - match &layout.kind { - MitkiAggregateKindAbi::Enum(layout) => Ok(layout), - MitkiAggregateKindAbi::Fields(_) => Err(Diagnostic::error( - "internal error: expected enum layout during ABI v2 lowering", - mitki_errors::TextRange::default(), - )), - } -} - -fn array_layout_from_abi(abi: &MitkiValueAbi) -> Result<&MitkiArrayLayoutAbi, Diagnostic> { - match abi.pointee.as_ref() { - Some(MitkiPointeeAbi::Array(layout)) => Ok(layout), - _ => Err(Diagnostic::error( - "internal error: array ABI v2 lowering is missing runtime array metadata", - mitki_errors::TextRange::default(), - )), - } -} - -fn union_runtime_member_order<'a>( - abi: &'a MitkiValueAbi, - semantic_members: &'a [TypeId], -) -> &'a [TypeId] { - abi.runtime_member_order.as_deref().unwrap_or(semantic_members) -} - -fn union_runtime_variant_index( - abi: &MitkiValueAbi, - semantic_members: &[TypeId], - semantic_member_ty: TypeId, -) -> Result { - union_runtime_member_order(abi, semantic_members) - .iter() - .position(|&runtime_member_ty| runtime_member_ty == semantic_member_ty) - .ok_or_else(|| { - Diagnostic::error( - format!( - "internal error: union member type `{}` is missing from the runtime ABI arm \ - order", - semantic_member_ty.0 - ), - mitki_errors::TextRange::default(), - ) - }) -} - -fn packed_scalar_width(abi: &MitkiValueAbi) -> Option { - match abi.kind { - MitkiValueKind::Bool => Some(1), - MitkiValueKind::Int | MitkiValueKind::Char => Some(4), - MitkiValueKind::Float => Some(8), - _ => None, - } -} - -fn packed_scalar_kind_tag(abi: &MitkiValueAbi) -> Option { - match abi.kind { - MitkiValueKind::Bool => Some(0), - MitkiValueKind::Int => Some(1), - MitkiValueKind::Float => Some(4), - MitkiValueKind::Char => Some(5), - _ => None, - } -} - -fn emit_enum_variant_index_to_tag( - function: &mut WasmFunction, - enum_layout: &MitkiEnumLayoutAbi, - variant_index_local: u32, -) { - for (index, variant) in enum_layout.variants.iter().enumerate().rev() { - function.instruction(&Instruction::LocalGet(variant_index_local)); - function.instruction(&Instruction::I32Const(index as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::Else); - } - function.instruction(&Instruction::I32Const(0)); - for _ in 0..enum_layout.variants.len() { - function.instruction(&Instruction::End); - } -} - -fn emit_enum_tag_to_variant_index( - function: &mut WasmFunction, - enum_layout: &MitkiEnumLayoutAbi, - tag_local: u32, -) { - for (index, variant) in enum_layout.variants.iter().enumerate().rev() { - function.instruction(&Instruction::LocalGet(tag_local)); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(index as i32)); - function.instruction(&Instruction::Else); - } - function.instruction(&Instruction::I32Const(0)); - for _ in 0..enum_layout.variants.len() { - function.instruction(&Instruction::End); - } -} - -fn emit_advance_value_ref_cursor( - function: &mut WasmFunction, - cursor_local: u32, - tag_local: u32, - aux_local: u32, -) { - emit_local_get_and_load8(function, cursor_local, 0); - function.instruction(&Instruction::LocalSet(tag_local)); - function.instruction(&Instruction::LocalGet(tag_local)); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::I32Const(CANONICAL_NODE_REF_SIZE as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); - function.instruction(&Instruction::Else); - emit_local_get_and_load8(function, cursor_local, 1); - function.instruction(&Instruction::LocalSet(aux_local)); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalGet(aux_local)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_UNIT_SIZE as i32)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(aux_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_BOOL_SIZE as i32)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(aux_local)); - function.instruction(&Instruction::I32Const(2)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_INT_SIZE as i32)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(aux_local)); - function.instruction(&Instruction::I32Const(3)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_FLOAT_SIZE as i32)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(aux_local)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_CHAR_SIZE as i32)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::I32Const(CANONICAL_INLINE_ENUM_TAG_SIZE as i32)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); - function.instruction(&Instruction::End); -} - -fn emit_advance_node_cursor( - function: &mut WasmFunction, - cursor_local: u32, - locals: CanonicalWrapperLocals, -) { - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::I32Const(CANONICAL_NODE_CHILDREN_OFFSET as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - - emit_local_get_and_load32(function, cursor_local, CANONICAL_NODE_CHILD_COUNT_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_advance_value_ref_cursor(function, locals.temp_ptr, locals.count, locals.bytes); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - - emit_local_get_and_load32(function, cursor_local, CANONICAL_NODE_PAYLOAD_LEN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.bytes)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); -} - -fn emit_find_node_offset( - function: &mut WasmFunction, - blob_local: u32, - node_id_local: u32, - offset_local: u32, - locals: CanonicalWrapperLocals, -) { - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_NODE_TABLE_OFFSET); - function.instruction(&Instruction::LocalSet(offset_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(node_id_local)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_advance_node_cursor(function, offset_local, locals); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); -} - -fn emit_find_handle_table_offset( - function: &mut WasmFunction, - blob_local: u32, - offset_local: u32, - locals: CanonicalWrapperLocals, -) { - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_NODE_TABLE_OFFSET); - function.instruction(&Instruction::LocalSet(offset_local)); - emit_local_get_and_load32(function, blob_local, CANONICAL_BLOB_NODE_COUNT_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_advance_node_cursor(function, offset_local, locals); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); -} - -fn emit_find_handle_slot_offset( - function: &mut WasmFunction, - blob_local: u32, - slot_id_local: u32, - offset_local: u32, - locals: CanonicalWrapperLocals, -) { - emit_find_handle_table_offset(function, blob_local, offset_local, locals); - emit_local_plus_offset(function, offset_local, CANONICAL_HANDLE_TABLE_LEN_SIZE); - function.instruction(&Instruction::LocalSet(offset_local)); - function.instruction(&Instruction::LocalGet(offset_local)); - function.instruction(&Instruction::LocalGet(slot_id_local)); - function.instruction(&Instruction::I32Const(ABI_V2_HANDLE_PAYLOAD_SIZE as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(offset_local)); -} - -#[allow(clippy::too_many_arguments)] -fn emit_decode_immediate_value_ref( - function: &mut WasmFunction, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - ref_local: u32, - dest: BoundaryValueDest, - runtime_indices: &FxHashMap, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - match semantic_type_kind(semantic_graph, semantic_type)? { - AbiV2TypeKind::Unit => Ok(()), - AbiV2TypeKind::Bool => { - emit_local_get_and_load8(function, ref_local, 2); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - AbiV2TypeKind::Int { .. } => { - emit_local_get_and_load32(function, ref_local, 5); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - AbiV2TypeKind::Float { .. } => { - emit_local_get_and_load_f64(function, ref_local, 4); - emit_store_f64_to_dest(function, dest, locals.f64_temp); - Ok(()) - } - AbiV2TypeKind::Char => { - emit_local_get_and_load32(function, ref_local, 2); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - AbiV2TypeKind::Enum { .. } => { - let enum_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, ref_local, 6); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - match abi.lowering(mode) { - MitkiLoweringAbi::Aggregate(layout) => { - let _ = layout; - emit_enum_variant_index_to_tag(function, enum_layout, locals.temp_ptr); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - MitkiLoweringAbi::Pointer => { - let layout = aggregate_layout_for_mode(abi, mode)?; - emit_alloc_nominal_payload_to_local( - function, - runtime_indices, - layout, - locals.temp_ptr_aux, - locals.base_id, - )?; - emit_enum_variant_index_to_tag(function, enum_layout, locals.temp_ptr); - emit_store32_at_local(function, locals.base_id, 0, locals.count); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - MitkiLoweringAbi::Unit | MitkiLoweringAbi::I32 | MitkiLoweringAbi::F64 => { - Err(Diagnostic::error( - "internal error: enum immediate canonical lowering expected aggregate or \ - pointer destination", - mitki_errors::TextRange::default(), - )) - } - } - } - other => Err(Diagnostic::error( - format!("internal error: unexpected immediate ABI v2 semantic type `{other:?}`"), - mitki_errors::TextRange::default(), - )), - } -} - -fn emit_decode_string_node( - function: &mut WasmFunction, - node_offset_local: u32, - dest: BoundaryValueDest, - runtime_indices: &FxHashMap, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_local_get_and_load32(function, node_offset_local, CANONICAL_NODE_PAYLOAD_LEN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + 4) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.bytes)); - emit_dynamic_alloc_to_local( - function, - runtime_indices, - locals.bytes, - ARC_ALIGN, - locals.temp_ptr, - )?; - emit_local_plus_offset(function, locals.temp_ptr, 0); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalGet(locals.bytes)); - function.instruction(&Instruction::MemoryFill(0)); - emit_write_arc_header(function, locals.temp_ptr); - - emit_local_plus_offset(function, locals.temp_ptr, ARC_HEADER_SIZE); - function.instruction(&Instruction::LocalSet(locals.base_id)); - emit_local_plus_offset(function, locals.base_id, 0); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); - - emit_local_plus_offset(function, locals.base_id, 4); - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn emit_decode_canonical_value_ref( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - blob_local: u32, - ref_local: u32, - dest: BoundaryValueDest, - runtime_indices: &FxHashMap, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let abi = resolved_semantic_value_abi(value_abis, semantic_type, abi); - if semantic_type_is_immediate(semantic_graph, semantic_type)? { - return emit_decode_immediate_value_ref( - function, - abi, - semantic_type, - semantic_graph, - mode, - ref_local, - dest, - runtime_indices, - locals, - ); - } - - if matches!( - semantic_type_kind(semantic_graph, semantic_type)?, - AbiV2TypeKind::Intersection { .. } - ) { - let (carrier, live_facets) = live_intersection_members(semantic_graph, semantic_type)?; - if live_facets.is_empty() { - return emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - resolved_semantic_value_abi(value_abis, carrier, abi), - carrier, - semantic_graph, - mode, - blob_local, - ref_local, - dest, - runtime_indices, - locals, - ); - } - } - - if matches!( - semantic_type_kind(semantic_graph, semantic_type)?, - AbiV2TypeKind::Function { .. } | AbiV2TypeKind::Opaque { .. } - ) { - emit_local_get_and_load32(function, ref_local, CANONICAL_VALUE_REF_NODE_ID_OFFSET); - function.instruction(&Instruction::LocalSet(locals.base_id)); - emit_find_handle_slot_offset( - function, - blob_local, - locals.base_id, - locals.node_offset, - locals, - ); - emit_local_get_and_load32(function, locals.node_offset, 4); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - return emit_unwrap_boundary_handle_slot_value( - function, - helper_indices, - abi, - locals.temp_ptr_aux, - dest, - locals, - ); - } - - emit_local_get_and_load32(function, ref_local, CANONICAL_VALUE_REF_NODE_ID_OFFSET); - function.instruction(&Instruction::LocalSet(locals.base_id)); - emit_find_node_offset(function, blob_local, locals.base_id, locals.node_offset, locals); - emit_decode_canonical_node( - function, - helper_indices, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - blob_local, - locals.node_offset, - dest, - runtime_indices, - locals, - ) -} - -#[allow(clippy::too_many_arguments)] -fn emit_decode_canonical_node( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - blob_local: u32, - node_offset_local: u32, - dest: BoundaryValueDest, - runtime_indices: &FxHashMap, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let abi = resolved_semantic_value_abi(value_abis, semantic_type, abi); - match semantic_type_kind(semantic_graph, semantic_type)? { - AbiV2TypeKind::String => { - emit_decode_string_node(function, node_offset_local, dest, runtime_indices, locals) - } - AbiV2TypeKind::Array { elem } => { - let layout = array_layout_from_abi(abi)?; - let packed = semantic_type_is_immediate(semantic_graph, *elem)? - && packed_scalar_width(&layout.item).is_some() - && packed_scalar_kind_tag(&layout.item).is_some(); - emit_local_get_and_load32(function, node_offset_local, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - emit_alloc_runtime_array_to_local( - function, - runtime_indices, - layout, - locals.len, - locals.temp_ptr, - locals.temp_ptr_aux, - )?; - emit_local_get_and_load32(function, node_offset_local, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - if packed { - function.instruction(&Instruction::LocalGet(locals.count)); - function - .instruction(&Instruction::I32Const(CANONICAL_ARRAY_VALUES_SENTINEL as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - } - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr_aux, - layout, - locals.index, - locals.base_id, - ); - emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - blob_local, - locals.cursor, - BoundaryValueDest::Memory { base_local: locals.base_id, offset: 0 }, - runtime_indices, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_advance_value_ref_cursor(function, locals.cursor, locals.count, locals.bytes); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - if packed { - function.instruction(&Instruction::Else); - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - match layout.item.kind { - MitkiValueKind::Bool => { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr_aux, - layout, - locals.index, - locals.base_id, - ); - emit_local_get_and_load8(function, locals.cursor, 0); - emit_store32_at_local(function, locals.base_id, 0, locals.count); - emit_local_plus_offset(function, locals.cursor, 1); - function.instruction(&Instruction::LocalSet(locals.cursor)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - } - MitkiValueKind::Int | MitkiValueKind::Char => { - emit_local_plus_offset(function, locals.temp_ptr_aux, layout.data_offset); - function.instruction(&Instruction::LocalGet(locals.cursor)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - } - MitkiValueKind::Float => { - emit_local_plus_offset(function, locals.temp_ptr_aux, layout.data_offset); - function.instruction(&Instruction::LocalGet(locals.cursor)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(8)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - } - _ => unreachable!("packed arrays only support scalar element kinds"), - } - function.instruction(&Instruction::End); - } - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - AbiV2TypeKind::Tuple { elems } => { - let base_local = emit_materialize_decode_dest_base( - function, - abi, - mode, - dest, - runtime_indices, - locals, - )?; - let layout = aggregate_layout_for_mode(abi, mode)?; - let MitkiAggregateKindAbi::Fields(fields) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: tuple ABI v2 lowering expected field layout", - mitki_errors::TextRange::default(), - )); - }; - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - for (field, &field_ty) in fields.iter().zip(elems.iter()) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - blob_local, - locals.cursor, - BoundaryValueDest::Memory { base_local, offset: field.offset }, - runtime_indices, - locals, - )?; - function.instruction(&Instruction::LocalSet(base_local)); - emit_advance_value_ref_cursor(function, locals.cursor, locals.count, locals.bytes); - } - if matches!(abi.lowering(mode), MitkiLoweringAbi::Pointer) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_store_i32_to_dest(function, dest, locals.count); - } - Ok(()) - } - AbiV2TypeKind::Record { fields: semantic_fields } - | AbiV2TypeKind::Struct { fields: semantic_fields, .. } => { - let base_local = emit_materialize_decode_dest_base( - function, - abi, - mode, - dest, - runtime_indices, - locals, - )?; - let layout = aggregate_layout_for_mode(abi, mode)?; - let MitkiAggregateKindAbi::Fields(fields) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: aggregate ABI v2 lowering expected field layout", - mitki_errors::TextRange::default(), - )); - }; - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - for (field, semantic_field) in fields.iter().zip(semantic_fields.iter()) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - &field.value, - semantic_field.ty, - semantic_graph, - mode, - blob_local, - locals.cursor, - BoundaryValueDest::Memory { base_local, offset: field.offset }, - runtime_indices, - locals, - )?; - function.instruction(&Instruction::LocalSet(base_local)); - emit_advance_value_ref_cursor(function, locals.cursor, locals.count, locals.bytes); - } - if matches!(abi.lowering(mode), MitkiLoweringAbi::Pointer) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_store_i32_to_dest(function, dest, locals.count); - } - Ok(()) - } - AbiV2TypeKind::Enum { variants, .. } => { - let base_local = emit_materialize_decode_dest_base( - function, - abi, - mode, - dest, - runtime_indices, - locals, - )?; - let enum_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, node_offset_local, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - emit_enum_variant_index_to_tag(function, enum_layout, locals.len); - emit_store32_at_local(function, base_local, 0, locals.count); - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - for (index, variant) in variants.iter().enumerate().rev() { - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(index as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - let layout_variant = &enum_layout.variants[index]; - for (field, &field_ty) in layout_variant.fields.iter().zip(variant.fields.iter()) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - blob_local, - locals.cursor, - BoundaryValueDest::Memory { base_local, offset: field.offset }, - runtime_indices, - locals, - )?; - function.instruction(&Instruction::LocalSet(base_local)); - emit_advance_value_ref_cursor( - function, - locals.cursor, - locals.count, - locals.bytes, - ); - } - function.instruction(&Instruction::End); - } - if matches!(abi.lowering(mode), MitkiLoweringAbi::Pointer) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_store_i32_to_dest(function, dest, locals.count); - } - Ok(()) - } - AbiV2TypeKind::Union { members } => { - let base_local = emit_materialize_decode_dest_base( - function, - abi, - mode, - dest, - runtime_indices, - locals, - )?; - let union_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, node_offset_local, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - emit_local_plus_offset(function, node_offset_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - for (arm_index, &member_ty) in members.iter().enumerate().rev() { - let runtime_variant_index = union_runtime_variant_index(abi, members, member_ty)?; - let layout_variant = &union_layout.variants[runtime_variant_index]; - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(arm_index as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(layout_variant.tag)); - emit_store32_at_local(function, base_local, 0, locals.count); - let field = layout_variant.fields.first().ok_or_else(|| { - Diagnostic::error( - "internal error: union ABI v2 lowering expected a payload field", - mitki_errors::TextRange::default(), - ) - })?; - emit_decode_canonical_value_ref( - function, - helper_indices, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - blob_local, - locals.cursor, - BoundaryValueDest::Memory { base_local, offset: field.offset }, - runtime_indices, - locals, - )?; - function.instruction(&Instruction::End); - } - if matches!(abi.lowering(mode), MitkiLoweringAbi::Pointer) { - function.instruction(&Instruction::LocalGet(base_local)); - emit_store_i32_to_dest(function, dest, locals.count); - } - Ok(()) - } - AbiV2TypeKind::Unit - | AbiV2TypeKind::Bool - | AbiV2TypeKind::Int { .. } - | AbiV2TypeKind::Float { .. } - | AbiV2TypeKind::Char - | AbiV2TypeKind::Function { .. } - | AbiV2TypeKind::Opaque { .. } - | AbiV2TypeKind::Intersection { .. } => Err(Diagnostic::error( - "ABI v2 baseline guest lowering does not support this canonical node kind yet", - mitki_errors::TextRange::default(), - )), - } -} - -#[allow(clippy::too_many_arguments)] -fn emit_measure_canonical_value_ref( - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - out_count_local: u32, - out_bytes_local: u32, - out_handle_count_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let abi = resolved_semantic_value_abi(value_abis, semantic_type, abi); - let kind = semantic_type_kind(semantic_graph, semantic_type)?; - if semantic_type_is_immediate(semantic_graph, semantic_type)? { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const(inline_scalar_ref_size(kind) as i32)); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - return Ok(()); - } - - if matches!(kind, AbiV2TypeKind::Intersection { .. }) { - let (carrier, live_facets) = live_intersection_members(semantic_graph, semantic_type)?; - if live_facets.is_empty() { - return emit_measure_canonical_value_ref( - function, - value_abis, - resolved_semantic_value_abi(value_abis, carrier, abi), - carrier, - semantic_graph, - mode, - source, - out_count_local, - out_bytes_local, - out_handle_count_local, - locals, - ); - } - } - - if matches!(kind, AbiV2TypeKind::Function { .. } | AbiV2TypeKind::Opaque { .. }) { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const(CANONICAL_NODE_REF_SIZE as i32)); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - return Ok(()); - } - - match kind { - AbiV2TypeKind::String => { - emit_load_pointer_from_source(function, source, locals.temp_ptr); - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - Ok(()) - } - AbiV2TypeKind::Array { elem } => { - let layout = array_layout_from_abi(abi)?; - emit_load_pointer_from_source(function, source, locals.temp_ptr); - emit_local_get_and_load32(function, locals.temp_ptr, ARRAY_LEN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - if semantic_type_is_immediate(semantic_graph, *elem)? - && packed_scalar_width(&layout.item).is_some() - && packed_scalar_kind_tag(&layout.item).is_some() - { - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const( - packed_scalar_width(&layout.item).expect("packed width") as i32, - )); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - return Ok(()); - } - - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_bytes_local)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr, - layout, - locals.index, - locals.base_id, - ); - emit_measure_canonical_value_ref( - function, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.base_id, offset: 0 }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_handle_count_local)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - Ok(()) - } - AbiV2TypeKind::Tuple { elems } => { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let layout = aggregate_layout_for_mode(abi, mode)?; - let MitkiAggregateKindAbi::Fields(fields) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: tuple ABI v2 measurement expected fields layout", - mitki_errors::TextRange::default(), - )); - }; - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - for (field, &field_ty) in fields.iter().zip(elems.iter()) { - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_bytes_local)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { - base_local: locals.temp_ptr, - offset: field.offset, - }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_handle_count_local)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - } - Ok(()) - } - AbiV2TypeKind::Record { fields: semantic_fields } - | AbiV2TypeKind::Struct { fields: semantic_fields, .. } => { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let layout = aggregate_layout_for_mode(abi, mode)?; - let MitkiAggregateKindAbi::Fields(fields) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: aggregate ABI v2 measurement expected fields layout", - mitki_errors::TextRange::default(), - )); - }; - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - for (field, semantic_field) in fields.iter().zip(semantic_fields.iter()) { - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_bytes_local)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - semantic_field.ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { - base_local: locals.temp_ptr, - offset: field.offset, - }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_handle_count_local)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - } - Ok(()) - } - AbiV2TypeKind::Enum { variants, .. } => { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let enum_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - for (variant_index, variant) in variants.iter().enumerate().rev() { - let layout_variant = &enum_layout.variants[variant_index]; - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(layout_variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - for (field, &field_ty) in layout_variant.fields.iter().zip(variant.fields.iter()) { - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_bytes_local)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { - base_local: locals.temp_ptr, - offset: field.offset, - }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_handle_count_local)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - } - function.instruction(&Instruction::End); - } - Ok(()) - } - AbiV2TypeKind::Union { members } => { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let union_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::I32Const( - (CANONICAL_NODE_REF_SIZE + CANONICAL_NODE_HEADER_LEN) as i32, - )); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - for &member_ty in members.iter().rev() { - let runtime_variant_index = union_runtime_variant_index(abi, members, member_ty)?; - let layout_variant = &union_layout.variants[runtime_variant_index]; - let field = layout_variant.fields.first().ok_or_else(|| { - Diagnostic::error( - "internal error: union ABI v2 measurement expected a payload field", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(layout_variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_bytes_local)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { - base_local: locals.temp_ptr, - offset: field.offset, - }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_bytes)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_bytes_local)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_count_local)); - function.instruction(&Instruction::LocalGet(out_handle_count_local)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(out_handle_count_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::End); - } - Ok(()) - } - AbiV2TypeKind::Unit - | AbiV2TypeKind::Bool - | AbiV2TypeKind::Int { .. } - | AbiV2TypeKind::Float { .. } - | AbiV2TypeKind::Char - | AbiV2TypeKind::Function { .. } - | AbiV2TypeKind::Opaque { .. } - | AbiV2TypeKind::Intersection { .. } => Err(Diagnostic::error( - "ABI v2 baseline guest lowering does not support this canonical measurement kind yet", - mitki_errors::TextRange::default(), - )), - } -} - -fn emit_write_node_ref_at_cursor( - function: &mut WasmFunction, - cursor_local: u32, - node_id_local: u32, -) { - emit_local_plus_offset(function, cursor_local, 0); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_VALUE_REF_NODE_ID_OFFSET); - function.instruction(&Instruction::LocalGet(node_id_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_REF_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); -} - -fn emit_write_handle_ref_at_cursor( - function: &mut WasmFunction, - cursor_local: u32, - handle_slot_local: u32, -) { - emit_local_plus_offset(function, cursor_local, 0); - function.instruction(&Instruction::I32Const(2)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_VALUE_REF_NODE_ID_OFFSET); - function.instruction(&Instruction::LocalGet(handle_slot_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_REF_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); -} - -#[allow(clippy::too_many_arguments)] -fn emit_write_non_immediate_canonical_value_ref( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - _mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - child_count_local: u32, - base_id_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let leaf_type = canonical_semantic_leaf_type(semantic_graph, semantic_type)?; - if canonical_value_uses_handle_slot(semantic_graph, semantic_type)? { - let handle_abi = resolved_semantic_value_abi(value_abis, leaf_type, abi); - return emit_write_runtime_handle_slot_ref( - function, - helper_indices, - runtime_indices, - handle_abi, - leaf_type, - source, - cursor_local, - locals, - ); - } - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalGet(child_count_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_write_node_ref_at_cursor(function, cursor_local, locals.count); - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn emit_write_immediate_canonical_value_ref( - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let abi = resolved_semantic_value_abi(value_abis, semantic_type, abi); - emit_local_plus_offset(function, cursor_local, 0); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - match semantic_type_kind(semantic_graph, semantic_type)? { - AbiV2TypeKind::Unit => { - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_UNIT_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - AbiV2TypeKind::Bool => { - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_load_i32_from_source(function, source); - emit_store8_at_local(function, cursor_local, 2, locals.count); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_BOOL_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - AbiV2TypeKind::Int { .. } => { - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(2)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, 2); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, 3); - function.instruction(&Instruction::I32Const(32)); - function.instruction(&Instruction::I32Store16(memarg(0, 0))); - emit_load_i32_from_source(function, source); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - emit_local_plus_offset(function, cursor_local, 5); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, 9); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::I32Const(31)); - function.instruction(&Instruction::I32ShrS); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_INT_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - AbiV2TypeKind::Float { .. } => { - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(3)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, 2); - function.instruction(&Instruction::I32Const(64)); - function.instruction(&Instruction::I32Store16(memarg(0, 0))); - emit_load_f64_from_source(function, source); - emit_store_f64_at_local(function, cursor_local, 4, locals.f64_temp); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_FLOAT_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - AbiV2TypeKind::Char => { - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_load_i32_from_source(function, source); - emit_store32_at_local(function, cursor_local, 2, locals.count); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_CHAR_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - AbiV2TypeKind::Enum { .. } => { - let enum_layout = enum_layout_for_mode(abi, mode)?; - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::I32Const(5)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, 2); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_enum_tag_to_variant_index(function, enum_layout, locals.temp_ptr_aux); - emit_store32_at_local(function, cursor_local, 6, locals.count); - emit_local_plus_offset(function, cursor_local, CANONICAL_INLINE_ENUM_TAG_SIZE); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - other => { - return Err(Diagnostic::error( - format!("internal error: unexpected immediate ABI v2 type `{other:?}`"), - mitki_errors::TextRange::default(), - )); - } - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn emit_encode_canonical_nodes( - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - base_id_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let abi = resolved_semantic_value_abi(value_abis, semantic_type, abi); - if matches!( - semantic_type_kind(semantic_graph, semantic_type)?, - AbiV2TypeKind::Intersection { .. } - ) { - let (carrier, live_facets) = live_intersection_members(semantic_graph, semantic_type)?; - if live_facets.is_empty() { - return emit_encode_canonical_nodes( - helper_indices, - runtime_indices, - function, - value_abis, - resolved_semantic_value_abi(value_abis, carrier, abi), - carrier, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - locals, - ); - } - } - if canonical_value_uses_handle_slot(semantic_graph, semantic_type)? { - return Ok(()); - } - match semantic_type_kind(semantic_graph, semantic_type)? { - AbiV2TypeKind::String => { - emit_load_pointer_from_source(function, source, locals.temp_ptr); - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, locals.temp_ptr_aux, 0); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset( - function, - locals.temp_ptr_aux, - CANONICAL_NODE_CHILD_COUNT_OFFSET, - ); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset( - function, - locals.temp_ptr_aux, - CANONICAL_NODE_PAYLOAD_LEN_OFFSET, - ); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(cursor_local)); - emit_local_plus_offset(function, cursor_local, 0); - emit_local_plus_offset(function, locals.temp_ptr, 4); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); - Ok(()) - } - AbiV2TypeKind::Array { elem } => { - let layout = array_layout_from_abi(abi)?; - emit_load_pointer_from_source(function, source, locals.temp_ptr); - emit_local_get_and_load32(function, locals.temp_ptr, ARRAY_LEN_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - let packed = semantic_type_is_immediate(semantic_graph, *elem)? - && packed_scalar_width(&layout.item).is_some() - && packed_scalar_kind_tag(&layout.item).is_some(); - if !packed { - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr, - layout, - locals.index, - locals.temp_ptr_aux, - ); - emit_measure_canonical_value_ref( - function, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.temp_ptr_aux, offset: 0 }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_encode_canonical_nodes( - helper_indices, - runtime_indices, - function, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.temp_ptr_aux, offset: 0 }, - cursor_local, - locals.count, - locals, - )?; - function.instruction(&Instruction::End); - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Else); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, locals.temp_ptr_aux, 0); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::I32Const(if packed { - packed_scalar_kind_tag(&layout.item).expect("packed scalar kind") as i32 - } else { - CANONICAL_ARRAY_VALUES_SENTINEL as i32 - })); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset( - function, - locals.temp_ptr_aux, - CANONICAL_NODE_CHILD_COUNT_OFFSET, - ); - if packed { - function.instruction(&Instruction::I32Const(0)); - } else { - function.instruction(&Instruction::LocalGet(locals.len)); - } - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset( - function, - locals.temp_ptr_aux, - CANONICAL_NODE_PAYLOAD_LEN_OFFSET, - ); - if packed { - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const( - packed_scalar_width(&layout.item).expect("packed scalar width") as i32, - )); - function.instruction(&Instruction::I32Mul); - } else { - function.instruction(&Instruction::I32Const(0)); - } - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(cursor_local)); - - if packed { - match layout.item.kind { - MitkiValueKind::Bool => { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr, - layout, - locals.index, - locals.temp_ptr_aux, - ); - emit_local_get_and_load32(function, locals.temp_ptr_aux, 0); - emit_store8_at_local(function, cursor_local, 0, locals.count); - emit_local_plus_offset(function, cursor_local, 1); - function.instruction(&Instruction::LocalSet(cursor_local)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - } - MitkiValueKind::Int | MitkiValueKind::Char => { - emit_local_plus_offset(function, cursor_local, 0); - emit_local_plus_offset(function, locals.temp_ptr, layout.data_offset); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - MitkiValueKind::Float => { - emit_local_plus_offset(function, cursor_local, 0); - emit_local_plus_offset(function, locals.temp_ptr, layout.data_offset); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(8)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Const(8)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(cursor_local)); - } - _ => unreachable!(), - } - return Ok(()); - } - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_runtime_array_element_addr( - function, - locals.temp_ptr, - layout, - locals.index, - locals.temp_ptr_aux, - ); - if semantic_type_is_immediate(semantic_graph, *elem)? { - emit_write_immediate_canonical_value_ref( - function, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.temp_ptr_aux, offset: 0 }, - cursor_local, - locals, - )?; - } else { - emit_measure_canonical_value_ref( - function, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.temp_ptr_aux, offset: 0 }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - } - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - if !semantic_type_is_immediate(semantic_graph, *elem)? { - emit_write_non_immediate_canonical_value_ref( - function, - helper_indices, - runtime_indices, - value_abis, - &layout.item, - *elem, - semantic_graph, - LoweringMode::Runtime, - BoundaryValueSource::Memory { base_local: locals.temp_ptr_aux, offset: 0 }, - cursor_local, - locals.child_count, - locals.base_id, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - Ok(()) - } - AbiV2TypeKind::Tuple { elems } => emit_encode_canonical_fields_node( - helper_indices, - runtime_indices, - function, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - elems, - 2, - locals, - ), - AbiV2TypeKind::Record { fields } => { - let field_types = fields.iter().map(|field| field.ty).collect::>(); - emit_encode_canonical_fields_node( - helper_indices, - runtime_indices, - function, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - &field_types, - 3, - locals, - ) - } - AbiV2TypeKind::Struct { fields, .. } => { - let field_types = fields.iter().map(|field| field.ty).collect::>(); - emit_encode_canonical_fields_node( - helper_indices, - runtime_indices, - function, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - &field_types, - 4, - locals, - ) - } - AbiV2TypeKind::Enum { variants, .. } => emit_encode_canonical_enum_node( - helper_indices, - runtime_indices, - function, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - variants, - locals, - ), - AbiV2TypeKind::Union { members } => emit_encode_canonical_union_node( - helper_indices, - runtime_indices, - function, - value_abis, - abi, - semantic_type, - semantic_graph, - mode, - source, - cursor_local, - base_id_local, - members, - locals, - ), - _ => Err(Diagnostic::error( - "ABI v2 baseline guest lowering does not support this structured node kind yet", - mitki_errors::TextRange::default(), - )), - } -} - -#[allow(clippy::too_many_arguments)] -fn emit_encode_canonical_fields_node( - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - base_id_local: u32, - field_types: &[TypeId], - node_kind_tag: i32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let layout = aggregate_layout_for_mode(abi, mode)?; - let MitkiAggregateKindAbi::Fields(fields) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: aggregate ABI v2 encoding expected fields layout", - mitki_errors::TextRange::default(), - )); - }; - - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - for (field, &field_ty) in fields.iter().zip(field_types.iter()) { - if semantic_type_is_immediate(semantic_graph, field_ty)? { - continue; - } - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.count)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_encode_canonical_nodes( - helper_indices, - runtime_indices, - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - cursor_local, - locals.count, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - function.instruction(&Instruction::LocalSet(locals.base_id)); - - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, locals.temp_ptr_aux, 0); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(node_kind_tag)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_CHILD_COUNT_OFFSET); - function.instruction(&Instruction::I32Const(field_types.len() as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_PAYLOAD_LEN_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(cursor_local)); - - for (field, &field_ty) in fields.iter().zip(field_types.iter()) { - let field_source = - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }; - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - if semantic_type_is_immediate(semantic_graph, field_ty)? { - emit_write_immediate_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - continue; - } - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - emit_write_non_immediate_canonical_value_ref( - function, - helper_indices, - runtime_indices, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals.child_count, - locals.base_id, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn emit_encode_canonical_enum_node( - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - base_id_local: u32, - variants: &[mitki_abi::EnumVariant], - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let enum_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.index)); - - for (variant_index, variant) in variants.iter().enumerate().rev() { - let layout_variant = &enum_layout.variants[variant_index]; - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(layout_variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - for (field, &field_ty) in layout_variant.fields.iter().zip(variant.fields.iter()) { - if semantic_type_is_immediate(semantic_graph, field_ty)? { - continue; - } - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.count)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_encode_canonical_nodes( - helper_indices, - runtime_indices, - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - cursor_local, - locals.count, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - function.instruction(&Instruction::LocalSet(locals.base_id)); - - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, locals.temp_ptr_aux, 0); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(5)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::I32Const(variant_index as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_CHILD_COUNT_OFFSET); - function.instruction(&Instruction::I32Const(layout_variant.fields.len() as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_PAYLOAD_LEN_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(cursor_local)); - - for (field, &field_ty) in layout_variant.fields.iter().zip(variant.fields.iter()) { - let field_source = - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }; - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - if semantic_type_is_immediate(semantic_graph, field_ty)? { - emit_write_immediate_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - continue; - } - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - emit_write_non_immediate_canonical_value_ref( - function, - helper_indices, - runtime_indices, - value_abis, - &field.value, - field_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals.child_count, - locals.base_id, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - - function.instruction(&Instruction::End); - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn emit_encode_canonical_union_node( - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - function: &mut WasmFunction, - value_abis: &BTreeMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - semantic_graph: &SemanticTypeGraph, - mode: LoweringMode, - source: BoundaryValueSource, - cursor_local: u32, - base_id_local: u32, - members: &[TypeId], - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_materialize_value_source_base(function, abi, mode, source, locals.temp_ptr)?; - let union_layout = enum_layout_for_mode(abi, mode)?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.index)); - - for (arm_index, &member_ty) in members.iter().enumerate().rev() { - let runtime_variant_index = union_runtime_variant_index(abi, members, member_ty)?; - let layout_variant = &union_layout.variants[runtime_variant_index]; - let field = layout_variant.fields.first().ok_or_else(|| { - Diagnostic::error( - "internal error: union ABI v2 encoding expected a payload field", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(layout_variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - - if !semantic_type_is_immediate(semantic_graph, member_ty)? { - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalGet(base_id_local)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_measure_canonical_value_ref( - function, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - locals.child_count, - locals.child_bytes, - locals.handle_index, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.count)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - emit_encode_canonical_nodes( - helper_indices, - runtime_indices, - function, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }, - cursor_local, - locals.count, - locals, - )?; - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr)); - function.instruction(&Instruction::LocalGet(locals.base_id)); - function.instruction(&Instruction::LocalGet(locals.child_count)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.base_id)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - } - - function.instruction(&Instruction::LocalGet(cursor_local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_local_plus_offset(function, locals.temp_ptr_aux, 0); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(6)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX0_OFFSET); - function.instruction(&Instruction::I32Const(arm_index as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_AUX1_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_CHILD_COUNT_OFFSET); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.temp_ptr_aux, CANONICAL_NODE_PAYLOAD_LEN_OFFSET); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, cursor_local, CANONICAL_NODE_CHILDREN_OFFSET); - function.instruction(&Instruction::LocalSet(cursor_local)); - - let field_source = - BoundaryValueSource::Memory { base_local: locals.temp_ptr, offset: field.offset }; - if semantic_type_is_immediate(semantic_graph, member_ty)? { - emit_write_immediate_canonical_value_ref( - function, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals, - )?; - } else { - emit_write_non_immediate_canonical_value_ref( - function, - helper_indices, - runtime_indices, - value_abis, - &field.value, - member_ty, - semantic_graph, - mode, - field_source, - cursor_local, - locals.child_count, - base_id_local, - locals, - )?; - } - - function.instruction(&Instruction::End); - } - Ok(()) -} - -fn emit_write_canonical_blob_header( - function: &mut WasmFunction, - blob_local: u32, - transport_type: TypeId, - total_len_local: u32, - node_count_local: u32, - handle_count_local: u32, -) { - for (index, byte) in [b'M', b'T', b'K', b'C', b'V', b'2', 0, 0].into_iter().enumerate() { - emit_local_plus_offset(function, blob_local, index as u32); - function.instruction(&Instruction::I32Const(i32::from(byte))); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - } - emit_local_plus_offset(function, blob_local, 8); - function.instruction(&Instruction::I32Const(2)); - function.instruction(&Instruction::I32Store16(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, 10); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store16(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, 12); - function.instruction(&Instruction::I32Const(i32::from(CANONICAL_BLOB_ENCODING_VERSION))); - function.instruction(&Instruction::I32Store16(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, 14); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, 18); - function.instruction(&Instruction::I32Const(transport_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_TOTAL_LEN_OFFSET); - function.instruction(&Instruction::LocalGet(total_len_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_NODE_COUNT_OFFSET); - function.instruction(&Instruction::LocalGet(node_count_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_HANDLE_COUNT_OFFSET); - function.instruction(&Instruction::LocalGet(handle_count_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_ROOT_OFFSET); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - emit_local_plus_offset(function, blob_local, CANONICAL_BLOB_ROOT_OFFSET + 1); - function.instruction(&Instruction::LocalGet(node_count_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::I32Store(memarg(0, 0))); -} - -fn emit_alloc_runtime_array_to_local( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - layout: &MitkiArrayLayoutAbi, - len_local: u32, - base_local: u32, - payload_local: u32, -) -> Result<(), Diagnostic> { - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + layout.data_offset) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(payload_local)); - emit_dynamic_alloc_to_local( - function, - runtime_indices, - payload_local, - layout.object_align, - base_local, - )?; - emit_local_plus_offset(function, base_local, 0); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalGet(payload_local)); - function.instruction(&Instruction::MemoryFill(0)); - emit_write_arc_header(function, base_local); - emit_local_plus_offset(function, base_local, ARC_HEADER_SIZE); - function.instruction(&Instruction::LocalSet(payload_local)); - emit_local_plus_offset(function, payload_local, ARRAY_LEN_OFFSET); - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); - emit_local_plus_offset(function, payload_local, ARRAY_CAPACITY_OFFSET); - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); - Ok(()) -} - -fn emit_runtime_array_element_addr( - function: &mut WasmFunction, - payload_local: u32, - layout: &MitkiArrayLayoutAbi, - index_local: u32, - dest_local: u32, -) { - emit_local_plus_offset(function, payload_local, layout.data_offset); - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(dest_local)); -} - -fn emit_materialize_decode_dest_base( - function: &mut WasmFunction, - abi: &MitkiValueAbi, - mode: LoweringMode, - dest: BoundaryValueDest, - runtime_indices: &FxHashMap, - locals: CanonicalWrapperLocals, -) -> Result { - match abi.lowering(mode) { - MitkiLoweringAbi::Aggregate(_) => { - match dest { - BoundaryValueDest::Local(local) => { - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - } - BoundaryValueDest::Memory { base_local, offset } => { - emit_local_plus_offset(function, base_local, offset); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - } - } - Ok(locals.temp_ptr_aux) - } - MitkiLoweringAbi::Pointer => { - let layout = aggregate_layout_for_mode(abi, mode)?; - emit_alloc_nominal_payload_to_local( - function, - runtime_indices, - layout, - locals.temp_ptr, - locals.temp_ptr_aux, - )?; - Ok(locals.temp_ptr_aux) - } - _ => Err(Diagnostic::error( - "internal error: expected aggregate decode destination", - mitki_errors::TextRange::default(), - )), - } -} - -fn emit_materialize_value_source_base( - function: &mut WasmFunction, - abi: &MitkiValueAbi, - mode: LoweringMode, - source: BoundaryValueSource, - out_local: u32, -) -> Result<(), Diagnostic> { - match abi.lowering(mode) { - MitkiLoweringAbi::Aggregate(_) => { - emit_load_aggregate_base_from_source(function, source, out_local); - Ok(()) - } - MitkiLoweringAbi::Pointer => { - emit_load_pointer_from_source(function, source, out_local); - Ok(()) - } - _ => Err(Diagnostic::error( - "internal error: expected aggregate or pointer source during ABI v2 lowering", - mitki_errors::TextRange::default(), - )), - } -} - -fn emit_local_plus_offset(function: &mut WasmFunction, local: u32, offset: u32) { - function.instruction(&Instruction::LocalGet(local)); - if offset != 0 { - function.instruction(&Instruction::I32Const(offset as i32)); - function.instruction(&Instruction::I32Add); - } -} - -fn emit_local_get_and_load8(function: &mut WasmFunction, local: u32, offset: u32) { - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::I32Load8U(memarg(0, 0))); -} - -fn emit_local_get_and_load32(function: &mut WasmFunction, local: u32, offset: u32) { - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::I32Load(memarg(0, 0))); -} - -fn emit_local_get_and_load_f64(function: &mut WasmFunction, local: u32, offset: u32) { - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::F64Load(memarg(0, 0))); -} - -fn emit_store8_at_local(function: &mut WasmFunction, local: u32, offset: u32, scratch_local: u32) { - function.instruction(&Instruction::LocalSet(scratch_local)); - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::LocalGet(scratch_local)); - function.instruction(&Instruction::I32Store8(memarg(0, 0))); -} - -fn emit_store32_at_local(function: &mut WasmFunction, local: u32, offset: u32, scratch_local: u32) { - function.instruction(&Instruction::LocalSet(scratch_local)); - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::LocalGet(scratch_local)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); -} - -fn emit_store_f64_at_local( - function: &mut WasmFunction, - local: u32, - offset: u32, - scratch_local: u32, -) { - function.instruction(&Instruction::LocalSet(scratch_local)); - emit_local_plus_offset(function, local, offset); - function.instruction(&Instruction::LocalGet(scratch_local)); - function.instruction(&Instruction::F64Store(memarg(0, 0))); -} - -fn emit_load_i32_from_source(function: &mut WasmFunction, source: BoundaryValueSource) { - match source { - BoundaryValueSource::Local(local) => { - function.instruction(&Instruction::LocalGet(local)); - } - BoundaryValueSource::Memory { base_local, offset } => { - emit_local_get_and_load32(function, base_local, offset); - } - } -} - -fn emit_load_f64_from_source(function: &mut WasmFunction, source: BoundaryValueSource) { - match source { - BoundaryValueSource::Local(local) => { - function.instruction(&Instruction::LocalGet(local)); - } - BoundaryValueSource::Memory { base_local, offset } => { - emit_local_get_and_load_f64(function, base_local, offset); - } - } -} - -fn emit_store_i32_to_dest( - function: &mut WasmFunction, - dest: BoundaryValueDest, - scratch_local: u32, -) { - match dest { - BoundaryValueDest::Local(local) => { - function.instruction(&Instruction::LocalSet(local)); - } - BoundaryValueDest::Memory { base_local, offset } => { - emit_store32_at_local(function, base_local, offset, scratch_local); - } - } -} - -fn emit_store_f64_to_dest( - function: &mut WasmFunction, - dest: BoundaryValueDest, - scratch_local: u32, -) { - match dest { - BoundaryValueDest::Local(local) => { - function.instruction(&Instruction::LocalSet(local)); - } - BoundaryValueDest::Memory { base_local, offset } => { - emit_store_f64_at_local(function, base_local, offset, scratch_local); - } - } -} - -fn emit_load_pointer_from_source( - function: &mut WasmFunction, - source: BoundaryValueSource, - out_local: u32, -) { - emit_load_i32_from_source(function, source); - function.instruction(&Instruction::LocalSet(out_local)); -} - -fn emit_load_aggregate_base_from_source( - function: &mut WasmFunction, - source: BoundaryValueSource, - out_local: u32, -) { - match source { - BoundaryValueSource::Local(local) => { - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::LocalSet(out_local)); - } - BoundaryValueSource::Memory { base_local, offset } => { - emit_local_plus_offset(function, base_local, offset); - function.instruction(&Instruction::LocalSet(out_local)); - } - } -} - -fn emit_zero_local_region(function: &mut WasmFunction, base_local: u32, offset: u32, size: u32) { - if size == 0 { - return; - } - emit_local_plus_offset(function, base_local, offset); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::MemoryFill(0)); -} - -fn emit_alloc_temp_buffer( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - local: u32, - size: u32, - align: u32, -) -> Result<(), Diagnostic> { - let alloc = runtime_indices.get(&RuntimeFunction::Alloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing alloc runtime import during boundary marshaling", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::I32Const(align as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalSet(local)); - Ok(()) -} - -fn emit_dealloc_temp_buffer( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - local: u32, - size: u32, - align: u32, -) -> Result<(), Diagnostic> { - let dealloc = runtime_indices.get(&RuntimeFunction::Dealloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing dealloc runtime import during boundary marshaling", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::I32Const(align as i32)); - function.instruction(&Instruction::Call(dealloc)); - Ok(()) -} - -fn emit_dealloc_canonical_blob( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - closure_destroyers: &[(u32, u32)], - blob_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_release_canonical_blob_impl( - function, - helper_indices, - runtime_indices, - closure_destroyers, - blob_local, - locals, - ) -} - -fn emit_release_canonical_blob_impl( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - closure_destroyers: &[(u32, u32)], - blob_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let dealloc = runtime_indices.get(&RuntimeFunction::Dealloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing dealloc runtime import during ABI v2 blob cleanup", - mitki_errors::TextRange::default(), - ) - })?; - emit_local_get_and_load32(function, blob_local, CANONICAL_BLOB_HANDLE_COUNT_OFFSET); - function.instruction(&Instruction::LocalSet(locals.len)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Else); - emit_find_handle_table_offset(function, blob_local, locals.cursor, locals); - emit_local_plus_offset(function, locals.cursor, CANONICAL_HANDLE_TABLE_LEN_SIZE); - function.instruction(&Instruction::LocalSet(locals.cursor)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::LocalGet(locals.len)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - emit_local_get_and_load32(function, locals.cursor, ABI_V2_HANDLE_FIELD1_OFFSET); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emit_release_boundary_handle_object_impl( - function, - helper_indices, - runtime_indices, - closure_destroyers, - locals.temp_ptr_aux, - locals.temp_ptr, - locals.count, - locals.base_id, - )?; - emit_local_plus_offset(function, locals.cursor, ABI_V2_HANDLE_PAYLOAD_SIZE); - function.instruction(&Instruction::LocalSet(locals.cursor)); - function.instruction(&Instruction::LocalGet(locals.index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.index)); - function.instruction(&Instruction::Br(0)); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::LocalGet(blob_local)); - emit_local_get_and_load32(function, blob_local, CANONICAL_BLOB_TOTAL_LEN_OFFSET); - function.instruction(&Instruction::I32Const(CANONICAL_BLOB_ALIGN as i32)); - function.instruction(&Instruction::Call(dealloc)); - Ok(()) -} - -fn emit_dynamic_alloc_to_local( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - size_local: u32, - align: u32, - dest_local: u32, -) -> Result<(), Diagnostic> { - let alloc = runtime_indices.get(&RuntimeFunction::Alloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing alloc runtime import during ABI v2 marshaling", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(size_local)); - function.instruction(&Instruction::I32Const(align as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalSet(dest_local)); - Ok(()) -} - -fn emit_write_arc_header(function: &mut WasmFunction, base_local: u32) { - emit_local_plus_offset(function, base_local, 0); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); - emit_local_plus_offset(function, base_local, 4); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); -} - -fn emit_alloc_nominal_payload_to_local( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - layout: &MitkiAggregateLayoutAbi, - base_local: u32, - payload_local: u32, -) -> Result<(), Diagnostic> { - let alloc = runtime_indices.get(&RuntimeFunction::Alloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing alloc runtime import during nominal ABI v2 marshaling", - mitki_errors::TextRange::default(), - ) - })?; - let total = ARC_HEADER_SIZE.checked_add(layout.size).ok_or_else(|| { - Diagnostic::error( - "internal error: nominal ABI v2 allocation overflowed", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::I32Const(total as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalSet(base_local)); - emit_zero_local_region(function, base_local, 0, total); - emit_write_arc_header(function, base_local); - emit_local_plus_offset(function, base_local, ARC_HEADER_SIZE); - function.instruction(&Instruction::LocalSet(payload_local)); - Ok(()) -} - -fn adapter_layout( - param_count: u32, - extra_i32_locals: u32, - extra_f64_locals: u32, -) -> FunctionLayout { - let mut local_plan = LocalPlanBuilder::new(param_count); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32, ValType::I32); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32Aux, ValType::I32); - local_plan.alloc_scratch(ScratchLocalKind::ObjectI32, ValType::I32); - for ordinal in 0..extra_i32_locals { - local_plan.alloc_join(LocalPurpose::AdapterTemp { ordinal }, None, ValType::I32); - } - if extra_f64_locals > 0 { - local_plan.alloc_scratch(ScratchLocalKind::ScratchF64, ValType::F64); - for ordinal in 1..extra_f64_locals { - local_plan.alloc_join( - LocalPurpose::AdapterTemp { ordinal: extra_i32_locals + ordinal }, - None, - ValType::F64, - ); - } - } - local_plan.alloc_scratch(ScratchLocalKind::ScratchI64, ValType::I64); - FunctionLayout::new( - local_plan.finish(), - FramePlan::default(), - FunctionLayoutLookups { - slots: FxHashMap::default(), - param_names: Vec::new(), - raw_params: Vec::new(), - temps: FxHashMap::default(), - pattern_scalar_locals: FxHashMap::default(), - nominal_locals: FxHashMap::default(), - array_repeat_locals: FxHashMap::default(), - }, - ) -} - -pub(super) fn emit_bool_param_normalization( - function: &mut WasmFunction, - body: &Function<'_>, - signature: &FunctionSignature, - layout: &FunctionLayout, -) { - for (index, (¶m, ty)) in body.params().iter().zip(&signature.params).enumerate() { - if !matches!(ty, AbiTy::Scalar(BackendTy::Bool)) { - continue; - } - - let _ = param; - let Some(slot) = layout.raw_params.get(index) else { - continue; - }; - let Some(index) = slot.local_index else { - continue; - }; - - function.instruction(&Instruction::LocalGet(index)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Ne); - function.instruction(&Instruction::LocalSet(index)); - } -} - -struct WrapperMirLayout { - layout: FunctionLayout, - place_locals: FxHashMap, - value_locals: FxHashMap, - canonical_locals: Option, -} - -struct WrapperMirBoundaryAbi<'a, 'db> { - semantic_graph: &'a SemanticTypeGraph, - boundary_signature: &'a BoundarySig<'db>, - abi_signature: &'a AbiV2FunctionSignature, - function_abi: MitkiFunctionAbi, -} - -struct WrapperMirResolvedSlot<'a> { - transport: &'a mitki_abi::TransportRef, - value_abi: &'a MitkiValueAbi, - runtime_abi: &'a AbiTy, -} - -struct WrapperMirEmitter<'a, 'db> { - backend: &'a Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &'a WrapperMirFunction<'db>, - direct_function_indices: &'a FxHashMap, u32>, - raw_import_function_indices: &'a FxHashMap, u32>, - runtime_indices: &'a FxHashMap, - stage_indices: &'a FxHashMap, - helper_indices: &'a FxHashMap, - nominal_destroyers: &'a FxHashMap, - nominal_eq_helpers: &'a FxHashMap, - array_destroyers: &'a FxHashMap, - array_eq_helpers: &'a FxHashMap, - callable_type_indices: &'a FxHashMap, - table_slots: &'a FxHashMap, u32>, - closure_destroyers: &'a [(u32, u32)], - layout: WrapperMirLayout, - boundary_abi: Option>, -} - -impl WrapperMirLayout { - fn build(mir: &WrapperMirFunction<'_>) -> Self { - let mut local_plan = LocalPlanBuilder::new(mir.signature.wasm_params.len() as u32); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32, ValType::I32); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32Aux, ValType::I32); - local_plan.alloc_scratch(ScratchLocalKind::ObjectI32, ValType::I32); - - let mut next_adapter_local = 0u32; - let mut place_locals = FxHashMap::default(); - for place in &mir.places { - let Some(value_type) = place.ty.value_type() else { - continue; - }; - let local = local_plan.alloc_join( - LocalPurpose::AdapterTemp { ordinal: next_adapter_local }, - None, - value_type, - ); - next_adapter_local += 1; - place_locals.insert(place.id, local); - } - - let mut value_locals = FxHashMap::default(); - collect_wrapper_mir_value_locals( - &mir.body, - &mut local_plan, - &mut value_locals, - &mut next_adapter_local, - ); - - local_plan.alloc_scratch(ScratchLocalKind::ScratchF64, ValType::F64); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI64, ValType::I64); - let layout = FunctionLayout::new( - local_plan.finish(), - FramePlan::default(), - FunctionLayoutLookups { - slots: FxHashMap::default(), - param_names: Vec::new(), - raw_params: Vec::new(), - temps: FxHashMap::default(), - pattern_scalar_locals: FxHashMap::default(), - nominal_locals: FxHashMap::default(), - array_repeat_locals: FxHashMap::default(), - }, - ); - let canonical_locals = build_wrapper_mir_canonical_locals(mir, &place_locals); - Self { layout, place_locals, value_locals, canonical_locals } - } -} - -fn collect_wrapper_mir_value_locals( - region: &WrapperRegion<'_>, - local_plan: &mut LocalPlanBuilder, - value_locals: &mut FxHashMap, - next_adapter_local: &mut u32, -) { - for stmt in ®ion.stmts { - match stmt { - WrapperStmt::Let { value, ty, .. } => { - let Some(value_type) = ty.value_type() else { - continue; - }; - let local = local_plan.alloc_join( - LocalPurpose::AdapterTemp { ordinal: *next_adapter_local }, - None, - value_type, - ); - *next_adapter_local += 1; - value_locals.insert(*value, local); - } - WrapperStmt::If { then_region, else_region, .. } => { - collect_wrapper_mir_value_locals( - then_region, - local_plan, - value_locals, - next_adapter_local, - ); - collect_wrapper_mir_value_locals( - else_region, - local_plan, - value_locals, - next_adapter_local, - ); - } - WrapperStmt::Store { .. } | WrapperStmt::Eval { .. } | WrapperStmt::Return { .. } => {} - } - } -} - -fn build_wrapper_mir_canonical_locals( - mir: &WrapperMirFunction<'_>, - place_locals: &FxHashMap, -) -> Option { - let mut scratch = FxHashMap::default(); - for place in &mir.places { - let WrapperPlaceKind::Scratch(kind) = place.kind else { - continue; - }; - let local = place_locals.get(&place.id).copied()?; - scratch.insert(kind, local); - } - (!scratch.is_empty()).then(|| CanonicalWrapperLocals { - node_offset: scratch[&WrapperScratchKind::NodeOffset], - cursor: scratch[&WrapperScratchKind::Cursor], - index: scratch[&WrapperScratchKind::Index], - len: scratch[&WrapperScratchKind::Len], - count: scratch[&WrapperScratchKind::Count], - bytes: scratch[&WrapperScratchKind::Bytes], - base_id: scratch[&WrapperScratchKind::BaseId], - temp_ptr: scratch[&WrapperScratchKind::TempPtr], - temp_ptr_aux: scratch[&WrapperScratchKind::TempPtrAux], - child_count: scratch[&WrapperScratchKind::ChildCount], - child_bytes: scratch[&WrapperScratchKind::ChildBytes], - handle_count: scratch[&WrapperScratchKind::HandleCount], - handle_index: scratch[&WrapperScratchKind::HandleIndex], - handle_cursor: scratch[&WrapperScratchKind::HandleCursor], - f64_temp: scratch[&WrapperScratchKind::F64Temp], - }) -} - -fn wrapper_mir_function_result_abi(signature: &WrapperMirSignature<'_>) -> AbiTy { - signature - .callable - .as_ref() - .map(|callable| callable.result.clone()) - .or_else(|| { - signature.internal.as_ref().and_then(|internal| internal.results.first().cloned()) - }) - .unwrap_or(AbiTy::Scalar(BackendTy::Unit)) -} - -fn wrapper_place_ty_for_param( - mir: &WrapperMirFunction<'_>, - param: u32, -) -> Result { - let value_type = mir.signature.wasm_params.get(param as usize).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR referenced an out-of-bounds parameter lane", - mitki_errors::TextRange::default(), - ) - })?; - Ok(match value_type { - ValType::I32 => WrapperPlaceTy::I32, - ValType::I64 => WrapperPlaceTy::I64, - ValType::F64 => WrapperPlaceTy::F64, - _ => WrapperPlaceTy::I32, - }) -} - -fn wrapper_transport_is_nullary_enum( - semantic_graph: &SemanticTypeGraph, - transport: &mitki_abi::TransportRef, -) -> Result { - Ok(matches!( - semantic_type_kind(semantic_graph, transport_type_id(transport))?, - AbiV2TypeKind::Enum { variants, .. } if variants.iter().all(|variant| variant.fields.is_empty()) - )) -} - -impl<'a, 'db> WrapperMirEmitter<'a, 'db> { - #[allow(clippy::too_many_arguments)] - fn new( - backend: &'a Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &'a WrapperMirFunction<'db>, - semantic_graph: Option<&'a SemanticTypeGraph>, - direct_function_indices: &'a FxHashMap, u32>, - raw_import_function_indices: &'a FxHashMap, u32>, - runtime_indices: &'a FxHashMap, - stage_indices: &'a FxHashMap, - helper_indices: &'a FxHashMap, - nominal_destroyers: &'a FxHashMap, - nominal_eq_helpers: &'a FxHashMap, - array_destroyers: &'a FxHashMap, - array_eq_helpers: &'a FxHashMap, - callable_type_indices: &'a FxHashMap, - table_slots: &'a FxHashMap, u32>, - closure_destroyers: &'a [(u32, u32)], - ) -> Result { - let boundary_abi = match (mir.signature.boundary.as_ref(), semantic_graph) { - (Some(boundary_signature), Some(semantic_graph)) => { - let signature_id = mir.signature.signature_id.ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR boundary signature is missing its ABI id", - backend.function_range(location), - ) - })?; - let abi_signature = - semantic_graph.signatures.get(signature_id.0 as usize).ok_or_else(|| { - Diagnostic::error( - format!( - "internal error: wrapper MIR referenced unknown ABI signature `{}`", - signature_id.0 - ), - backend.function_range(location), - ) - })?; - let param_runtime_tys = boundary_signature - .params - .iter() - .map(|slot| slot.semantic_ty) - .collect::>(); - let result_runtime_ty = boundary_signature - .results - .first() - .map_or(Ty::new(backend.db, TyKind::Tuple(Vec::new())), |slot| { - slot.semantic_ty - }); - let function_abi = MitkiFunctionAbi::from_v2_signature_with_runtime_types( - backend.db, - semantic_graph, - abi_signature.id, - ¶m_runtime_tys, - result_runtime_ty, - ) - .map_err(|message| Diagnostic::error(message, backend.function_range(location)))?; - Some(WrapperMirBoundaryAbi { - semantic_graph, - boundary_signature, - abi_signature, - function_abi, - }) - } - (Some(_), None) => { - return Err(Diagnostic::error( - "internal error: boundary wrapper MIR emission requires ABI graph context", - backend.function_range(location), - )); - } - (None, _) => None, - }; - Ok(Self { - backend, - location, - source_map, - mir, - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - layout: WrapperMirLayout::build(mir), - boundary_abi, - }) - } - - fn emit(self) -> Result { - let function_result = wrapper_mir_function_result_abi(&self.mir.signature); - let mut backend_emitter = backend_ir::BackendEmitter { - backend: self.backend, - location: self.location, - source_map: self.source_map, - function_indices: self.direct_function_indices, - runtime_indices: self.runtime_indices, - stage_indices: self.stage_indices, - helper_indices: self.helper_indices, - nominal_destroyers: self.nominal_destroyers, - nominal_eq_helpers: self.nominal_eq_helpers, - array_destroyers: self.array_destroyers, - array_eq_helpers: self.array_eq_helpers, - callable_type_indices: self.callable_type_indices, - table_slots: self.table_slots, - closure_destroyers: self.closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &self.layout.layout, - function_result: &function_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - let mut function = WasmFunction::new(self.layout.layout.wasm_locals().iter().copied()); - self.emit_region(&mut backend_emitter, &mut function, &self.mir.body)?; - function.instruction(&Instruction::End); - Ok(function) - } - - fn place_local(&self, place: PlaceId) -> Result { - self.layout.place_locals.get(&place).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR expected a concrete Wasm local for a place", - self.backend.function_range(self.location), - ) - }) - } - - fn place_local_opt(&self, place: PlaceId) -> Option { - self.layout.place_locals.get(&place).copied() - } - - fn value_local(&self, value: ValueId) -> Result { - self.layout.value_locals.get(&value).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR expected a concrete Wasm local for a value", - self.backend.function_range(self.location), - ) - }) - } - - fn operand_local(&self, operand: &WrapperOperand) -> Result { - match operand { - WrapperOperand::Place(place) => self.place_local(*place), - WrapperOperand::Value(value) => self.value_local(*value), - } - } - - fn canonical_locals(&self) -> Result { - self.layout.canonical_locals.ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR operation required canonical scratch locals", - self.backend.function_range(self.location), - ) - }) - } - - fn emit_region( - &self, - backend_emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - function: &mut WasmFunction, - region: &WrapperRegion<'db>, - ) -> Result<(), Diagnostic> { - for stmt in ®ion.stmts { - match stmt { - WrapperStmt::Let { value, rhs, .. } => { - let local = self.value_local(*value)?; - let produced = self.emit_rvalue(backend_emitter, function, rhs)?; - if produced.is_none() { - return Err(Diagnostic::error( - "internal error: wrapper MIR let expected a value-producing rvalue", - self.backend.function_range(self.location), - )); - } - function.instruction(&Instruction::LocalSet(local)); - } - WrapperStmt::Store { place, value } => { - let Some(local) = self.place_local_opt(*place) else { - continue; - }; - self.emit_operand(function, value)?; - function.instruction(&Instruction::LocalSet(local)); - } - WrapperStmt::If { cond, then_region, else_region, .. } => { - self.emit_operand(function, cond)?; - function.instruction(&Instruction::If(BlockType::Empty)); - self.emit_region(backend_emitter, function, then_region)?; - function.instruction(&Instruction::Else); - self.emit_region(backend_emitter, function, else_region)?; - function.instruction(&Instruction::End); - } - WrapperStmt::Eval { rhs } => { - if self.emit_rvalue(backend_emitter, function, rhs)?.is_some() { - function.instruction(&Instruction::Drop); - } - } - WrapperStmt::Return { values } => { - for value in values { - self.emit_operand(function, value)?; - } - function.instruction(&Instruction::Return); - } - } - } - Ok(()) - } - - fn emit_operand( - &self, - function: &mut WasmFunction, - operand: &WrapperOperand, - ) -> Result<(), Diagnostic> { - function.instruction(&Instruction::LocalGet(self.operand_local(operand)?)); - Ok(()) - } - - fn direct_call_target_index(&self, target: &WrapperCallTarget<'db>) -> Result { - match target { - WrapperCallTarget::DirectFunction(instance) => { - self.direct_function_indices.get(instance).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing direct function index for wrapper MIR call", - self.backend.function_range(self.location), - ) - }) - } - WrapperCallTarget::RawImport(instance) => { - self.raw_import_function_indices.get(instance).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing raw import function index for wrapper MIR call", - self.backend.function_range(self.location), - ) - }) - } - } - } - - fn resolve_slot( - &self, - slot: &super::super::boundary::BoundarySlot<'db>, - rhs: &WrapperRValue<'db>, - ) -> Result, Diagnostic> { - let boundary = self.boundary_abi.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR transport op missing boundary ABI context", - self.backend.function_range(self.location), - ) - })?; - let search_results = match (&self.mir.kind, rhs) { - ( - WrapperKind::ImportThunk { .. }, - WrapperRValue::EncodeImmediate { .. } - | WrapperRValue::EncodeCanonical { .. } - | WrapperRValue::WrapHandle { .. }, - ) => false, - ( - WrapperKind::ImportThunk { .. }, - WrapperRValue::DecodeImmediate { .. } - | WrapperRValue::DecodeCanonical { .. } - | WrapperRValue::UnwrapHandle { .. }, - ) => true, - ( - WrapperKind::ExportWrapper { .. } | WrapperKind::HandleInvokeTrampoline { .. }, - WrapperRValue::DecodeImmediate { .. } - | WrapperRValue::DecodeCanonical { .. } - | WrapperRValue::UnwrapHandle { .. }, - ) => false, - ( - WrapperKind::ExportWrapper { .. } | WrapperKind::HandleInvokeTrampoline { .. }, - WrapperRValue::EncodeImmediate { .. } - | WrapperRValue::EncodeCanonical { .. } - | WrapperRValue::WrapHandle { .. }, - ) => true, - _ => { - return Err(Diagnostic::error( - "internal error: wrapper MIR transport op appeared in an unsupported wrapper \ - kind", - self.backend.function_range(self.location), - )); - } - }; - - let index = if search_results { - boundary.boundary_signature.results.iter().position(|candidate| candidate == slot) - } else { - boundary.boundary_signature.params.iter().position(|candidate| candidate == slot) - } - .ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR transport op drifted from its boundary signature", - self.backend.function_range(self.location), - ) - })?; - - if search_results { - Ok(WrapperMirResolvedSlot { - transport: &boundary.abi_signature.result, - value_abi: &boundary.function_abi.result.value, - runtime_abi: boundary.boundary_signature.internal.results.first().ok_or_else( - || { - Diagnostic::error( - "internal error: wrapper MIR result transport drifted from the \ - internal signature", - self.backend.function_range(self.location), - ) - }, - )?, - }) - } else { - Ok(WrapperMirResolvedSlot { - transport: &boundary.abi_signature.params[index], - value_abi: &boundary.function_abi.params[index].value, - runtime_abi: &boundary.boundary_signature.internal.params[index], - }) - } - } - - fn emit_rvalue( - &self, - backend_emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - function: &mut WasmFunction, - rhs: &WrapperRValue<'db>, - ) -> Result, Diagnostic> { - match rhs { - WrapperRValue::ReadParam { param } => { - function.instruction(&Instruction::LocalGet(*param)); - Ok(Some(wrapper_place_ty_for_param(self.mir, *param)?)) - } - WrapperRValue::NormalizeBool { operand } => { - self.emit_operand(function, operand)?; - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Ne); - Ok(Some(WrapperPlaceTy::I32)) - } - WrapperRValue::LoadPlace { place } => { - function.instruction(&Instruction::LocalGet(self.place_local(*place)?)); - Ok(Some( - self.mir - .places - .iter() - .find(|decl| decl.id == *place) - .map(|decl| decl.ty) - .ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR referenced an unknown place", - self.backend.function_range(self.location), - ) - })?, - )) - } - WrapperRValue::ReadHandleField { handle, field } => { - let offset = match field { - WrapperHandleField::Slot => ABI_V2_HANDLE_FIELD0_OFFSET, - WrapperHandleField::Env => ABI_V2_HANDLE_FIELD1_OFFSET, - }; - emit_local_get_and_load32(function, self.operand_local(handle)?, offset); - Ok(Some(WrapperPlaceTy::I32)) - } - WrapperRValue::CallDirect { target, args, result, wasm_results } => { - for arg in args { - self.emit_operand(function, arg)?; - } - function.instruction(&Instruction::Call(self.direct_call_target_index(target)?)); - if let Some(place) = result { - function.instruction(&Instruction::LocalSet(self.place_local(*place)?)); - } else if !wasm_results.is_empty() { - return Err(Diagnostic::error( - "internal error: wrapper MIR direct call dropped a visible result", - self.backend.function_range(self.location), - )); - } - Ok(None) - } - WrapperRValue::CallIndirect { - signature, - env, - table_index, - args, - result, - wasm_results, - } => { - self.emit_operand(function, env)?; - for arg in args { - self.emit_operand(function, arg)?; - } - self.emit_operand(function, table_index)?; - let callable_type = - self.callable_type_indices.get(signature).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing callable type index for wrapper MIR indirect \ - call", - self.backend.function_range(self.location), - ) - })?; - function.instruction(&Instruction::CallIndirect { - type_index: callable_type, - table_index: 0, - }); - if let Some(place) = result { - function.instruction(&Instruction::LocalSet(self.place_local(*place)?)); - } else if !wasm_results.is_empty() { - return Err(Diagnostic::error( - "internal error: wrapper MIR indirect call dropped a visible result", - self.backend.function_range(self.location), - )); - } - Ok(None) - } - WrapperRValue::EncodeImmediate { source, dest, slot } => { - self.emit_encode_immediate(function, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::DecodeImmediate { source, dest, slot } => { - self.emit_decode_immediate(function, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::EncodeCanonical { source, dest, slot } => { - self.emit_encode_canonical(function, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::DecodeCanonical { source, dest, slot } => { - self.emit_decode_canonical(function, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::WrapHandle { source, dest, slot } => { - self.emit_wrap_handle(function, backend_emitter, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::UnwrapHandle { source, dest, slot } => { - self.emit_unwrap_handle(function, backend_emitter, *source, *dest, slot)?; - Ok(None) - } - WrapperRValue::RetainNestedHandles { .. } => Ok(None), - WrapperRValue::ReleaseValue { place, abi } => { - backend_emitter.emit_release_value_from_local( - function, - self.place_local(*place)?, - abi, - ExprId::ZERO, - )?; - Ok(None) - } - WrapperRValue::ReleaseCanonicalBlob { place } => { - emit_dealloc_canonical_blob( - function, - self.helper_indices, - self.runtime_indices, - self.closure_destroyers, - self.place_local(*place)?, - self.canonical_locals()?, - )?; - Ok(None) - } - WrapperRValue::ReleaseHandleObject { place } => { - emit_release_boundary_handle_object( - backend_emitter, - function, - self.runtime_indices, - self.place_local(*place)?, - self.canonical_locals()?, - )?; - Ok(None) - } - WrapperRValue::AllocTempBuffer { place, layout } => { - emit_alloc_temp_buffer( - function, - self.runtime_indices, - self.place_local(*place)?, - layout.size, - layout.align, - )?; - Ok(None) - } - WrapperRValue::DeallocTempBuffer { place, layout } => { - emit_dealloc_temp_buffer( - function, - self.runtime_indices, - self.place_local(*place)?, - layout.size, - layout.align, - )?; - Ok(None) - } - WrapperRValue::ZeroTempBuffer { place, size } => { - emit_zero_local_region(function, self.place_local(*place)?, 0, *size); - Ok(None) - } - } - } - - fn emit_encode_immediate( - &self, - function: &mut WasmFunction, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self.resolve_slot( - slot, - &WrapperRValue::EncodeImmediate { source, dest, slot: slot.clone() }, - )?; - let source_local = self.place_local(source)?; - let dest_local = self.place_local(dest)?; - if wrapper_transport_is_nullary_enum( - self.boundary_abi.as_ref().expect("boundary ABI").semantic_graph, - resolved.transport, - )? { - let locals = self.canonical_locals()?; - emit_materialize_value_source_base( - function, - resolved.value_abi, - LoweringMode::Runtime, - internal_value_source(resolved.runtime_abi, source_local), - locals.temp_ptr, - )?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.count)); - let enum_layout = enum_layout_for_mode(resolved.value_abi, LoweringMode::Runtime)?; - emit_enum_tag_to_variant_index(function, enum_layout, locals.count); - function.instruction(&Instruction::LocalSet(dest_local)); - return Ok(()); - } - function.instruction(&Instruction::LocalGet(source_local)); - function.instruction(&Instruction::LocalSet(dest_local)); - Ok(()) - } - - fn emit_decode_immediate( - &self, - function: &mut WasmFunction, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self.resolve_slot( - slot, - &WrapperRValue::DecodeImmediate { source, dest, slot: slot.clone() }, - )?; - let source_local = self.place_local(source)?; - let dest_local = self.place_local(dest)?; - if !wrapper_transport_is_nullary_enum( - self.boundary_abi.as_ref().expect("boundary ABI").semantic_graph, - resolved.transport, - )? { - function.instruction(&Instruction::LocalGet(source_local)); - function.instruction(&Instruction::LocalSet(dest_local)); - return Ok(()); - } - - let locals = self.canonical_locals()?; - let enum_layout = enum_layout_for_mode(resolved.value_abi, LoweringMode::Runtime)?; - function.instruction(&Instruction::LocalGet(source_local)); - function.instruction(&Instruction::LocalSet(locals.count)); - match resolved.runtime_abi { - AbiTy::Aggregate(layout) => { - emit_alloc_temp_buffer( - function, - self.runtime_indices, - dest_local, - layout.size, - layout.align, - )?; - emit_zero_local_region(function, dest_local, 0, layout.size); - emit_enum_variant_index_to_tag(function, enum_layout, locals.count); - emit_store32_at_local(function, dest_local, 0, locals.count); - } - AbiTy::Scalar(BackendTy::Ref(_)) => { - let layout = aggregate_layout_for_mode(resolved.value_abi, LoweringMode::Runtime)?; - emit_alloc_nominal_payload_to_local( - function, - self.runtime_indices, - layout, - locals.temp_ptr_aux, - dest_local, - )?; - emit_enum_variant_index_to_tag(function, enum_layout, locals.count); - emit_store32_at_local(function, dest_local, 0, locals.count); - } - _ => { - return Err(Diagnostic::error( - "internal error: wrapper MIR immediate decode expected aggregate or pointer \ - enum lowering", - self.backend.function_range(self.location), - )); - } - } - Ok(()) - } - - fn emit_encode_canonical( - &self, - function: &mut WasmFunction, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self.resolve_slot( - slot, - &WrapperRValue::EncodeCanonical { source, dest, slot: slot.clone() }, - )?; - let boundary = self.boundary_abi.as_ref().expect("boundary ABI"); - let locals = self.canonical_locals()?; - let source_value = internal_value_source(resolved.runtime_abi, self.place_local(source)?); - let dest_local = self.place_local(dest)?; - emit_measure_canonical_value_ref( - function, - &boundary.function_abi.type_values, - resolved.value_abi, - resolved.transport.semantic_type, - boundary.semantic_graph, - LoweringMode::Runtime, - source_value, - locals.count, - locals.bytes, - locals.handle_count, - locals, - )?; - function.instruction(&Instruction::LocalGet(locals.bytes)); - function.instruction(&Instruction::I32Const( - (CANONICAL_BLOB_FIXED_HEADER_LEN + CANONICAL_HANDLE_TABLE_LEN_SIZE) as i32, - )); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalGet(locals.handle_count)); - function.instruction(&Instruction::I32Const(ABI_V2_HANDLE_PAYLOAD_SIZE as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.len)); - emit_dynamic_alloc_to_local( - function, - self.runtime_indices, - locals.len, - CANONICAL_BLOB_ALIGN, - dest_local, - )?; - emit_write_canonical_blob_header( - function, - dest_local, - transport_type_id(resolved.transport), - locals.len, - locals.count, - locals.handle_count, - ); - emit_local_plus_offset(function, dest_local, CANONICAL_BLOB_NODE_TABLE_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - function.instruction(&Instruction::LocalGet(dest_local)); - function.instruction(&Instruction::LocalGet(locals.bytes)); - function.instruction(&Instruction::I32Const(CANONICAL_BLOB_FIXED_HEADER_LEN as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.handle_cursor)); - function.instruction(&Instruction::LocalGet(locals.handle_cursor)); - function.instruction(&Instruction::LocalGet(locals.handle_count)); - if matches!(self.mir.kind, WrapperKind::ImportThunk { .. }) { - function.instruction(&Instruction::I32Store8(memarg(0, 0))); - } else { - function.instruction(&Instruction::I32Store(memarg(0, 0))); - } - emit_local_plus_offset(function, locals.handle_cursor, CANONICAL_HANDLE_TABLE_LEN_SIZE); - function.instruction(&Instruction::LocalSet(locals.handle_cursor)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.handle_index)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.base_id)); - emit_encode_canonical_nodes( - self.helper_indices, - self.runtime_indices, - function, - &boundary.function_abi.type_values, - resolved.value_abi, - resolved.transport.semantic_type, - boundary.semantic_graph, - LoweringMode::Runtime, - source_value, - locals.cursor, - locals.base_id, - locals, - )?; - Ok(()) - } - - fn emit_decode_canonical( - &self, - function: &mut WasmFunction, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self.resolve_slot( - slot, - &WrapperRValue::DecodeCanonical { source, dest, slot: slot.clone() }, - )?; - let boundary = self.boundary_abi.as_ref().expect("boundary ABI"); - let source_local = self.place_local(source)?; - let dest_local = self.place_local(dest)?; - let locals = self.canonical_locals()?; - emit_local_plus_offset(function, source_local, CANONICAL_BLOB_ROOT_OFFSET); - function.instruction(&Instruction::LocalSet(locals.cursor)); - match resolved.runtime_abi { - AbiTy::Aggregate(_) => emit_decode_canonical_value_ref( - function, - self.helper_indices, - &boundary.function_abi.type_values, - resolved.value_abi, - resolved.transport.semantic_type, - boundary.semantic_graph, - LoweringMode::Runtime, - source_local, - locals.cursor, - BoundaryValueDest::Memory { base_local: dest_local, offset: 0 }, - self.runtime_indices, - locals, - )?, - AbiTy::Scalar(BackendTy::Ref(_)) => emit_decode_canonical_value_ref( - function, - self.helper_indices, - &boundary.function_abi.type_values, - resolved.value_abi, - resolved.transport.semantic_type, - boundary.semantic_graph, - LoweringMode::Runtime, - source_local, - locals.cursor, - BoundaryValueDest::Local(dest_local), - self.runtime_indices, - locals, - )?, - _ => { - return Err(Diagnostic::error( - "internal error: wrapper MIR canonical decode expected aggregate or pointer \ - runtime lowering", - self.backend.function_range(self.location), - )); - } - } - Ok(()) - } - - fn emit_wrap_handle( - &self, - function: &mut WasmFunction, - backend_emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self - .resolve_slot(slot, &WrapperRValue::WrapHandle { source, dest, slot: slot.clone() })?; - emit_wrap_runtime_handle_value( - backend_emitter, - function, - self.runtime_indices, - resolved.value_abi, - internal_value_source(resolved.runtime_abi, self.place_local(source)?), - self.place_local(dest)?, - self.canonical_locals()?, - ) - } - - fn emit_unwrap_handle( - &self, - function: &mut WasmFunction, - backend_emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - source: PlaceId, - dest: PlaceId, - slot: &super::super::boundary::BoundarySlot<'db>, - ) -> Result<(), Diagnostic> { - let resolved = self.resolve_slot( - slot, - &WrapperRValue::UnwrapHandle { source, dest, slot: slot.clone() }, - )?; - let dest_local = self.place_local(dest)?; - let dest = match resolved.runtime_abi { - AbiTy::Aggregate(_) => BoundaryValueDest::Memory { base_local: dest_local, offset: 0 }, - AbiTy::Scalar(BackendTy::Ref(_)) => BoundaryValueDest::Local(dest_local), - _ => { - return Err(Diagnostic::error( - "internal error: wrapper MIR handle decode expected aggregate or pointer \ - runtime lowering", - self.backend.function_range(self.location), - )); - } - }; - emit_unwrap_boundary_handle_value( - backend_emitter, - function, - resolved.value_abi, - self.place_local(source)?, - dest, - self.canonical_locals()?, - ) - } -} - -#[allow(clippy::too_many_arguments)] -fn emit_wrapper_mir<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &WrapperMirFunction<'db>, - semantic_graph: Option<&SemanticTypeGraph>, - direct_function_indices: &FxHashMap, u32>, - raw_import_function_indices: &FxHashMap, u32>, - runtime_indices: &FxHashMap, - stage_indices: &FxHashMap, - helper_indices: &FxHashMap, - nominal_destroyers: &FxHashMap, - nominal_eq_helpers: &FxHashMap, - array_destroyers: &FxHashMap, - array_eq_helpers: &FxHashMap, - callable_type_indices: &FxHashMap, - table_slots: &FxHashMap, u32>, - closure_destroyers: &[(u32, u32)], -) -> Result { - WrapperMirEmitter::new( - backend, - location, - source_map, - mir, - semantic_graph, - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - )? - .emit() -} - -#[allow(clippy::too_many_arguments)] -pub(super) fn emit_function_value_wrapper<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &WrapperMirFunction<'db>, - direct_function_indices: &FxHashMap, u32>, - runtime_indices: &FxHashMap, - stage_indices: &FxHashMap, - helper_indices: &FxHashMap, - nominal_destroyers: &FxHashMap, - nominal_eq_helpers: &FxHashMap, - array_destroyers: &FxHashMap, - array_eq_helpers: &FxHashMap, - callable_type_indices: &FxHashMap, - table_slots: &FxHashMap, u32>, - closure_destroyers: &[(u32, u32)], -) -> Result { - let raw_import_function_indices = FxHashMap::default(); - emit_wrapper_mir( - backend, - location, - source_map, - mir, - None, - direct_function_indices, - &raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - ) -} - -#[allow(clippy::too_many_arguments)] -pub(super) fn emit_import_thunk_v2<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &WrapperMirFunction<'db>, - semantic_graph: &SemanticTypeGraph, - direct_function_indices: &FxHashMap, u32>, - raw_import_function_indices: &FxHashMap, u32>, - runtime_indices: &FxHashMap, - stage_indices: &FxHashMap, - helper_indices: &FxHashMap, - nominal_destroyers: &FxHashMap, - nominal_eq_helpers: &FxHashMap, - array_destroyers: &FxHashMap, - array_eq_helpers: &FxHashMap, - callable_type_indices: &FxHashMap, - table_slots: &FxHashMap, u32>, - closure_destroyers: &[(u32, u32)], -) -> Result { - emit_wrapper_mir( - backend, - location, - source_map, - mir, - Some(semantic_graph), - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - ) -} - -pub(super) fn emit_blob_release_helper( - _helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - _closure_destroyers: &[(u32, u32)], -) -> Result { - let layout = adapter_layout(1, 0, 0); - let mut function = WasmFunction::new(layout.wasm_locals().iter().copied()); - let dealloc = runtime_indices.get(&RuntimeFunction::Dealloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing dealloc runtime import for `mitki:abi/2/blob_release`", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(0)); - emit_local_get_and_load32(&mut function, 0, CANONICAL_BLOB_TOTAL_LEN_OFFSET); - function.instruction(&Instruction::I32Const(CANONICAL_BLOB_ALIGN as i32)); - function.instruction(&Instruction::Call(dealloc)); - function.instruction(&Instruction::End); - Ok(function) -} - -pub(super) fn emit_alloc_helper( - runtime_indices: &FxHashMap, -) -> Result { - let layout = adapter_layout(2, 0, 0); - let mut function = WasmFunction::new(layout.wasm_locals().iter().copied()); - let alloc = runtime_indices.get(&RuntimeFunction::Alloc).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing alloc runtime import for `mitki:abi/2/alloc`", - mitki_errors::TextRange::default(), - ) - })?; - function.instruction(&Instruction::LocalGet(0)); - function.instruction(&Instruction::LocalGet(1)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::End); - Ok(function) -} - -fn arc_retain_helper_index<'db>( - emitter: &backend_ir::BackendEmitter<'_, 'db>, -) -> Result { - emitter.helper_indices.get(&HelperFunction::ArcRetain).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing ARC retain helper during ABI v2 handle lowering", - emitter.backend.function_range(emitter.location), - ) - }) -} - -fn alloc_runtime_index( - runtime_indices: &FxHashMap, - message: &'static str, -) -> Result { - runtime_indices - .get(&RuntimeFunction::Alloc) - .copied() - .ok_or_else(|| Diagnostic::error(message, mitki_errors::TextRange::default())) -} - -fn dealloc_runtime_index( - runtime_indices: &FxHashMap, - message: &'static str, -) -> Result { - runtime_indices - .get(&RuntimeFunction::Dealloc) - .copied() - .ok_or_else(|| Diagnostic::error(message, mitki_errors::TextRange::default())) -} - -fn arc_retain_helper_index_from_map( - helper_indices: &FxHashMap, - message: &'static str, -) -> Result { - helper_indices - .get(&HelperFunction::ArcRetain) - .copied() - .ok_or_else(|| Diagnostic::error(message, mitki_errors::TextRange::default())) -} - -fn emit_alloc_handle_object_to_local( - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - kind_tag: i32, - field0_local: u32, - field1_local: u32, - base_local: u32, - handle_local: u32, -) -> Result<(), Diagnostic> { - let alloc = alloc_runtime_index( - runtime_indices, - "internal error: missing alloc runtime import during ABI v2 handle marshaling", - )?; - function.instruction(&Instruction::I32Const(ABI_V2_HANDLE_TOTAL_SIZE as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalSet(base_local)); - emit_zero_local_region(function, base_local, 0, ABI_V2_HANDLE_TOTAL_SIZE); - emit_write_arc_header(function, base_local); - emit_local_plus_offset(function, base_local, ABI_V2_HANDLE_KIND_OFFSET); - function.instruction(&Instruction::I32Const(kind_tag)); - function.instruction(&Instruction::I32Store(memarg(0, 2))); - emit_local_plus_offset(function, base_local, ARC_HEADER_SIZE); - function.instruction(&Instruction::LocalTee(handle_local)); - function.instruction(&Instruction::LocalGet(field0_local)); - function.instruction(&Instruction::I32Store(memarg(ABI_V2_HANDLE_FIELD0_OFFSET, 2))); - function.instruction(&Instruction::LocalGet(handle_local)); - function.instruction(&Instruction::LocalGet(field1_local)); - function.instruction(&Instruction::I32Store(memarg(ABI_V2_HANDLE_FIELD1_OFFSET, 2))); - Ok(()) -} - -fn emit_wrap_runtime_handle_slot_value( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - abi: &MitkiValueAbi, - source: BoundaryValueSource, - handle_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let retain = arc_retain_helper_index_from_map( - helper_indices, - "internal error: missing ARC retain helper during nested ABI v2 handle lowering", - )?; - match abi.kind { - MitkiValueKind::Function => { - emit_materialize_value_source_base( - function, - abi, - LoweringMode::Runtime, - source, - locals.temp_ptr, - )?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_local_get_and_load32(function, locals.temp_ptr, 4); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(retain)); - emit_alloc_handle_object_to_local( - function, - runtime_indices, - ABI_V2_HANDLE_KIND_FUNCTION, - locals.count, - locals.temp_ptr_aux, - locals.base_id, - handle_local, - ) - } - MitkiValueKind::Opaque => { - emit_load_i32_from_source(function, source); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(retain)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_alloc_handle_object_to_local( - function, - runtime_indices, - ABI_V2_HANDLE_KIND_OPAQUE, - locals.temp_ptr_aux, - locals.count, - locals.base_id, - handle_local, - ) - } - _ => Err(Diagnostic::error( - "internal error: nested ABI v2 handle lowering expected a function or opaque runtime \ - value", - mitki_errors::TextRange::default(), - )), - } -} - -#[allow(clippy::too_many_arguments)] -fn emit_write_runtime_handle_slot_ref( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - abi: &MitkiValueAbi, - semantic_type: TypeId, - source: BoundaryValueSource, - cursor_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_wrap_runtime_handle_slot_value( - function, - helper_indices, - runtime_indices, - abi, - source, - locals.temp_ptr_aux, - locals, - )?; - emit_local_plus_offset(function, locals.handle_cursor, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::I32Const(semantic_type.0 as i32)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_local_plus_offset(function, locals.handle_cursor, ABI_V2_HANDLE_FIELD1_OFFSET); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::I32Store(memarg(0, 0))); - emit_write_handle_ref_at_cursor(function, cursor_local, locals.handle_index); - emit_local_plus_offset(function, locals.handle_cursor, ABI_V2_HANDLE_PAYLOAD_SIZE); - function.instruction(&Instruction::LocalSet(locals.handle_cursor)); - function.instruction(&Instruction::LocalGet(locals.handle_index)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(locals.handle_index)); - Ok(()) -} - -fn emit_unwrap_boundary_handle_slot_value( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - abi: &MitkiValueAbi, - handle_local: u32, - dest: BoundaryValueDest, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - let retain = arc_retain_helper_index_from_map( - helper_indices, - "internal error: missing ARC retain helper during nested ABI v2 handle decoding", - )?; - match abi.kind { - MitkiValueKind::Function => { - let BoundaryValueDest::Memory { base_local, offset } = dest else { - return Err(Diagnostic::error( - "internal error: nested ABI v2 function handles must decode into aggregate \ - storage", - mitki_errors::TextRange::default(), - )); - }; - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD1_OFFSET); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(retain)); - function.instruction(&Instruction::LocalGet(locals.count)); - emit_store32_at_local(function, base_local, offset, locals.count); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_store32_at_local(function, base_local, offset + 4, locals.temp_ptr_aux); - Ok(()) - } - MitkiValueKind::Opaque => { - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(retain)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - _ => Err(Diagnostic::error( - "internal error: nested ABI v2 handle decoding expected a function or opaque runtime \ - value", - mitki_errors::TextRange::default(), - )), - } -} - -#[allow(clippy::too_many_arguments)] -fn emit_release_boundary_handle_object_impl( - function: &mut WasmFunction, - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - closure_destroyers: &[(u32, u32)], - handle_local: u32, - base_local: u32, - kind_local: u32, - temp_local: u32, -) -> Result<(), Diagnostic> { - let arc_release = - helper_indices.get(&HelperFunction::ArcRelease).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing ARC release helper during ABI v2 handle cleanup", - mitki_errors::TextRange::default(), - ) - })?; - let dealloc = dealloc_runtime_index( - runtime_indices, - "internal error: missing dealloc runtime import during ABI v2 handle cleanup", - )?; - - function.instruction(&Instruction::LocalGet(handle_local)); - function.instruction(&Instruction::Call(arc_release)); - function.instruction(&Instruction::If(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(handle_local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalSet(base_local)); - - emit_local_get_and_load32(function, base_local, ABI_V2_HANDLE_KIND_OFFSET); - function.instruction(&Instruction::LocalSet(kind_local)); - - function.instruction(&Instruction::LocalGet(kind_local)); - function.instruction(&Instruction::I32Const(ABI_V2_HANDLE_KIND_FUNCTION)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD1_OFFSET); - function.instruction(&Instruction::LocalSet(temp_local)); - function.instruction(&Instruction::LocalGet(temp_local)); - function.instruction(&Instruction::Call(arc_release)); - function.instruction(&Instruction::If(BlockType::Empty)); - - for (slot, destroy_index) in closure_destroyers { - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::I32Const(*slot as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(temp_local)); - function.instruction(&Instruction::Call(*destroy_index)); - function.instruction(&Instruction::End); - } - - function.instruction(&Instruction::End); - function.instruction(&Instruction::Else); - - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::LocalSet(temp_local)); - function.instruction(&Instruction::LocalGet(temp_local)); - function.instruction(&Instruction::Call(arc_release)); - function.instruction(&Instruction::Drop); - - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(base_local)); - function.instruction(&Instruction::I32Const(ABI_V2_HANDLE_TOTAL_SIZE as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(dealloc)); - - function.instruction(&Instruction::End); - Ok(()) -} - -fn emit_release_boundary_handle_object<'db>( - emitter: &backend_ir::BackendEmitter<'_, 'db>, - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - handle_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - emit_release_boundary_handle_object_impl( - function, - emitter.helper_indices, - runtime_indices, - emitter.closure_destroyers, - handle_local, - locals.temp_ptr, - locals.count, - locals.temp_ptr_aux, - ) -} - -fn opaque_ref_abi() -> AbiTy { - AbiTy::Scalar(BackendTy::Ref(RefKind::Opaque)) -} - -fn emit_wrap_runtime_handle_value<'db>( - emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - function: &mut WasmFunction, - runtime_indices: &FxHashMap, - abi: &MitkiValueAbi, - source: BoundaryValueSource, - handle_local: u32, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - match abi.kind { - MitkiValueKind::Function => { - emit_materialize_value_source_base( - function, - abi, - LoweringMode::Runtime, - source, - locals.temp_ptr, - )?; - emit_local_get_and_load32(function, locals.temp_ptr, 0); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_local_get_and_load32(function, locals.temp_ptr, 4); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(arc_retain_helper_index(emitter)?)); - emit_alloc_handle_object_to_local( - function, - runtime_indices, - ABI_V2_HANDLE_KIND_FUNCTION, - locals.count, - locals.temp_ptr_aux, - locals.base_id, - handle_local, - ) - } - MitkiValueKind::Opaque => { - emit_load_i32_from_source(function, source); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(arc_retain_helper_index(emitter)?)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_alloc_handle_object_to_local( - function, - runtime_indices, - ABI_V2_HANDLE_KIND_OPAQUE, - locals.temp_ptr_aux, - locals.count, - locals.base_id, - handle_local, - ) - } - _ => Err(Diagnostic::error( - "internal error: ABI v2 capability-handle wrapping expected a function or opaque \ - runtime value", - emitter.backend.function_range(emitter.location), - )), - } -} - -fn emit_unwrap_boundary_handle_value<'db>( - emitter: &mut backend_ir::BackendEmitter<'_, 'db>, - function: &mut WasmFunction, - abi: &MitkiValueAbi, - handle_local: u32, - dest: BoundaryValueDest, - locals: CanonicalWrapperLocals, -) -> Result<(), Diagnostic> { - match abi.kind { - MitkiValueKind::Function => { - let BoundaryValueDest::Memory { base_local, offset } = dest else { - return Err(Diagnostic::error( - "internal error: ABI v2 function handles must decode into aggregate storage", - emitter.backend.function_range(emitter.location), - )); - }; - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.count)); - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD1_OFFSET); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - function.instruction(&Instruction::Call(arc_retain_helper_index(emitter)?)); - function.instruction(&Instruction::LocalGet(locals.count)); - emit_store32_at_local(function, base_local, offset, locals.count); - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_store32_at_local(function, base_local, offset + 4, locals.temp_ptr_aux); - Ok(()) - } - MitkiValueKind::Opaque => { - emit_local_get_and_load32(function, handle_local, ABI_V2_HANDLE_FIELD0_OFFSET); - function.instruction(&Instruction::LocalSet(locals.temp_ptr_aux)); - emitter.emit_retain_value_from_local( - function, - locals.temp_ptr_aux, - &opaque_ref_abi(), - ExprId::ZERO, - )?; - function.instruction(&Instruction::LocalGet(locals.temp_ptr_aux)); - emit_store_i32_to_dest(function, dest, locals.count); - Ok(()) - } - _ => Err(Diagnostic::error( - "internal error: ABI v2 capability-handle decoding expected a function or opaque \ - runtime value", - emitter.backend.function_range(emitter.location), - )), - } -} - -pub(super) fn emit_handle_retain_helper( - helper_indices: &FxHashMap, -) -> Result { - let layout = adapter_layout(1, 0, 0); - let mut function = WasmFunction::new(layout.wasm_locals().iter().copied()); - let retain = helper_indices.get(&HelperFunction::ArcRetain).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing ARC retain helper for `mitki:abi/2/handle_retain`", - mitki_errors::TextRange::default(), - ) - })?; - let scratch = layout - .scratch_i32_local() - .expect("handle retain helper should reserve an i32 scratch local"); - function.instruction(&Instruction::LocalGet(0)); - function.instruction(&Instruction::LocalTee(scratch)); - function.instruction(&Instruction::Call(retain)); - function.instruction(&Instruction::LocalGet(scratch)); - function.instruction(&Instruction::End); - Ok(function) -} - -pub(super) fn emit_handle_release_helper( - helper_indices: &FxHashMap, - runtime_indices: &FxHashMap, - closure_destroyers: &[(u32, u32)], -) -> Result { - let layout = adapter_layout(1, 0, 0); - let mut function = WasmFunction::new(layout.wasm_locals().iter().copied()); - let base_local = - layout.scratch_i32_local().expect("handle release helper should reserve a scratch local"); - let kind_local = layout - .scratch_i32_aux_local() - .expect("handle release helper should reserve an aux scratch local"); - let temp_local = layout - .object_i32_local() - .expect("handle release helper should reserve an object scratch local"); - emit_release_boundary_handle_object_impl( - &mut function, - helper_indices, - runtime_indices, - closure_destroyers, - 0, - base_local, - kind_local, - temp_local, - )?; - function.instruction(&Instruction::End); - Ok(function) -} - -#[allow(clippy::too_many_arguments)] -pub(super) fn emit_handle_invoke_trampoline_v2<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &WrapperMirFunction<'db>, - semantic_graph: &SemanticTypeGraph, - direct_function_indices: &FxHashMap, u32>, - raw_import_function_indices: &FxHashMap, u32>, - runtime_indices: &FxHashMap, - stage_indices: &FxHashMap, - helper_indices: &FxHashMap, - nominal_destroyers: &FxHashMap, - nominal_eq_helpers: &FxHashMap, - array_destroyers: &FxHashMap, - array_eq_helpers: &FxHashMap, - callable_type_indices: &FxHashMap, - table_slots: &FxHashMap, u32>, - closure_destroyers: &[(u32, u32)], -) -> Result { - emit_wrapper_mir( - backend, - location, - source_map, - mir, - Some(semantic_graph), - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - ) -} - -#[allow(clippy::too_many_arguments)] -pub(super) fn emit_export_wrapper_v2<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - mir: &WrapperMirFunction<'db>, - semantic_graph: &SemanticTypeGraph, - direct_function_indices: &FxHashMap, u32>, - raw_import_function_indices: &FxHashMap, u32>, - runtime_indices: &FxHashMap, - stage_indices: &FxHashMap, - helper_indices: &FxHashMap, - nominal_destroyers: &FxHashMap, - nominal_eq_helpers: &FxHashMap, - array_destroyers: &FxHashMap, - array_eq_helpers: &FxHashMap, - callable_type_indices: &FxHashMap, - table_slots: &FxHashMap, u32>, - closure_destroyers: &[(u32, u32)], -) -> Result { - emit_wrapper_mir( - backend, - location, - source_map, - mir, - Some(semantic_graph), - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers, - nominal_eq_helpers, - array_destroyers, - array_eq_helpers, - callable_type_indices, - table_slots, - closure_destroyers, - ) -} diff --git a/crates/mitki-backend-wasm/src/emit/function.rs b/crates/mitki-backend-wasm/src/emit/function.rs deleted file mode 100644 index 3207315..0000000 --- a/crates/mitki-backend-wasm/src/emit/function.rs +++ /dev/null @@ -1,4605 +0,0 @@ -use salsa::plumbing::FromId as _; - -use super::function_kernel::{ - FunctionKernelClosureEnvInit, FunctionKernelFieldValue, FunctionKernelValue, - FunctionKernelValueKind, -}; -use super::function_legalize::{ - FunctionLegalization, LegalizedArgPassing, LegalizedCallKind, LegalizedCallSite, - LegalizedCallableRepresentation, LegalizedResultPassing, -}; -use super::function_wasm_ir::{ - StructuredWasmBindingInit, StructuredWasmBindingSource, StructuredWasmExpr, - StructuredWasmExprKind, StructuredWasmFunction, StructuredWasmMatchArm, - StructuredWasmOwnershipOp, StructuredWasmRegion, StructuredWasmResolvedRefs, - StructuredWasmResultArity, StructuredWasmStmt, -}; -use super::module::{emit_scalar_load, emit_scalar_store}; -use super::*; -use crate::abi::backend_ty_block_type; -use crate::layout::VariantLayout; - -pub(super) struct BackendEmitter<'a, 'db> { - pub(super) backend: &'a Backend<'db>, - pub(super) location: FunctionLocation<'db>, - pub(super) source_map: &'db mitki_lower::hir::FunctionSourceMap, - pub(super) function_indices: &'a FxHashMap, u32>, - pub(super) runtime_indices: &'a FxHashMap, - pub(super) stage_indices: &'a FxHashMap, - pub(super) helper_indices: &'a FxHashMap, - pub(super) nominal_destroyers: &'a FxHashMap, - pub(super) nominal_eq_helpers: &'a FxHashMap, - pub(super) array_destroyers: &'a FxHashMap, - pub(super) array_eq_helpers: &'a FxHashMap, - pub(super) callable_type_indices: &'a FxHashMap, - pub(super) table_slots: &'a FxHashMap, u32>, - pub(super) closure_destroyers: &'a [(u32, u32)], - pub(super) resolved_wasm_refs: Option<&'a StructuredWasmResolvedRefs<'db>>, - pub(super) function_legalization: Option<&'a FunctionLegalization<'db>>, - pub(super) layout: &'a FunctionLayout, - pub(super) function_result: &'a AbiTy, - pub(super) control_depth: u32, - pub(super) loop_stack: Vec, - pub(super) scope_stack: Vec, - pub(super) return_target: Option, -} - -#[derive(Clone, Copy, Debug)] -pub(super) struct LoopTargets { - break_target: u32, - continue_target: u32, - scope_depth: usize, -} - -#[derive(Clone, Debug, Default)] -pub(super) struct ScopeFrame { - locals: Vec, -} - -#[derive(Clone, Debug)] -pub(super) struct ScopeLocal { - name: NameId, - abi: AbiTy, -} - -#[derive(Clone, Copy, Debug)] -enum PatternStorage { - Unit, - ScalarLocal(u32), - PointerLocal(u32), - FrameSlot(FrameSlotId), -} - -#[derive(Clone, Debug)] -struct PatternValue { - storage: PatternStorage, - abi: AbiTy, - offset: u32, -} - -#[derive(Clone, Copy)] -struct StructuredStmtContext<'a> { - abi: &'a AbiTy, - ownership: ValueOwnership, - ownership_ops: &'a [StructuredWasmOwnershipOp], -} - -impl<'a, 'db> BackendEmitter<'a, 'db> { - pub(super) fn structured_wasm_body( - &mut self, - function: &mut WasmFunction, - lowered: &StructuredWasmFunction<'db>, - ) -> Result<(), Diagnostic> { - let block_type = match self.legalized_result_passing() { - LegalizedResultPassing::Unit | LegalizedResultPassing::IndirectOutPtr => { - BlockType::Empty - } - LegalizedResultPassing::DirectScalar(ty) => backend_ty_block_type(ty), - }; - function.instruction(&Instruction::Block(block_type)); - let return_target = self.control_depth; - self.return_target = Some(return_target); - self.control_depth += 1; - self.push_scope(); - self.register_param_locals()?; - for init in &lowered.param_inits { - self.emit_structured_wasm_binding_init(function, init)?; - } - - let Some(expr) = lowered.body.as_ref() else { - self.release_scopes_to(function, 0, ExprId::ZERO)?; - self.control_depth -= 1; - self.return_target = None; - function.instruction(&Instruction::End); - return Ok(()); - }; - - match self.legalized_result_passing() { - LegalizedResultPassing::Unit => { - self.structured_wasm_expr(function, expr)?; - if let AbiTy::Scalar(ty) = expr.abi - && !lowered.body_ownership_ops.is_empty() - { - let consumed = self.emit_stack_ownership_ops( - function, - ty, - expr.source, - &lowered.body_ownership_ops, - )?; - if !consumed && !matches!(ty, BackendTy::Unit) { - function.instruction(&Instruction::Drop); - } - } - self.release_scopes_to(function, 0, expr.source)?; - } - LegalizedResultPassing::DirectScalar(BackendTy::Float) => { - self.structured_wasm_expr(function, expr)?; - let scratch = self.scratch_f64_local(expr.source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_scopes_to(function, 0, expr.source)?; - function.instruction(&Instruction::LocalGet(scratch)); - } - LegalizedResultPassing::DirectScalar(BackendTy::I64) => { - self.structured_wasm_expr(function, expr)?; - let scratch = self.scratch_i64_local(expr.source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_scopes_to(function, 0, expr.source)?; - function.instruction(&Instruction::LocalGet(scratch)); - } - LegalizedResultPassing::DirectScalar(ty) => { - self.structured_wasm_expr(function, expr)?; - if !lowered.body_ownership_ops.is_empty() { - if self.emit_stack_ownership_ops( - function, - ty, - expr.source, - &lowered.body_ownership_ops, - )? { - return Err(Diagnostic::error( - "internal error: explicit ownership ops consumed a non-unit function \ - body result", - self.node_range(expr.source), - )); - } - } else if expr.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - let scratch = self.result_i32_local(expr.source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_scopes_to(function, 0, expr.source)?; - function.instruction(&Instruction::LocalGet(scratch)); - } - LegalizedResultPassing::IndirectOutPtr => { - let result_ptr = self.legalized_result_ptr_local().ok_or_else(|| { - Diagnostic::error( - "internal error: aggregate result is missing its out-pointer local", - self.node_range(expr.source), - ) - })?; - self.emit_structured_wasm_expr_into_owner( - function, - Dest::pointer_local(result_ptr), - expr, - )?; - if !lowered.body_ownership_ops.is_empty() { - self.emit_dest_ownership_ops( - function, - Dest::pointer_local(result_ptr), - &expr.abi, - expr.source, - &lowered.body_ownership_ops, - )?; - } - self.release_scopes_to(function, 0, expr.source)?; - } - } - - self.control_depth -= 1; - self.return_target = None; - function.instruction(&Instruction::End); - Ok(()) - } - - pub(super) fn emit_prologue(&self, function: &mut WasmFunction) { - let Some(frame_base_local) = self.layout.frame_base_local() else { - return; - }; - - function.instruction(&Instruction::GlobalGet(0)); - function.instruction(&Instruction::LocalSet(frame_base_local)); - function.instruction(&Instruction::LocalGet(frame_base_local)); - function.instruction(&Instruction::I32Const(self.layout.frame_size() as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::GlobalSet(0)); - } - - pub(super) fn emit_epilogue(&self, function: &mut WasmFunction) { - let Some(frame_base_local) = self.layout.frame_base_local() else { - return; - }; - - function.instruction(&Instruction::LocalGet(frame_base_local)); - function.instruction(&Instruction::GlobalSet(0)); - } - - pub(super) fn emit_closure_env_destructor( - &mut self, - function: &mut WasmFunction, - env_layout: &AggregateLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - let env_ptr_local = self.layout.env_ptr_local().ok_or_else(|| { - Diagnostic::error( - "internal error: closure env destructor is missing its env parameter", - self.node_range(source), - ) - })?; - self.release_aggregate_at_local( - function, - env_ptr_local, - &AbiTy::Aggregate(Box::new(env_layout.clone())), - source, - )?; - self.dealloc_env_from_local(function, env_ptr_local, env_layout.size, source) - } - - pub(super) fn emit_nominal_destructor( - &mut self, - function: &mut WasmFunction, - payload_layout: &AggregateLayout, - payload_local: u32, - source: ExprId, - ) -> Result<(), Diagnostic> { - self.release_aggregate_at_local( - function, - payload_local, - &AbiTy::Aggregate(Box::new(payload_layout.clone())), - source, - )?; - self.dealloc_env_from_local(function, payload_local, payload_layout.size, source) - } - - pub(super) fn emit_nominal_equality( - &mut self, - function: &mut WasmFunction, - payload_layout: &AggregateLayout, - lhs_local: u32, - rhs_local: u32, - source: ExprId, - ) -> Result<(), Diagnostic> { - function.instruction(&Instruction::LocalGet(lhs_local)); - function.instruction(&Instruction::LocalGet(rhs_local)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::Else); - self.emit_abi_equality( - function, - lhs_local, - rhs_local, - 0, - &AbiTy::Aggregate(Box::new(payload_layout.clone())), - source, - )?; - function.instruction(&Instruction::End); - Ok(()) - } - - pub(super) fn emit_array_destructor( - &mut self, - function: &mut WasmFunction, - layout: &ArrayRuntimeLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - const ARRAY_PTR_LOCAL: u32 = 0; - - if layout.item_abi.contains_heap_refs() { - let index_local = self.object_local(source)?; - let element_local = self.scratch_i32_local(source)?; - let element_value_local = self.scratch_i32_aux_local(source)?; - - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(index_local)); - - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::LocalGet(ARRAY_PTR_LOCAL)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - - Self::emit_array_element_addr(function, ARRAY_PTR_LOCAL, layout, index_local); - function.instruction(&Instruction::LocalSet(element_local)); - - match &layout.item_abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - function.instruction(&Instruction::LocalGet(element_local)); - emit_scalar_load(function, *ty, 0); - function.instruction(&Instruction::LocalSet(element_value_local)); - self.release_heap_ref_from_local(function, element_value_local, *ty, source)?; - } - AbiTy::Aggregate(_) => { - self.release_aggregate_at_local( - function, - element_local, - &layout.item_abi, - source, - )?; - } - _ => {} - } - - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(index_local)); - function.instruction(&Instruction::Br(0)); - - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - } - - self.dealloc_array_from_local(function, ARRAY_PTR_LOCAL, layout, source) - } - - pub(super) fn emit_array_equality( - &mut self, - function: &mut WasmFunction, - layout: &ArrayRuntimeLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - const LHS_PTR_LOCAL: u32 = 0; - const RHS_PTR_LOCAL: u32 = 1; - - function.instruction(&Instruction::LocalGet(LHS_PTR_LOCAL)); - function.instruction(&Instruction::LocalGet(RHS_PTR_LOCAL)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(LHS_PTR_LOCAL)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::LocalGet(RHS_PTR_LOCAL)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::I32Ne); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - if !layout.item_abi.contains_heap_refs() { - let helper = self.helper_index(HelperFunction::MemoryEq, source)?; - Self::emit_array_data_addr(function, LHS_PTR_LOCAL, layout); - Self::emit_array_data_addr(function, RHS_PTR_LOCAL, layout); - function.instruction(&Instruction::LocalGet(LHS_PTR_LOCAL)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::Call(helper)); - return Ok(()); - } - - let index_local = self.scratch_i32_local(source)?; - let lhs_item_local = self.scratch_i32_aux_local(source)?; - let rhs_item_local = self.object_local(source)?; - - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(index_local)); - - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::LocalGet(LHS_PTR_LOCAL)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - - Self::emit_array_element_addr(function, LHS_PTR_LOCAL, layout, index_local); - function.instruction(&Instruction::LocalSet(lhs_item_local)); - Self::emit_array_element_addr(function, RHS_PTR_LOCAL, layout, index_local); - function.instruction(&Instruction::LocalSet(rhs_item_local)); - self.emit_abi_equality( - function, - lhs_item_local, - rhs_item_local, - 0, - &layout.item_abi, - source, - )?; - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(index_local)); - function.instruction(&Instruction::Br(0)); - - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::I32Const(1)); - Ok(()) - } - - pub(super) fn emit_release_value_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - match abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - self.release_heap_ref_from_local(function, local, *ty, source) - } - AbiTy::Aggregate(_) if abi.contains_heap_refs() => { - self.release_aggregate_at_local(function, local, abi, source) - } - _ => Ok(()), - } - } - - pub(super) fn emit_retain_value_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - match abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - function.instruction(&Instruction::LocalGet(local)); - self.retain_heap_ref_on_stack(function, source)?; - function.instruction(&Instruction::Drop); - Ok(()) - } - AbiTy::Aggregate(_) if abi.contains_heap_refs() => { - self.retain_aggregate_at_local(function, local, abi, source) - } - _ => Ok(()), - } - } - - fn expr( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - if expr.abi.is_aggregate() { - return Err(Diagnostic::error( - "internal error: aggregate expression reached the scalar Wasm emitter", - self.node_range(expr.source), - )); - } - - match &expr.kind { - FunctionKernelValueKind::Local(name) => self.emit_local(function, *name, expr.source), - FunctionKernelValueKind::Capture(field) => { - self.emit_capture_scalar(function, field, expr.source) - } - FunctionKernelValueKind::Clone { value } => { - self.expr(function, value)?; - if let AbiTy::Scalar(ty) = value.abi - && ty.is_heap_ref() - { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - Ok(()) - } - FunctionKernelValueKind::Bool(value) => { - function.instruction(&Instruction::I32Const(i32::from(*value))); - Ok(()) - } - FunctionKernelValueKind::Int(value) => { - if matches!(expr.abi, AbiTy::Scalar(BackendTy::I64)) { - function.instruction(&Instruction::I64Const(*value)); - } else { - function.instruction(&Instruction::I32Const(*value as i32)); - } - Ok(()) - } - FunctionKernelValueKind::Float(value) => { - function.instruction(&Instruction::F64Const((*value).into())); - Ok(()) - } - FunctionKernelValueKind::String(offset) => { - function.instruction(&Instruction::I32Const(*offset as i32)); - Ok(()) - } - FunctionKernelValueKind::StringFromBytes { ptr, len } => { - self.emit_owned_string_from_bytes(function, expr.source, ptr, len) - } - FunctionKernelValueKind::Char(value) => { - function.instruction(&Instruction::I32Const(*value as i32)); - Ok(()) - } - FunctionKernelValueKind::Unit => Ok(()), - FunctionKernelValueKind::StackAddr { frame_slot } => { - self.emit_dest_addr(function, Dest::frame_slot(*frame_slot)) - } - FunctionKernelValueKind::AddrOffset { base, offset } => { - self.expr(function, base)?; - if *offset != 0 { - function.instruction(&Instruction::I32Const(*offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - FunctionKernelValueKind::MemoryRead { addr, access } => { - self.expr(function, addr)?; - if let Some(access) = access { - access.emit_load(function); - } - Ok(()) - } - FunctionKernelValueKind::MemoryWrite { addr, value } => { - let addr_local = self.scratch_i32_local(expr.source)?; - match &value.abi { - AbiTy::Scalar(BackendTy::Unit) => { - self.expr(function, addr)?; - function.instruction(&Instruction::Drop); - self.expr(function, value) - } - AbiTy::Scalar(ty) => { - self.expr(function, value)?; - if value.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, value.source)?; - } - let value_local = match ty { - BackendTy::Float => self.scratch_f64_local(value.source)?, - BackendTy::I64 => self.scratch_i64_local(value.source)?, - _ => self.scratch_i32_aux_local(value.source)?, - }; - function.instruction(&Instruction::LocalSet(value_local)); - self.expr(function, addr)?; - function.instruction(&Instruction::LocalSet(addr_local)); - if ty.is_heap_ref() { - function.instruction(&Instruction::LocalGet(addr_local)); - emit_scalar_load(function, *ty, 0); - self.release_heap_ref_from_stack(function, *ty, expr.source)?; - } - function.instruction(&Instruction::LocalGet(addr_local)); - function.instruction(&Instruction::LocalGet(value_local)); - emit_scalar_store(function, *ty, 0); - Ok(()) - } - AbiTy::Aggregate(layout) => { - let temp = self.temp_dest(value.source)?; - self.emit_expr_into_owner(function, temp, value)?; - self.expr(function, addr)?; - function.instruction(&Instruction::LocalSet(addr_local)); - if value.abi.contains_heap_refs() { - self.release_aggregate_at_local( - function, - addr_local, - &value.abi, - expr.source, - )?; - } - self.copy_dest_to_dest( - function, - Dest::pointer_local(addr_local), - temp, - layout.size, - ) - } - } - } - FunctionKernelValueKind::PointerAdd { ptr, count, stride } => { - self.expr(function, ptr)?; - self.expr(function, count)?; - if *stride != 1 { - function.instruction(&Instruction::I32Const(*stride as i32)); - function.instruction(&Instruction::I32Mul); - } - function.instruction(&Instruction::I32Add); - Ok(()) - } - FunctionKernelValueKind::Array { layout, items } => { - self.emit_array_value(function, expr, layout, items) - } - FunctionKernelValueKind::ArrayRepeat { layout, value, len } => { - self.emit_array_repeat(function, expr, layout, value, len) - } - FunctionKernelValueKind::Struct { fields } => { - self.emit_nominal_struct_value(function, expr, fields) - } - FunctionKernelValueKind::Union { .. } => Err(Diagnostic::error( - "internal error: union aggregate reached the scalar Wasm emitter", - self.node_range(expr.source), - )), - FunctionKernelValueKind::VariantValue { variant } => { - self.emit_nominal_variant_value(function, expr, variant) - } - FunctionKernelValueKind::VariantCall { variant, args } => { - self.emit_nominal_variant_call(function, expr, variant, args) - } - FunctionKernelValueKind::Call { target, args } => { - self.emit_call(function, expr.source, target, args) - } - FunctionKernelValueKind::IndirectCall { callee, signature, args } => { - self.emit_indirect_call(function, expr.source, callee, signature, args) - } - FunctionKernelValueKind::Binary { op, lhs, rhs } => { - self.emit_binary(function, expr.source, *op, lhs, rhs) - } - FunctionKernelValueKind::Prefix { op, expr: inner } => { - self.emit_prefix(function, expr.source, *op, inner) - } - FunctionKernelValueKind::Block { .. } - | FunctionKernelValueKind::Loop { .. } - | FunctionKernelValueKind::Break - | FunctionKernelValueKind::Continue - | FunctionKernelValueKind::If { .. } - | FunctionKernelValueKind::Match { .. } => Err(Diagnostic::error( - "internal error: structured control flow reached raw kernel leaf emission", - self.node_range(expr.source), - )), - FunctionKernelValueKind::Field { base, field } => { - self.emit_field(function, expr.source, base, field) - } - kind => Err(Diagnostic::error( - format!("internal error: unsupported IR node in scalar Wasm emitter: {kind:?}"), - self.node_range(expr.source), - )), - } - } - - fn structured_wasm_expr( - &mut self, - function: &mut WasmFunction, - expr: &StructuredWasmExpr<'db>, - ) -> Result<(), Diagnostic> { - if expr.abi.is_aggregate() { - return Err(Diagnostic::error( - "internal error: aggregate expression reached the stackified scalar Wasm emitter", - self.node_range(expr.source), - )); - } - - match &expr.kind { - StructuredWasmExprKind::Leaf(backend) => self.expr(function, backend), - StructuredWasmExprKind::WasmLocal(local) => { - function.instruction(&Instruction::LocalGet(*local)); - Ok(()) - } - StructuredWasmExprKind::Eqz { value } => { - self.structured_wasm_expr(function, value)?; - match value.abi { - AbiTy::Scalar(BackendTy::I64) => { - function.instruction(&Instruction::I64Eqz); - } - AbiTy::Scalar(_) => { - function.instruction(&Instruction::I32Eqz); - } - AbiTy::Aggregate(_) => { - return Err(Diagnostic::error( - "internal error: aggregate eqz operand reached Wasm emission", - self.node_range(value.source), - )); - } - } - Ok(()) - } - StructuredWasmExprKind::TeeLocal { local, value } => { - self.structured_wasm_expr(function, value)?; - if value.ownership.is_borrowed() - && matches!(value.abi, AbiTy::Scalar(ty) if ty.is_heap_ref()) - { - self.retain_heap_ref_on_stack(function, value.source)?; - } - function.instruction(&Instruction::LocalTee(*local)); - Ok(()) - } - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - self.structured_wasm_expr(function, then_value)?; - self.structured_wasm_expr(function, else_value)?; - self.structured_wasm_expr(function, cond)?; - function.instruction(&Instruction::Select); - Ok(()) - } - StructuredWasmExprKind::Block { region, .. } => { - self.emit_structured_wasm_region(function, region) - } - StructuredWasmExprKind::If { cond, then_region, else_region, result_arity } => self - .emit_structured_wasm_if( - function, - expr.source, - cond, - then_region, - else_region, - *result_arity, - ), - StructuredWasmExprKind::Match { - scrutinee, - arms, - result_arity, - fallback_unreachable, - } => self.emit_structured_wasm_match( - function, - expr.source, - scrutinee, - arms, - *result_arity, - *fallback_unreachable, - ), - StructuredWasmExprKind::Loop { body, .. } => { - function.instruction(&Instruction::Block(BlockType::Empty)); - let break_target = self.control_depth; - self.control_depth += 1; - - function.instruction(&Instruction::Loop(BlockType::Empty)); - let continue_target = self.control_depth; - self.control_depth += 1; - self.loop_stack.push(LoopTargets { - break_target, - continue_target, - scope_depth: self.scope_stack.len(), - }); - - if let Some(body) = body { - self.structured_wasm_expr(function, body)?; - } - function.instruction(&Instruction::Br(0)); - - self.loop_stack.pop(); - self.control_depth -= 1; - function.instruction(&Instruction::End); - - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - StructuredWasmExprKind::Break => self.emit_break(function, expr.source), - StructuredWasmExprKind::Continue => self.emit_continue(function, expr.source), - StructuredWasmExprKind::Unreachable => { - function.instruction(&Instruction::Unreachable); - Ok(()) - } - } - } - - fn structured_wasm_stmt( - &mut self, - function: &mut WasmFunction, - stmt: &StructuredWasmStmt<'db>, - ) -> Result<(), Diagnostic> { - match stmt { - StructuredWasmStmt::Local { name, abi, initializer, ownership_ops } => { - let Some(initializer) = initializer.as_ref() else { - return Ok(()); - }; - let slot = self.layout.slots.get(name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing local slot", - self.node_range((*name).into()), - ) - })?; - match abi { - AbiTy::Scalar(ty) => { - self.structured_wasm_expr(function, initializer)?; - if !ownership_ops.is_empty() { - if let Some(index) = slot.local_index { - function.instruction(&Instruction::LocalSet(index)); - self.emit_local_ownership_ops( - function, - index, - abi, - initializer.source, - ownership_ops, - )?; - } else if !matches!(ty, BackendTy::Unit) { - return Err(Diagnostic::error( - "internal error: scalar local is missing a Wasm local index", - self.node_range((*name).into()), - )); - } - } else { - if initializer.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, initializer.source)?; - } - if let Some(index) = slot.local_index { - function.instruction(&Instruction::LocalSet(index)); - } else if !matches!(ty, BackendTy::Unit) { - return Err(Diagnostic::error( - "internal error: scalar local is missing a Wasm local index", - self.node_range((*name).into()), - )); - } - } - } - AbiTy::Aggregate(_) => { - let dest = self.dest_for_slot(slot)?; - self.emit_structured_wasm_expr_into_owner(function, dest, initializer)?; - if !ownership_ops.is_empty() { - self.emit_dest_ownership_ops( - function, - dest, - abi, - initializer.source, - ownership_ops, - )?; - } - } - } - self.register_scope_local(*name, abi); - Ok(()) - } - StructuredWasmStmt::Pattern(init) => { - self.emit_structured_wasm_binding_init_stmt(function, init) - } - StructuredWasmStmt::Assign { name, abi, value } => { - let slot = self.layout.slots.get(name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing mutable local slot during backend emission", - self.node_range((*name).into()), - ) - })?; - match abi { - AbiTy::Scalar(ty) => { - self.structured_wasm_expr(function, value)?; - if value.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, value.source)?; - } - let Some(index) = slot.local_index else { - return Err(Diagnostic::error( - "internal error: scalar mutable local is missing a Wasm local \ - index", - self.node_range((*name).into()), - )); - }; - if ty.is_heap_ref() { - let value_local = self.scratch_i32_aux_local(value.source)?; - function.instruction(&Instruction::LocalSet(value_local)); - function.instruction(&Instruction::LocalGet(index)); - self.release_heap_ref_from_stack(function, *ty, value.source)?; - function.instruction(&Instruction::LocalGet(value_local)); - function.instruction(&Instruction::LocalSet(index)); - } else { - function.instruction(&Instruction::LocalSet(index)); - } - Ok(()) - } - AbiTy::Aggregate(layout) => { - let temp = self.temp_dest(value.source)?; - self.emit_structured_wasm_expr_into_owner(function, temp, value)?; - if abi.contains_heap_refs() { - self.release_aggregate_at_dest( - function, - self.dest_for_slot(slot)?, - abi, - value.source, - )?; - } - self.copy_dest_to_dest( - function, - self.dest_for_slot(slot)?, - temp, - layout.size, - ) - } - } - } - StructuredWasmStmt::If { - source: _, - abi, - ownership, - cond, - then_region, - else_region, - ownership_ops, - } => self.emit_structured_wasm_stmt_if( - function, - StructuredStmtContext { abi, ownership: *ownership, ownership_ops }, - cond, - then_region, - else_region, - ), - StructuredWasmStmt::Match { - source, - abi, - ownership, - scrutinee, - arms, - fallback_unreachable, - ownership_ops, - } => self.emit_structured_wasm_stmt_match( - function, - *source, - StructuredStmtContext { abi, ownership: *ownership, ownership_ops }, - scrutinee, - arms, - *fallback_unreachable, - ), - StructuredWasmStmt::Expr { expr, ownership_ops } => { - self.emit_structured_wasm_discarded_expr(function, expr, ownership_ops) - } - StructuredWasmStmt::Return { source, value, ownership_ops } => { - self.emit_structured_wasm_return(function, *source, value.as_ref(), ownership_ops) - } - StructuredWasmStmt::SetLocal { local, value, .. } => { - self.structured_wasm_expr(function, value)?; - if value.ownership.is_borrowed() - && matches!(value.abi, AbiTy::Scalar(ty) if ty.is_heap_ref()) - { - self.retain_heap_ref_on_stack(function, value.source)?; - } - function.instruction(&Instruction::LocalSet(*local)); - Ok(()) - } - } - } - - fn emit_structured_wasm_binding_init_stmt( - &mut self, - function: &mut WasmFunction, - init: &StructuredWasmBindingInit<'db>, - ) -> Result<(), Diagnostic> { - self.emit_structured_wasm_binding_init(function, init)?; - self.register_pattern_locals(&init.pattern) - } - - fn emit_structured_wasm_binding_init( - &mut self, - function: &mut WasmFunction, - init: &StructuredWasmBindingInit<'db>, - ) -> Result<(), Diagnostic> { - let source_expr = match &init.source { - StructuredWasmBindingSource::Expr(expr) => expr.source, - StructuredWasmBindingSource::Param { source, .. } => *source, - }; - let value = self.materialize_structured_wasm_binding_source(function, &init.source)?; - self.emit_pattern_bindings(function, &value, &init.pattern, source_expr)?; - self.release_pattern_value(function, &value, source_expr) - } - - fn register_pattern_locals(&mut self, pattern: &BackendPattern) -> Result<(), Diagnostic> { - let mut names = Vec::new(); - pattern.binding_names(&mut names); - for name in names { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing pattern binding slot during ARC registration", - self.node_range(name.into()), - ) - })?; - self.register_scope_local(name, &slot.abi); - } - Ok(()) - } - - fn materialize_structured_wasm_binding_source( - &mut self, - function: &mut WasmFunction, - source: &StructuredWasmBindingSource<'db>, - ) -> Result { - match source { - StructuredWasmBindingSource::Expr(expr) => match &expr.abi { - AbiTy::Scalar(BackendTy::Unit) => Ok(PatternValue { - storage: PatternStorage::Unit, - abi: expr.abi.clone(), - offset: 0, - }), - AbiTy::Scalar(ty) => { - let local = self.pattern_source_local(expr.source)?; - self.structured_wasm_expr(function, expr)?; - if expr.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - function.instruction(&Instruction::LocalSet(local)); - Ok(PatternValue { - storage: PatternStorage::ScalarLocal(local), - abi: expr.abi.clone(), - offset: 0, - }) - } - AbiTy::Aggregate(_) => { - let dest = self.temp_dest(expr.source)?; - self.emit_structured_wasm_expr_into_owner(function, dest, expr)?; - let DestBase::FrameSlot(frame_slot) = dest.base else { - return Err(Diagnostic::error( - "internal error: aggregate pattern source temp must be frame-backed", - self.node_range(expr.source), - )); - }; - Ok(PatternValue { - storage: PatternStorage::FrameSlot(frame_slot), - abi: expr.abi.clone(), - offset: dest.offset, - }) - } - }, - StructuredWasmBindingSource::Param { index, abi, source } => { - let slot = self.layout.raw_params.get(*index).ok_or_else(|| { - Diagnostic::error( - "internal error: missing raw parameter slot during pattern emission", - self.node_range(*source), - ) - })?; - Ok(PatternValue { - storage: match abi { - AbiTy::Scalar(BackendTy::Unit) => PatternStorage::Unit, - AbiTy::Scalar(_) => PatternStorage::ScalarLocal( - slot.local_index.expect("scalar param should have a Wasm local"), - ), - AbiTy::Aggregate(_) => PatternStorage::PointerLocal( - slot.local_index.expect("aggregate param should lower as a pointer"), - ), - }, - abi: abi.clone(), - offset: 0, - }) - } - } - } - - fn emit_pattern_bindings( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - pattern: &BackendPattern, - source: ExprId, - ) -> Result<(), Diagnostic> { - match pattern { - BackendPattern::Binding(name) => { - self.copy_pattern_value_to_binding(function, value, *name, source) - } - BackendPattern::Wildcard | BackendPattern::Literal(_) => Ok(()), - BackendPattern::Tuple(fields) | BackendPattern::Struct(fields) => { - for field in fields { - let field_value = self.project_pattern_field(value, &field.field, source)?; - let field_value = - self.materialize_pattern_value(function, &field_value, source)?; - self.emit_pattern_bindings(function, &field_value, &field.pattern, source)?; - } - Ok(()) - } - BackendPattern::Variant { fields, .. } => { - for field in fields { - let field_value = self.project_pattern_field(value, &field.field, source)?; - let field_value = - self.materialize_pattern_value(function, &field_value, source)?; - self.emit_pattern_bindings(function, &field_value, &field.pattern, source)?; - } - Ok(()) - } - } - } - - fn emit_pattern_test( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - pattern: &BackendPattern, - source: ExprId, - ) -> Result<(), Diagnostic> { - match pattern { - BackendPattern::Binding(_) | BackendPattern::Wildcard => { - function.instruction(&Instruction::I32Const(1)); - Ok(()) - } - BackendPattern::Literal(BackendPatternLiteral::Bool(expected)) => { - self.emit_pattern_scalar(function, value, source)?; - function.instruction(&Instruction::I32Const(i32::from(*expected))); - function.instruction(&Instruction::I32Eq); - Ok(()) - } - BackendPattern::Literal(BackendPatternLiteral::Int(expected)) => { - self.emit_pattern_scalar(function, value, source)?; - if let AbiTy::Scalar(BackendTy::I64) = value.abi { - function.instruction(&Instruction::I64Const(*expected)); - function.instruction(&Instruction::I64Eq); - } else { - function.instruction(&Instruction::I32Const(*expected as i32)); - function.instruction(&Instruction::I32Eq); - } - Ok(()) - } - BackendPattern::Literal(BackendPatternLiteral::Char(expected)) => { - self.emit_pattern_scalar(function, value, source)?; - function.instruction(&Instruction::I32Const(*expected as i32)); - function.instruction(&Instruction::I32Eq); - Ok(()) - } - BackendPattern::Literal(BackendPatternLiteral::String(expected)) => { - let scratch = self.scratch_i32_local(source)?; - self.emit_pattern_scalar(function, value, source)?; - function.instruction(&Instruction::LocalSet(scratch)); - function.instruction(&Instruction::LocalGet(scratch)); - function.instruction(&Instruction::I32Const(*expected as i32)); - function.instruction(&Instruction::Call( - self.helper_index(HelperFunction::StringEq, source)?, - )); - Ok(()) - } - BackendPattern::Tuple(fields) | BackendPattern::Struct(fields) => { - if fields.is_empty() { - function.instruction(&Instruction::I32Const(1)); - return Ok(()); - } - for (index, field) in fields.iter().enumerate() { - let field_value = self.project_pattern_field(value, &field.field, source)?; - let field_value = - self.materialize_pattern_value(function, &field_value, source)?; - self.emit_pattern_test(function, &field_value, &field.pattern, source)?; - if index > 0 { - function.instruction(&Instruction::I32And); - } - } - Ok(()) - } - BackendPattern::Variant { variant, fields } => { - let container = self.addressable_pattern_value(value, source)?; - self.emit_pattern_addr(function, &container, source)?; - MemAccess::enum_tag(0).emit_load(function); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::I32Eq); - for field in fields { - let field_value = - self.project_pattern_field(&container, &field.field, source)?; - let field_value = - self.materialize_pattern_value(function, &field_value, source)?; - self.emit_pattern_test(function, &field_value, &field.pattern, source)?; - function.instruction(&Instruction::I32And); - } - Ok(()) - } - } - } - - fn copy_pattern_value_to_binding( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - name: NameId, - source: ExprId, - ) -> Result<(), Diagnostic> { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing pattern binding slot", - self.node_range(name.into()), - ) - })?; - match &slot.abi { - AbiTy::Scalar(BackendTy::Unit) => Ok(()), - AbiTy::Scalar(ty) => { - self.emit_pattern_scalar(function, value, source)?; - if ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, source)?; - } - if let Some(local) = slot.local_index { - function.instruction(&Instruction::LocalSet(local)); - Ok(()) - } else { - Err(Diagnostic::error( - "internal error: scalar pattern binding is missing a Wasm local index", - self.node_range(name.into()), - )) - } - } - AbiTy::Aggregate(layout) => { - let dest = self.dest_for_slot(slot)?; - self.emit_dest_addr(function, dest)?; - self.emit_pattern_addr(function, value, source)?; - function.instruction(&Instruction::I32Const(layout.size as i32)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - if slot.abi.contains_heap_refs() { - self.retain_aggregate_at_dest(function, dest, &slot.abi, source)?; - } - Ok(()) - } - } - } - - fn project_pattern_field( - &self, - value: &PatternValue, - field: &FieldLayout, - source: ExprId, - ) -> Result { - let storage = match value.storage { - PatternStorage::ScalarLocal(local) => match value.abi { - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(_))) => { - PatternStorage::PointerLocal(local) - } - _ => { - return Err(Diagnostic::error( - "internal error: non-addressable pattern value cannot be projected", - self.node_range(source), - )); - } - }, - PatternStorage::PointerLocal(local) => PatternStorage::PointerLocal(local), - PatternStorage::FrameSlot(slot) => PatternStorage::FrameSlot(slot), - PatternStorage::Unit => { - return Err(Diagnostic::error( - "internal error: unit pattern value cannot be projected", - self.node_range(source), - )); - } - }; - Ok(PatternValue { storage, abi: field.ty.clone(), offset: value.offset + field.offset }) - } - - fn addressable_pattern_value( - &self, - value: &PatternValue, - source: ExprId, - ) -> Result { - match value.storage { - PatternStorage::PointerLocal(_) | PatternStorage::FrameSlot(_) => Ok(value.clone()), - PatternStorage::ScalarLocal(local) - if matches!(value.abi, AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(_)))) => - { - Ok(PatternValue { - storage: PatternStorage::PointerLocal(local), - abi: value.abi.clone(), - offset: value.offset, - }) - } - _ => Err(Diagnostic::error( - "internal error: pattern value is not addressable", - self.node_range(source), - )), - } - } - - fn materialize_pattern_value( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - source: ExprId, - ) -> Result { - let AbiTy::Scalar(ty) = value.abi else { - return Ok(value.clone()); - }; - if matches!(value.storage, PatternStorage::ScalarLocal(_)) && value.offset == 0 { - return Ok(value.clone()); - } - - let local = match ty { - BackendTy::Float => self.scratch_f64_local(source)?, - BackendTy::I64 => self.scratch_i64_local(source)?, - BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_) => { - self.scratch_i32_local(source)? - } - BackendTy::Unit => { - return Ok(PatternValue { - storage: PatternStorage::Unit, - abi: value.abi.clone(), - offset: 0, - }); - } - }; - self.emit_pattern_addr(function, value, source)?; - emit_scalar_load(function, ty, 0); - function.instruction(&Instruction::LocalSet(local)); - Ok(PatternValue { - storage: PatternStorage::ScalarLocal(local), - abi: value.abi.clone(), - offset: 0, - }) - } - - fn emit_pattern_addr( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - source: ExprId, - ) -> Result<(), Diagnostic> { - match value.storage { - PatternStorage::PointerLocal(local) => { - function.instruction(&Instruction::LocalGet(local)); - if value.offset != 0 { - function.instruction(&Instruction::I32Const(value.offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - PatternStorage::FrameSlot(slot) => { - self.emit_dest_addr(function, Dest::frame_slot(slot).with_offset(value.offset)) - } - PatternStorage::ScalarLocal(local) - if matches!(value.abi, AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(_)))) => - { - function.instruction(&Instruction::LocalGet(local)); - if value.offset != 0 { - function.instruction(&Instruction::I32Const(value.offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - _ => Err(Diagnostic::error( - "internal error: attempted to take the address of a scalar pattern value", - self.node_range(source), - )), - } - } - - fn emit_pattern_scalar( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - source: ExprId, - ) -> Result<(), Diagnostic> { - let AbiTy::Scalar(ty) = value.abi else { - return Err(Diagnostic::error( - "internal error: aggregate pattern value reached scalar emission", - self.node_range(source), - )); - }; - if let (PatternStorage::ScalarLocal(local), 0) = (value.storage, value.offset) { - function.instruction(&Instruction::LocalGet(local)); - Ok(()) - } else { - self.emit_pattern_addr(function, value, source)?; - emit_scalar_load(function, ty, 0); - Ok(()) - } - } - - fn release_pattern_value( - &mut self, - function: &mut WasmFunction, - value: &PatternValue, - source: ExprId, - ) -> Result<(), Diagnostic> { - match (&value.abi, value.storage) { - (AbiTy::Scalar(ty), PatternStorage::ScalarLocal(local)) if ty.is_heap_ref() => { - self.release_heap_ref_from_local(function, local, *ty, source) - } - (AbiTy::Aggregate(_), PatternStorage::PointerLocal(local)) => { - self.release_aggregate_at_local(function, local, &value.abi, source) - } - (AbiTy::Aggregate(_), PatternStorage::FrameSlot(slot)) => self - .release_aggregate_at_dest( - function, - Dest::frame_slot(slot).with_offset(value.offset), - &value.abi, - source, - ), - _ => Ok(()), - } - } - - fn emit_structured_wasm_return( - &mut self, - function: &mut WasmFunction, - source: ExprId, - value: Option<&StructuredWasmExpr<'db>>, - ownership_ops: &[StructuredWasmOwnershipOp], - ) -> Result<(), Diagnostic> { - match self.legalized_result_passing() { - LegalizedResultPassing::Unit => { - if let Some(value) = value { - self.structured_wasm_expr(function, value)?; - if !ownership_ops.is_empty() { - if let AbiTy::Scalar(ty) = value.abi { - let consumed = self.emit_stack_ownership_ops( - function, - ty, - value.source, - ownership_ops, - )?; - if !consumed && !matches!(ty, BackendTy::Unit) { - function.instruction(&Instruction::Drop); - } - } - } else { - match value.abi { - AbiTy::Scalar(ty) if value.ownership.is_owned() && ty.is_heap_ref() => { - self.release_heap_ref_from_stack(function, ty, value.source)?; - } - AbiTy::Scalar(BackendTy::Unit) => {} - _ => { - function.instruction(&Instruction::Drop); - } - } - } - } - self.release_scopes_to(function, 0, source)?; - } - LegalizedResultPassing::DirectScalar(BackendTy::Float) => { - if let Some(value) = value { - self.structured_wasm_expr(function, value)?; - let scratch = self.scratch_f64_local(source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_scopes_to(function, 0, source)?; - function.instruction(&Instruction::LocalGet(scratch)); - } else { - self.release_scopes_to(function, 0, source)?; - } - } - LegalizedResultPassing::DirectScalar(ty) => { - if let Some(value) = value { - self.structured_wasm_expr(function, value)?; - if !ownership_ops.is_empty() { - if self.emit_stack_ownership_ops( - function, - ty, - value.source, - ownership_ops, - )? { - return Err(Diagnostic::error( - "internal error: explicit ownership ops consumed a non-unit \ - scalar return value", - self.node_range(source), - )); - } - } else if value.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, value.source)?; - } - let scratch = match ty { - BackendTy::I64 => self.scratch_i64_local(source)?, - _ => self.result_i32_local(source)?, - }; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_scopes_to(function, 0, source)?; - function.instruction(&Instruction::LocalGet(scratch)); - } else { - self.release_scopes_to(function, 0, source)?; - } - } - LegalizedResultPassing::IndirectOutPtr => { - let result_ptr = self.legalized_result_ptr_local().ok_or_else(|| { - Diagnostic::error( - "internal error: aggregate result is missing its out-pointer local", - self.node_range(source), - ) - })?; - if let Some(value) = value { - self.emit_structured_wasm_expr_into_owner( - function, - Dest::pointer_local(result_ptr), - value, - )?; - if !ownership_ops.is_empty() { - self.emit_dest_ownership_ops( - function, - Dest::pointer_local(result_ptr), - &value.abi, - value.source, - ownership_ops, - )?; - } - } - self.release_scopes_to(function, 0, source)?; - } - } - function.instruction(&Instruction::Br(self.return_branch_depth(source)?)); - Ok(()) - } - - fn emit_structured_wasm_region( - &mut self, - function: &mut WasmFunction, - region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - self.push_scope(); - for stmt in ®ion.stmts { - self.structured_wasm_stmt(function, stmt)?; - } - if let Some(tail) = region.tail.as_deref() { - self.structured_wasm_expr(function, tail)?; - } - let source = region.tail.as_deref().map_or(ExprId::ZERO, |tail| tail.source); - self.release_scope(function, source)?; - Ok(()) - } - - fn emit_structured_wasm_region_into_owner( - &mut self, - function: &mut WasmFunction, - dest: Dest, - region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - self.push_scope(); - for stmt in ®ion.stmts { - self.structured_wasm_stmt(function, stmt)?; - } - if let Some(tail) = region.tail.as_deref() { - self.emit_structured_wasm_expr_into_owner(function, dest, tail)?; - } - let source = region.tail.as_deref().map_or(ExprId::ZERO, |tail| tail.source); - self.release_scope(function, source)?; - Ok(()) - } - - fn emit_structured_wasm_region_as_stmt( - &mut self, - function: &mut WasmFunction, - abi: &AbiTy, - ownership: ValueOwnership, - ownership_ops: &[StructuredWasmOwnershipOp], - region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - self.push_scope(); - for stmt in ®ion.stmts { - self.structured_wasm_stmt(function, stmt)?; - } - if let Some(tail) = region.tail.as_deref() { - debug_assert_eq!(&tail.abi, abi); - let tail = StructuredWasmExpr { - source: tail.source, - abi: tail.abi.clone(), - ownership, - kind: tail.kind.clone(), - }; - self.emit_structured_wasm_discarded_expr(function, &tail, ownership_ops)?; - } - let source = region.tail.as_deref().map_or(ExprId::ZERO, |tail| tail.source); - self.release_scope(function, source)?; - Ok(()) - } - - fn emit_structured_wasm_discarded_expr( - &mut self, - function: &mut WasmFunction, - expr: &StructuredWasmExpr<'db>, - ownership_ops: &[StructuredWasmOwnershipOp], - ) -> Result<(), Diagnostic> { - match &expr.abi { - AbiTy::Scalar(BackendTy::Unit) => self.structured_wasm_expr(function, expr), - AbiTy::Scalar(ty) => { - self.structured_wasm_expr(function, expr)?; - if !ownership_ops.is_empty() { - let consumed = - self.emit_stack_ownership_ops(function, *ty, expr.source, ownership_ops)?; - if !consumed { - function.instruction(&Instruction::Drop); - } - } else if expr.ownership.is_owned() && ty.is_heap_ref() { - self.release_heap_ref_from_stack(function, *ty, expr.source)?; - } else { - function.instruction(&Instruction::Drop); - } - Ok(()) - } - AbiTy::Aggregate(_) => { - let dest = self.temp_dest(expr.source)?; - self.emit_structured_wasm_expr_into_owner(function, dest, expr)?; - if !ownership_ops.is_empty() { - self.emit_dest_ownership_ops( - function, - dest, - &expr.abi, - expr.source, - ownership_ops, - )?; - } else if expr.ownership.is_owned() && expr.abi.contains_heap_refs() { - self.release_aggregate_at_dest(function, dest, &expr.abi, expr.source)?; - } - Ok(()) - } - } - } - - fn emit_structured_wasm_if( - &mut self, - function: &mut WasmFunction, - source: ExprId, - cond: &StructuredWasmExpr<'db>, - then_region: &StructuredWasmRegion<'db>, - else_region: &StructuredWasmRegion<'db>, - result_arity: StructuredWasmResultArity, - ) -> Result<(), Diagnostic> { - let block_type = match result_arity { - StructuredWasmResultArity::Unit | StructuredWasmResultArity::Aggregate => { - BlockType::Empty - } - StructuredWasmResultArity::Scalar(ty) => BlockType::Result(ty), - }; - self.structured_wasm_expr(function, cond)?; - function.instruction(&Instruction::If(block_type)); - self.control_depth += 1; - self.emit_structured_wasm_region(function, then_region)?; - if !else_region.stmts.is_empty() || else_region.tail.is_some() { - function.instruction(&Instruction::Else); - self.emit_structured_wasm_region(function, else_region)?; - } - self.control_depth -= 1; - function.instruction(&Instruction::End); - if matches!(result_arity, StructuredWasmResultArity::Aggregate) { - return Err(Diagnostic::error( - "internal error: aggregate if reached scalar stackified emitter", - self.node_range(source), - )); - } - Ok(()) - } - - fn emit_structured_wasm_if_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - cond: &StructuredWasmExpr<'db>, - then_region: &StructuredWasmRegion<'db>, - else_region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - self.structured_wasm_expr(function, cond)?; - function.instruction(&Instruction::If(BlockType::Empty)); - self.control_depth += 1; - self.emit_structured_wasm_region_into_owner(function, dest, then_region)?; - if !else_region.stmts.is_empty() || else_region.tail.is_some() { - function.instruction(&Instruction::Else); - self.emit_structured_wasm_region_into_owner(function, dest, else_region)?; - } - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - - fn emit_structured_wasm_stmt_if( - &mut self, - function: &mut WasmFunction, - context: StructuredStmtContext<'_>, - cond: &StructuredWasmExpr<'db>, - then_region: &StructuredWasmRegion<'db>, - else_region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - self.structured_wasm_expr(function, cond)?; - function.instruction(&Instruction::If(BlockType::Empty)); - self.control_depth += 1; - self.emit_structured_wasm_region_as_stmt( - function, - context.abi, - context.ownership, - context.ownership_ops, - then_region, - )?; - if !else_region.stmts.is_empty() || else_region.tail.is_some() { - function.instruction(&Instruction::Else); - self.emit_structured_wasm_region_as_stmt( - function, - context.abi, - context.ownership, - context.ownership_ops, - else_region, - )?; - } - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - - fn emit_structured_wasm_match( - &mut self, - function: &mut WasmFunction, - source: ExprId, - scrutinee: &StructuredWasmExpr<'db>, - arms: &[StructuredWasmMatchArm<'db>], - result_arity: StructuredWasmResultArity, - fallback_unreachable: bool, - ) -> Result<(), Diagnostic> { - let block_type = match result_arity { - StructuredWasmResultArity::Unit | StructuredWasmResultArity::Aggregate => { - BlockType::Empty - } - StructuredWasmResultArity::Scalar(ty) => BlockType::Result(ty), - }; - let scrutinee_value = self.materialize_structured_wasm_binding_source( - function, - &StructuredWasmBindingSource::Expr(scrutinee.clone()), - )?; - - function.instruction(&Instruction::Block(block_type)); - self.control_depth += 1; - - for arm in arms { - function.instruction(&Instruction::Block(BlockType::Empty)); - self.control_depth += 1; - - self.emit_pattern_test(function, &scrutinee_value, &arm.pattern, source)?; - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::BrIf(0)); - - self.push_scope(); - self.emit_pattern_bindings(function, &scrutinee_value, &arm.pattern, source)?; - self.register_pattern_locals(&arm.pattern)?; - self.emit_structured_wasm_region(function, &arm.body)?; - self.release_scope(function, source)?; - self.release_pattern_value(function, &scrutinee_value, source)?; - function.instruction(&Instruction::Br(1)); - - self.control_depth -= 1; - function.instruction(&Instruction::End); - } - - self.release_pattern_value(function, &scrutinee_value, source)?; - if fallback_unreachable { - function.instruction(&Instruction::Unreachable); - } - - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - - fn emit_structured_wasm_stmt_match( - &mut self, - function: &mut WasmFunction, - source: ExprId, - context: StructuredStmtContext<'_>, - scrutinee: &StructuredWasmExpr<'db>, - arms: &[StructuredWasmMatchArm<'db>], - fallback_unreachable: bool, - ) -> Result<(), Diagnostic> { - let scrutinee_value = self.materialize_structured_wasm_binding_source( - function, - &StructuredWasmBindingSource::Expr(scrutinee.clone()), - )?; - - function.instruction(&Instruction::Block(BlockType::Empty)); - self.control_depth += 1; - - for arm in arms { - function.instruction(&Instruction::Block(BlockType::Empty)); - self.control_depth += 1; - - self.emit_pattern_test(function, &scrutinee_value, &arm.pattern, source)?; - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::BrIf(0)); - - self.push_scope(); - self.emit_pattern_bindings(function, &scrutinee_value, &arm.pattern, source)?; - self.register_pattern_locals(&arm.pattern)?; - self.emit_structured_wasm_region_as_stmt( - function, - context.abi, - context.ownership, - context.ownership_ops, - &arm.body, - )?; - self.release_scope(function, source)?; - self.release_pattern_value(function, &scrutinee_value, source)?; - function.instruction(&Instruction::Br(1)); - - self.control_depth -= 1; - function.instruction(&Instruction::End); - } - - self.release_pattern_value(function, &scrutinee_value, source)?; - if fallback_unreachable { - function.instruction(&Instruction::Unreachable); - } - - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - - fn emit_structured_wasm_match_into( - &mut self, - function: &mut WasmFunction, - source: ExprId, - dest: Dest, - scrutinee: &StructuredWasmExpr<'db>, - arms: &[StructuredWasmMatchArm<'db>], - fallback_unreachable: bool, - ) -> Result<(), Diagnostic> { - let scrutinee_value = self.materialize_structured_wasm_binding_source( - function, - &StructuredWasmBindingSource::Expr(scrutinee.clone()), - )?; - - function.instruction(&Instruction::Block(BlockType::Empty)); - self.control_depth += 1; - - for arm in arms { - function.instruction(&Instruction::Block(BlockType::Empty)); - self.control_depth += 1; - - self.emit_pattern_test(function, &scrutinee_value, &arm.pattern, source)?; - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::BrIf(0)); - - self.push_scope(); - self.emit_pattern_bindings(function, &scrutinee_value, &arm.pattern, source)?; - self.register_pattern_locals(&arm.pattern)?; - self.emit_structured_wasm_region_into_owner(function, dest, &arm.body)?; - self.release_scope(function, source)?; - self.release_pattern_value(function, &scrutinee_value, source)?; - function.instruction(&Instruction::Br(1)); - - self.control_depth -= 1; - function.instruction(&Instruction::End); - } - - self.release_pattern_value(function, &scrutinee_value, source)?; - if fallback_unreachable { - function.instruction(&Instruction::Unreachable); - } - - self.control_depth -= 1; - function.instruction(&Instruction::End); - Ok(()) - } - - fn emit_nominal_struct_value( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - fields: &[FunctionKernelFieldValue<'db>], - ) -> Result<(), Diagnostic> { - let layout = self.nominal_layout_from_expr(expr)?; - let payload_local = self.alloc_nominal_payload(function, expr.source, &layout)?; - self.zero_dest(function, Dest::pointer_local(payload_local), layout.size)?; - for field in fields { - self.store_expr_to_dest( - function, - Dest::pointer_local(payload_local).with_offset(field.field.offset), - &field.value, - &field.field.ty, - )?; - } - function.instruction(&Instruction::LocalGet(payload_local)); - Ok(()) - } - - fn emit_nominal_variant_value( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - variant: &VariantLayout, - ) -> Result<(), Diagnostic> { - let layout = self.nominal_layout_from_expr(expr)?; - let payload_local = self.alloc_nominal_payload(function, expr.source, &layout)?; - self.zero_dest(function, Dest::pointer_local(payload_local), layout.size)?; - function.instruction(&Instruction::LocalGet(payload_local)); - function.instruction(&Instruction::I32Const(variant.tag)); - MemAccess::enum_tag(0).emit_store(function); - function.instruction(&Instruction::LocalGet(payload_local)); - Ok(()) - } - - fn emit_nominal_variant_call( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - variant: &VariantLayout, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let layout = self.nominal_layout_from_expr(expr)?; - let payload_local = self.alloc_nominal_payload(function, expr.source, &layout)?; - self.zero_dest(function, Dest::pointer_local(payload_local), layout.size)?; - function.instruction(&Instruction::LocalGet(payload_local)); - function.instruction(&Instruction::I32Const(variant.tag)); - MemAccess::enum_tag(0).emit_store(function); - for (field, value) in variant.fields.iter().zip(args.iter()) { - self.store_expr_to_dest( - function, - Dest::pointer_local(payload_local).with_offset(field.offset), - value, - &field.ty, - )?; - } - function.instruction(&Instruction::LocalGet(payload_local)); - Ok(()) - } - - pub(super) fn alloc_nominal_payload( - &mut self, - function: &mut WasmFunction, - source: ExprId, - layout: &AggregateLayout, - ) -> Result { - let payload_local = self.nominal_local(source)?; - let alloc = self.runtime_index(RuntimeFunction::Alloc, source)?; - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + layout.size) as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalTee(payload_local)); - function.instruction(&Instruction::I32Const(1)); - MemAccess::arc_ref_count().emit_store(function); - function.instruction(&Instruction::LocalGet(payload_local)); - function.instruction(&Instruction::I32Const(0)); - MemAccess::arc_type_bits().emit_store(function); - function.instruction(&Instruction::LocalGet(payload_local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(payload_local)); - Ok(payload_local) - } - - fn emit_owned_string_from_bytes( - &mut self, - function: &mut WasmFunction, - source: ExprId, - ptr: &FunctionKernelValue<'db>, - len: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - let src_local = self.scratch_i32_local(source)?; - let len_local = self.scratch_i32_aux_local(source)?; - let string_local = self.object_local(source)?; - let alloc = self.runtime_index(RuntimeFunction::Alloc, source)?; - - self.expr(function, ptr)?; - function.instruction(&Instruction::LocalSet(src_local)); - self.expr(function, len)?; - function.instruction(&Instruction::LocalSet(len_local)); - - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + 4) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalTee(string_local)); - function.instruction(&Instruction::I32Const(1)); - MemAccess::arc_ref_count().emit_store(function); - function.instruction(&Instruction::LocalGet(string_local)); - function.instruction(&Instruction::I32Const(0)); - MemAccess::arc_type_bits().emit_store(function); - function.instruction(&Instruction::LocalGet(string_local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalTee(string_local)); - function.instruction(&Instruction::LocalGet(len_local)); - MemAccess::i32(0, 2).emit_store(function); - - function.instruction(&Instruction::LocalGet(string_local)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalGet(src_local)); - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - function.instruction(&Instruction::LocalGet(string_local)); - Ok(()) - } - - fn emit_array_value( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - layout: &ArrayRuntimeLayout, - items: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let len = u32::try_from(items.len()).map_err(|_overflow| { - Diagnostic::error( - "internal error: array literal exceeds the supported length", - self.node_range(expr.source), - ) - })?; - let array_local = self.alloc_array_with_len_const(function, expr.source, layout, len)?; - for (index, item) in items.iter().enumerate() { - let index = u32::try_from(index).map_err(|_overflow| { - Diagnostic::error( - "internal error: array literal index exceeds the supported range", - self.node_range(expr.source), - ) - })?; - let offset = layout - .item_stride - .checked_mul(index) - .and_then(|offset| offset.checked_add(layout.data_offset)) - .ok_or_else(|| { - Diagnostic::error( - "internal error: array literal layout overflowed during emission", - self.node_range(expr.source), - ) - })?; - self.emit_expr_into_owner( - function, - Dest::pointer_local(array_local).with_offset(offset), - item, - )?; - } - function.instruction(&Instruction::LocalGet(array_local)); - Ok(()) - } - - fn emit_array_repeat( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - layout: &ArrayRuntimeLayout, - value: &FunctionKernelValue<'db>, - len: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - let repeat_local = self.array_repeat_local(expr.source)?; - let array_local = self.nominal_local(expr.source)?; - - self.expr(function, len)?; - function.instruction(&Instruction::LocalSet(repeat_local)); - - function.instruction(&Instruction::LocalGet(repeat_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32LtS); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Unreachable); - function.instruction(&Instruction::End); - - self.alloc_array_with_len_local(function, expr.source, layout, repeat_local)?; - - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::I32Const(0)); - MemAccess::array_capacity().emit_store(function); - - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(array_local)); - MemAccess::array_capacity().emit_load(function); - function.instruction(&Instruction::LocalGet(array_local)); - MemAccess::array_len().emit_load(function); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - - function.instruction(&Instruction::LocalGet(array_local)); - if layout.data_offset != 0 { - function.instruction(&Instruction::I32Const(layout.data_offset as i32)); - function.instruction(&Instruction::I32Add); - } - function.instruction(&Instruction::LocalGet(array_local)); - MemAccess::array_capacity().emit_load(function); - if layout.item_stride != 0 { - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - } else { - function.instruction(&Instruction::Drop); - } - function.instruction(&Instruction::LocalSet(repeat_local)); - self.emit_expr_into_owner(function, Dest::pointer_local(repeat_local), value)?; - - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::LocalGet(array_local)); - MemAccess::array_capacity().emit_load(function); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - MemAccess::array_capacity().emit_store(function); - function.instruction(&Instruction::Br(0)); - - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::LocalGet(array_local)); - MemAccess::array_len().emit_load(function); - MemAccess::array_capacity().emit_store(function); - function.instruction(&Instruction::LocalGet(array_local)); - Ok(()) - } - - fn alloc_array_with_len_const( - &mut self, - function: &mut WasmFunction, - source: ExprId, - layout: &ArrayRuntimeLayout, - len: u32, - ) -> Result { - layout.total_size_for_len(len).ok_or_else(|| { - Diagnostic::error( - "internal error: array literal allocation overflowed during Wasm emission", - self.node_range(source), - ) - })?; - let len_local = self.array_repeat_local(source)?; - function.instruction(&Instruction::I32Const(len as i32)); - function.instruction(&Instruction::LocalSet(len_local)); - self.alloc_array_with_len_local(function, source, layout, len_local) - } - - fn alloc_array_with_len_local( - &mut self, - function: &mut WasmFunction, - source: ExprId, - layout: &ArrayRuntimeLayout, - len_local: u32, - ) -> Result { - let array_local = self.nominal_local(source)?; - let alloc = self.runtime_index(RuntimeFunction::Alloc, source)?; - - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + layout.data_offset) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::I32Const(layout.object_align() as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalTee(array_local)); - function.instruction(&Instruction::I32Const(1)); - MemAccess::arc_ref_count().emit_store(function); - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::I32Const(0)); - MemAccess::arc_type_bits().emit_store(function); - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(array_local)); - - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalGet(len_local)); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Const(layout.data_offset as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::MemoryFill(0)); - - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::LocalGet(len_local)); - MemAccess::array_len().emit_store(function); - function.instruction(&Instruction::LocalGet(array_local)); - function.instruction(&Instruction::LocalGet(len_local)); - MemAccess::array_capacity().emit_store(function); - Ok(array_local) - } - - fn nominal_layout_from_expr( - &self, - expr: &FunctionKernelValue<'db>, - ) -> Result { - let bits = match expr.abi { - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(bits))) => bits, - _ => { - return Err(Diagnostic::error( - "internal error: expected nominal ref ABI during nominal emission", - self.node_range(expr.source), - )); - } - }; - let ty = Ty::from_id(salsa::Id::from_bits(u64::from(bits))); - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this nominal value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr.source))) - } - - fn emit_local( - &mut self, - function: &mut WasmFunction, - name: NameId, - source: ExprId, - ) -> Result<(), Diagnostic> { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error("internal error: missing local slot", self.node_range(source)) - })?; - if let Some(index) = slot.local_index { - function.instruction(&Instruction::LocalGet(index)); - } - Ok(()) - } - - fn emit_capture_scalar( - &mut self, - function: &mut WasmFunction, - field: &FieldLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - let env_ptr_local = self.legalized_env_ptr_local().ok_or_else(|| { - Diagnostic::error( - "internal error: closure capture reached scalar emission without an env pointer", - self.node_range(source), - ) - })?; - let AbiTy::Scalar(ty) = field.ty else { - return Err(Diagnostic::error( - "internal error: aggregate capture reached scalar emission", - self.node_range(source), - )); - }; - - function.instruction(&Instruction::LocalGet(env_ptr_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - emit_scalar_load(function, ty, 0); - Ok(()) - } - - fn emit_break( - &mut self, - function: &mut WasmFunction, - source: ExprId, - ) -> Result<(), Diagnostic> { - let Some(targets) = self.loop_stack.last().copied() else { - return Err(Diagnostic::error( - "internal error: loop control reached the Wasm emitter outside a loop", - self.node_range(source), - )); - }; - self.release_scopes_to(function, targets.scope_depth, source)?; - let depth = self.loop_branch_depth(source, true)?; - function.instruction(&Instruction::Br(depth)); - Ok(()) - } - - fn emit_continue( - &mut self, - function: &mut WasmFunction, - source: ExprId, - ) -> Result<(), Diagnostic> { - let Some(targets) = self.loop_stack.last().copied() else { - return Err(Diagnostic::error( - "internal error: loop control reached the Wasm emitter outside a loop", - self.node_range(source), - )); - }; - self.release_scopes_to(function, targets.scope_depth, source)?; - let depth = self.loop_branch_depth(source, false)?; - function.instruction(&Instruction::Br(depth)); - Ok(()) - } - - fn emit_call( - &mut self, - function: &mut WasmFunction, - source: ExprId, - target: &BackendCallTarget<'db>, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let call_site = self.legalized_call_site(source).cloned(); - if let Some(site) = call_site.as_ref() { - let LegalizedCallKind::Direct(legalized_target) = &site.kind else { - return Err(Diagnostic::error( - "internal error: indirect call legalization reached direct emission", - self.node_range(source), - )); - }; - if legalized_target.signature != target.signature { - return Err(Diagnostic::error( - "internal error: direct call legalization drifted from the call signature", - self.node_range(source), - )); - } - } - let result = call_site - .as_ref() - .map_or_else(|| result_passing_for_abi(&target.signature.result), |site| site.result); - if matches!(result, LegalizedResultPassing::IndirectOutPtr) { - return Err(Diagnostic::error( - "internal error: aggregate call result reached the scalar Wasm emitter", - self.node_range(source), - )); - } - - for (index, arg) in args.iter().enumerate() { - let passing = call_site - .as_ref() - .and_then(|site| site.arg_passings.get(index)) - .copied() - .unwrap_or_else(|| arg_passing_for_abi(&arg.abi)); - self.emit_legalized_call_arg(function, arg, passing)?; - } - - function.instruction(&Instruction::Call(self.call_index(target, source)?)); - if matches!(result, LegalizedResultPassing::DirectScalar(BackendTy::Bool)) { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Ne); - } - Ok(()) - } - - fn emit_binary( - &mut self, - function: &mut WasmFunction, - source: ExprId, - op: BackendBinaryOp, - lhs: &FunctionKernelValue<'db>, - rhs: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - if matches!(op, BackendBinaryOp::And | BackendBinaryOp::Or) { - self.expr(function, lhs)?; - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - self.control_depth += 1; - if matches!(op, BackendBinaryOp::And) { - self.expr(function, rhs)?; - } else { - function.instruction(&Instruction::I32Const(1)); - } - function.instruction(&Instruction::Else); - if matches!(op, BackendBinaryOp::And) { - function.instruction(&Instruction::I32Const(0)); - } else { - self.expr(function, rhs)?; - } - self.control_depth -= 1; - function.instruction(&Instruction::End); - return Ok(()); - } - - if matches!(op, BackendBinaryOp::Eq | BackendBinaryOp::Ne) { - match (&lhs.abi, &rhs.abi) { - (AbiTy::Scalar(BackendTy::Unit), AbiTy::Scalar(BackendTy::Unit)) => { - function.instruction(&Instruction::I32Const( - if matches!(op, BackendBinaryOp::Eq) { 1 } else { 0 }, - )); - return Ok(()); - } - (AbiTy::Scalar(BackendTy::Int), AbiTy::Scalar(BackendTy::Int)) - | (AbiTy::Scalar(BackendTy::I64), AbiTy::Scalar(BackendTy::I64)) - | (AbiTy::Scalar(BackendTy::Bool), AbiTy::Scalar(BackendTy::Bool)) - | (AbiTy::Scalar(BackendTy::Char), AbiTy::Scalar(BackendTy::Char)) => { - self.expr(function, lhs)?; - self.expr(function, rhs)?; - function.instruction(match (lhs.abi.clone(), op) { - (AbiTy::Scalar(BackendTy::I64), BackendBinaryOp::Eq) => &Instruction::I64Eq, - (AbiTy::Scalar(BackendTy::I64), BackendBinaryOp::Ne) => &Instruction::I64Ne, - (_, BackendBinaryOp::Eq) => &Instruction::I32Eq, - (_, BackendBinaryOp::Ne) => &Instruction::I32Ne, - _ => unreachable!("equality branch only"), - }); - return Ok(()); - } - (AbiTy::Scalar(BackendTy::Float), AbiTy::Scalar(BackendTy::Float)) => { - self.expr(function, lhs)?; - self.expr(function, rhs)?; - function.instruction(if matches!(op, BackendBinaryOp::Eq) { - &Instruction::F64Eq - } else { - &Instruction::F64Ne - }); - return Ok(()); - } - ( - AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - ) => { - let helper = self.helper_index(HelperFunction::StringEq, source)?; - let lhs_local = self.scratch_i32_local(source)?; - let rhs_local = self.scratch_i32_aux_local(source)?; - self.expr(function, lhs)?; - function.instruction(&Instruction::LocalSet(lhs_local)); - self.expr(function, rhs)?; - function.instruction(&Instruction::LocalSet(rhs_local)); - function.instruction(&Instruction::LocalGet(lhs_local)); - function.instruction(&Instruction::LocalGet(rhs_local)); - function.instruction(&Instruction::Call(helper)); - if lhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - lhs_local, - BackendTy::Ref(RefKind::String), - source, - )?; - } - if rhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - rhs_local, - BackendTy::Ref(RefKind::String), - source, - )?; - } - if matches!(op, BackendBinaryOp::Ne) { - function.instruction(&Instruction::I32Eqz); - } - return Ok(()); - } - ( - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(lhs_bits))), - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(rhs_bits))), - ) if lhs_bits == rhs_bits => { - let helper = self.nominal_eq_index(*lhs_bits, source)?; - let lhs_local = self.scratch_i32_local(source)?; - let rhs_local = self.scratch_i32_aux_local(source)?; - self.expr(function, lhs)?; - function.instruction(&Instruction::LocalSet(lhs_local)); - self.expr(function, rhs)?; - function.instruction(&Instruction::LocalSet(rhs_local)); - function.instruction(&Instruction::LocalGet(lhs_local)); - function.instruction(&Instruction::LocalGet(rhs_local)); - function.instruction(&Instruction::Call(helper)); - if lhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - lhs_local, - self.expr_scalar_ty(lhs)?, - source, - )?; - } - if rhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - rhs_local, - self.expr_scalar_ty(rhs)?, - source, - )?; - } - if matches!(op, BackendBinaryOp::Ne) { - function.instruction(&Instruction::I32Eqz); - } - return Ok(()); - } - ( - AbiTy::Scalar(BackendTy::Ref(RefKind::Array(lhs_bits))), - AbiTy::Scalar(BackendTy::Ref(RefKind::Array(rhs_bits))), - ) if lhs_bits == rhs_bits => { - let helper = self.array_eq_index(*lhs_bits, source)?; - let lhs_local = self.scratch_i32_local(source)?; - let rhs_local = self.scratch_i32_aux_local(source)?; - self.expr(function, lhs)?; - function.instruction(&Instruction::LocalSet(lhs_local)); - self.expr(function, rhs)?; - function.instruction(&Instruction::LocalSet(rhs_local)); - function.instruction(&Instruction::LocalGet(lhs_local)); - function.instruction(&Instruction::LocalGet(rhs_local)); - function.instruction(&Instruction::Call(helper)); - if lhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - lhs_local, - self.expr_scalar_ty(lhs)?, - source, - )?; - } - if rhs.ownership.is_owned() { - self.release_heap_ref_from_local( - function, - rhs_local, - self.expr_scalar_ty(rhs)?, - source, - )?; - } - if matches!(op, BackendBinaryOp::Ne) { - function.instruction(&Instruction::I32Eqz); - } - return Ok(()); - } - (AbiTy::Aggregate(lhs_layout), AbiTy::Aggregate(rhs_layout)) - if lhs_layout == rhs_layout => - { - let lhs_local = self.scratch_i32_local(source)?; - let rhs_local = self.scratch_i32_aux_local(source)?; - self.emit_aggregate_addr(function, lhs)?; - function.instruction(&Instruction::LocalSet(lhs_local)); - self.emit_aggregate_addr(function, rhs)?; - function.instruction(&Instruction::LocalSet(rhs_local)); - self.emit_abi_equality(function, lhs_local, rhs_local, 0, &lhs.abi, source)?; - if lhs.ownership.is_owned() && lhs.abi.contains_heap_refs() { - self.release_aggregate_at_local(function, lhs_local, &lhs.abi, source)?; - } - if rhs.ownership.is_owned() && rhs.abi.contains_heap_refs() { - self.release_aggregate_at_local(function, rhs_local, &rhs.abi, source)?; - } - if matches!(op, BackendBinaryOp::Ne) { - function.instruction(&Instruction::I32Eqz); - } - return Ok(()); - } - _ => { - return Err(Diagnostic::error( - "internal error: unsupported equality operator in staged backend IR", - self.node_range(source), - )); - } - } - } - - let lhs_scalar = self.expr_scalar_ty(lhs)?; - let rhs_scalar = self.expr_scalar_ty(rhs)?; - self.expr(function, lhs)?; - self.expr(function, rhs)?; - - match (lhs_scalar, rhs_scalar, op) { - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Add) => { - function.instruction(&Instruction::I32Add); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Sub) => { - function.instruction(&Instruction::I32Sub); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Mul) => { - function.instruction(&Instruction::I32Mul); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Div) => { - function.instruction(&Instruction::I32DivS); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Rem) => { - function.instruction(&Instruction::I32RemS); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Lt) => { - function.instruction(&Instruction::I32LtS); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Gt) => { - function.instruction(&Instruction::I32GtS); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Le) => { - function.instruction(&Instruction::I32LeS); - } - (BackendTy::Int, BackendTy::Int, BackendBinaryOp::Ge) => { - function.instruction(&Instruction::I32GeS); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Add) => { - function.instruction(&Instruction::F64Add); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Sub) => { - function.instruction(&Instruction::F64Sub); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Mul) => { - function.instruction(&Instruction::F64Mul); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Div) => { - function.instruction(&Instruction::F64Div); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Lt) => { - function.instruction(&Instruction::F64Lt); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Gt) => { - function.instruction(&Instruction::F64Gt); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Le) => { - function.instruction(&Instruction::F64Le); - } - (BackendTy::Float, BackendTy::Float, BackendBinaryOp::Ge) => { - function.instruction(&Instruction::F64Ge); - } - (BackendTy::Char, BackendTy::Char, BackendBinaryOp::Lt) => { - function.instruction(&Instruction::I32LtU); - } - (BackendTy::Char, BackendTy::Char, BackendBinaryOp::Gt) => { - function.instruction(&Instruction::I32GtU); - } - (BackendTy::Char, BackendTy::Char, BackendBinaryOp::Le) => { - function.instruction(&Instruction::I32LeU); - } - (BackendTy::Char, BackendTy::Char, BackendBinaryOp::Ge) => { - function.instruction(&Instruction::I32GeU); - } - _ => { - return Err(Diagnostic::error( - "internal error: unsupported binary operator in staged backend IR", - self.node_range(source), - )); - } - } - - Ok(()) - } - - fn emit_abi_equality( - &mut self, - function: &mut WasmFunction, - lhs_base_local: u32, - rhs_base_local: u32, - base_offset: u32, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - match abi { - AbiTy::Scalar(BackendTy::Int | BackendTy::Bool | BackendTy::Char) => { - Self::emit_scalar_from_base(function, lhs_base_local, base_offset, BackendTy::Int)?; - Self::emit_scalar_from_base(function, rhs_base_local, base_offset, BackendTy::Int)?; - function.instruction(&Instruction::I32Eq); - } - AbiTy::Scalar(BackendTy::I64) => { - Self::emit_scalar_from_base(function, lhs_base_local, base_offset, BackendTy::I64)?; - Self::emit_scalar_from_base(function, rhs_base_local, base_offset, BackendTy::I64)?; - function.instruction(&Instruction::I64Eq); - } - AbiTy::Scalar(BackendTy::Float) => { - Self::emit_scalar_from_base( - function, - lhs_base_local, - base_offset, - BackendTy::Float, - )?; - Self::emit_scalar_from_base( - function, - rhs_base_local, - base_offset, - BackendTy::Float, - )?; - function.instruction(&Instruction::F64Eq); - } - AbiTy::Scalar(BackendTy::Unit) => { - function.instruction(&Instruction::I32Const(1)); - } - AbiTy::Scalar(BackendTy::Ref(RefKind::String)) => { - let helper = self.helper_index(HelperFunction::StringEq, source)?; - Self::emit_scalar_from_base( - function, - lhs_base_local, - base_offset, - BackendTy::Ref(RefKind::String), - )?; - Self::emit_scalar_from_base( - function, - rhs_base_local, - base_offset, - BackendTy::Ref(RefKind::String), - )?; - function.instruction(&Instruction::Call(helper)); - } - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(bits))) => { - let helper = self.nominal_eq_index(*bits, source)?; - Self::emit_scalar_from_base( - function, - lhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Nominal(*bits)), - )?; - Self::emit_scalar_from_base( - function, - rhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Nominal(*bits)), - )?; - function.instruction(&Instruction::Call(helper)); - } - AbiTy::Scalar(BackendTy::Ref(RefKind::Array(bits))) => { - let helper = self.array_eq_index(*bits, source)?; - Self::emit_scalar_from_base( - function, - lhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Array(*bits)), - )?; - Self::emit_scalar_from_base( - function, - rhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Array(*bits)), - )?; - function.instruction(&Instruction::Call(helper)); - } - AbiTy::Scalar(BackendTy::Ref(_)) => { - Self::emit_scalar_from_base( - function, - lhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Opaque), - )?; - Self::emit_scalar_from_base( - function, - rhs_base_local, - base_offset, - BackendTy::Ref(RefKind::Opaque), - )?; - function.instruction(&Instruction::I32Eq); - } - AbiTy::Aggregate(layout) if !abi.contains_heap_refs() => { - let helper = self.helper_index(HelperFunction::MemoryEq, source)?; - let size = i32::try_from(layout.size).map_err(|_overflow| { - Diagnostic::error( - "internal error: aggregate comparison exceeds the supported size", - self.node_range(source), - ) - })?; - Self::emit_base_plus_offset(function, lhs_base_local, base_offset); - Self::emit_base_plus_offset(function, rhs_base_local, base_offset); - function.instruction(&Instruction::I32Const(size)); - function.instruction(&Instruction::Call(helper)); - } - AbiTy::Aggregate(layout) => match &layout.kind { - AggregateKind::Fields(fields) => { - function.instruction(&Instruction::I32Const(1)); - for field in fields { - self.emit_abi_equality( - function, - lhs_base_local, - rhs_base_local, - base_offset + field.offset, - &field.ty, - source, - )?; - function.instruction(&Instruction::I32And); - } - } - AggregateKind::Enum(enum_layout) => { - Self::emit_base_plus_offset(function, lhs_base_local, base_offset); - MemAccess::enum_tag(0).emit_load(function); - Self::emit_base_plus_offset(function, rhs_base_local, base_offset); - MemAccess::enum_tag(0).emit_load(function); - function.instruction(&Instruction::I32Eq); - self.emit_enum_variant_equality( - function, - lhs_base_local, - rhs_base_local, - base_offset, - enum_layout, - source, - )?; - function.instruction(&Instruction::I32And); - } - AggregateKind::FunctionValue => { - function.instruction(&Instruction::I32Const(1)); - for offset in [0, 4] { - Self::emit_base_plus_offset(function, lhs_base_local, base_offset + offset); - MemAccess::function_word(0).emit_load(function); - Self::emit_base_plus_offset(function, rhs_base_local, base_offset + offset); - MemAccess::function_word(0).emit_load(function); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::I32And); - } - } - }, - } - Ok(()) - } - - fn emit_enum_variant_equality( - &mut self, - function: &mut WasmFunction, - lhs_base_local: u32, - rhs_base_local: u32, - base_offset: u32, - enum_layout: &EnumLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - for variant in enum_layout.variants.iter().rev() { - Self::emit_base_plus_offset(function, lhs_base_local, base_offset); - MemAccess::enum_tag(0).emit_load(function); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Result(ValType::I32))); - function.instruction(&Instruction::I32Const(1)); - for field in &variant.fields { - self.emit_abi_equality( - function, - lhs_base_local, - rhs_base_local, - base_offset + field.offset, - &field.ty, - source, - )?; - function.instruction(&Instruction::I32And); - } - function.instruction(&Instruction::Else); - } - function.instruction(&Instruction::I32Const(0)); - for _ in 0..enum_layout.variants.len() { - function.instruction(&Instruction::End); - } - Ok(()) - } - - fn emit_scalar_from_base( - function: &mut WasmFunction, - base_local: u32, - offset: u32, - ty: BackendTy, - ) -> Result<(), Diagnostic> { - Self::emit_base_plus_offset(function, base_local, offset); - emit_scalar_load(function, ty, 0); - Ok(()) - } - - fn emit_base_plus_offset(function: &mut WasmFunction, base_local: u32, offset: u32) { - function.instruction(&Instruction::LocalGet(base_local)); - if offset != 0 { - function.instruction(&Instruction::I32Const(offset as i32)); - function.instruction(&Instruction::I32Add); - } - } - - fn emit_array_data_addr( - function: &mut WasmFunction, - array_local: u32, - layout: &ArrayRuntimeLayout, - ) { - function.instruction(&Instruction::LocalGet(array_local)); - if layout.data_offset != 0 { - function.instruction(&Instruction::I32Const(layout.data_offset as i32)); - function.instruction(&Instruction::I32Add); - } - } - - fn emit_array_element_addr( - function: &mut WasmFunction, - array_local: u32, - layout: &ArrayRuntimeLayout, - index_local: u32, - ) { - Self::emit_array_data_addr(function, array_local, layout); - if layout.item_stride != 0 { - function.instruction(&Instruction::LocalGet(index_local)); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Add); - } - } - - fn emit_field( - &mut self, - function: &mut WasmFunction, - source: ExprId, - base: &FunctionKernelValue<'db>, - field: &FieldLayout, - ) -> Result<(), Diagnostic> { - let AbiTy::Scalar(field_ty) = &field.ty else { - return Err(Diagnostic::error( - "internal error: aggregate field reached the scalar Wasm emitter", - self.node_range(source), - )); - }; - - let scratch = self.scratch_i32_local(source)?; - self.emit_value_addr(function, base)?; - function.instruction(&Instruction::LocalSet(scratch)); - function.instruction(&Instruction::LocalGet(scratch)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - emit_scalar_load(function, *field_ty, 0); - if base.ownership.is_owned() { - if field_ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, source)?; - } - self.emit_release_value_from_local(function, scratch, &base.abi, source)?; - } - Ok(()) - } - - fn emit_value_addr( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - if expr.abi.is_aggregate() { - return self.emit_aggregate_addr(function, expr); - } - match expr.abi { - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(_))) => self.expr(function, expr), - _ => Err(Diagnostic::error( - "internal error: non-addressable value reached payload-address emission", - self.node_range(expr.source), - )), - } - } - - fn emit_prefix( - &mut self, - function: &mut WasmFunction, - source: ExprId, - op: BackendPrefixOp, - inner: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - match op { - BackendPrefixOp::Not => { - self.expr(function, inner)?; - function.instruction(&Instruction::I32Eqz); - } - BackendPrefixOp::Neg => match self.expr_scalar_ty(inner)? { - BackendTy::Int => { - function.instruction(&Instruction::I32Const(0)); - self.expr(function, inner)?; - function.instruction(&Instruction::I32Sub); - } - BackendTy::Float => { - function.instruction(&Instruction::F64Const(0.0.into())); - self.expr(function, inner)?; - function.instruction(&Instruction::F64Sub); - } - _ => { - return Err(Diagnostic::error( - "internal error: unsupported prefix operator in staged backend IR", - self.node_range(source), - )); - } - }, - } - - Ok(()) - } - - fn emit_legalized_call_arg( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - passing: LegalizedArgPassing, - ) -> Result<(), Diagnostic> { - match passing { - LegalizedArgPassing::Direct => { - let AbiTy::Scalar(ty) = &expr.abi else { - return Err(Diagnostic::error( - "internal error: aggregate argument reached direct-call lowering", - self.node_range(expr.source), - )); - }; - self.expr(function, expr)?; - if expr.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - Ok(()) - } - LegalizedArgPassing::ByAddress => { - if !expr.abi.is_aggregate() { - return Err(Diagnostic::error( - "internal error: scalar argument reached by-address lowering", - self.node_range(expr.source), - )); - } - self.emit_aggregate_addr(function, expr)?; - if expr.ownership.is_borrowed() && expr.abi.contains_heap_refs() { - let scratch = self.scratch_i32_local(expr.source)?; - function.instruction(&Instruction::LocalTee(scratch)); - self.retain_aggregate_at_local(function, scratch, &expr.abi, expr.source)?; - } - Ok(()) - } - } - } - - fn emit_aggregate_addr( - &mut self, - function: &mut WasmFunction, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - match &expr.kind { - FunctionKernelValueKind::Local(name) => { - self.emit_name_addr(function, *name, expr.source) - } - FunctionKernelValueKind::Field { base, field } - if field.ty.is_aggregate() && !base.ownership.is_owned() => - { - self.emit_value_addr(function, base)?; - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - FunctionKernelValueKind::Capture(field) if field.ty.is_aggregate() => { - let env_ptr_local = self.legalized_env_ptr_local().ok_or_else(|| { - Diagnostic::error( - "internal error: closure capture reached aggregate emission without an \ - env pointer", - self.node_range(expr.source), - ) - })?; - function.instruction(&Instruction::LocalGet(env_ptr_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - _ => { - let dest = self.temp_dest(expr.source)?; - self.emit_expr_into(function, dest, expr)?; - self.emit_dest_addr(function, dest) - } - } - } - - fn emit_expr_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - let layout = expr.abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: scalar expression reached the aggregate Wasm emitter", - self.node_range(expr.source), - ) - })?; - - match &expr.kind { - FunctionKernelValueKind::Field { base, field } - if field.ty.is_aggregate() && base.ownership.is_owned() => - { - self.emit_owned_aggregate_field_into(function, dest, expr, base, field, layout) - } - FunctionKernelValueKind::Local(_) - | FunctionKernelValueKind::Field { .. } - | FunctionKernelValueKind::Capture(_) => { - self.copy_aggregate_expr_to_dest(function, dest, expr, layout.size) - } - FunctionKernelValueKind::Clone { value } => { - self.emit_expr_into_owner(function, dest, value) - } - FunctionKernelValueKind::MemoryRead { addr, .. } => { - self.emit_dest_addr(function, dest)?; - self.expr(function, addr)?; - function.instruction(&Instruction::I32Const(layout.size as i32)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - Ok(()) - } - FunctionKernelValueKind::Tuple { fields } => { - self.emit_tuple_into(function, dest, layout, fields) - } - FunctionKernelValueKind::Struct { fields } => { - self.emit_struct_expr_into(function, dest, layout, fields) - } - FunctionKernelValueKind::Union { variant, value } => { - self.emit_union_into(function, dest, layout, variant, value.as_deref()) - } - FunctionKernelValueKind::VariantValue { variant } => { - self.emit_variant_value_into(function, dest, layout, variant) - } - FunctionKernelValueKind::VariantCall { variant, args } => { - self.emit_variant_call_into(function, dest, layout, variant, args) - } - FunctionKernelValueKind::Call { target, args } => { - self.emit_call_into(function, dest, expr.source, target, args) - } - FunctionKernelValueKind::IndirectCall { callee, signature, args } => { - self.emit_indirect_call_into(function, dest, expr.source, callee, signature, args) - } - FunctionKernelValueKind::FunctionValue { target } => { - self.emit_function_value_into(function, dest, expr.source, target) - } - FunctionKernelValueKind::ClosureValue { target, env } => { - self.emit_closure_value_into(function, dest, expr.source, target, env) - } - FunctionKernelValueKind::Block { .. } - | FunctionKernelValueKind::If { .. } - | FunctionKernelValueKind::Match { .. } => Err(Diagnostic::error( - "internal error: structured control flow reached raw aggregate kernel emission", - self.node_range(expr.source), - )), - kind => Err(Diagnostic::error( - format!("internal error: unsupported IR node in aggregate Wasm emitter: {kind:?}"), - self.node_range(expr.source), - )), - } - } - - fn emit_structured_wasm_expr_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &StructuredWasmExpr<'db>, - ) -> Result<(), Diagnostic> { - let layout = expr.abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: scalar stackified expression reached the aggregate Wasm emitter", - self.node_range(expr.source), - ) - })?; - - match &expr.kind { - StructuredWasmExprKind::Leaf(backend) => self.emit_expr_into(function, dest, backend), - StructuredWasmExprKind::Block { region, .. } => { - self.emit_structured_wasm_region_into_owner(function, dest, region) - } - StructuredWasmExprKind::If { cond, then_region, else_region, .. } => { - self.emit_structured_wasm_if_into(function, dest, cond, then_region, else_region) - } - StructuredWasmExprKind::Match { scrutinee, arms, fallback_unreachable, .. } => self - .emit_structured_wasm_match_into( - function, - expr.source, - dest, - scrutinee, - arms, - *fallback_unreachable, - ), - StructuredWasmExprKind::Unreachable => { - function.instruction(&Instruction::Unreachable); - Ok(()) - } - kind => Err(Diagnostic::error( - format!( - "internal error: unsupported stackified node in aggregate Wasm emitter: \ - {kind:?}" - ), - self.node_range(expr.source), - )), - }?; - - if layout.size == 0 { - return Ok(()); - } - Ok(()) - } - - fn emit_name_addr( - &mut self, - function: &mut WasmFunction, - name: NameId, - source: ExprId, - ) -> Result<(), Diagnostic> { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error("internal error: missing local slot", self.node_range(source)) - })?; - if !slot.abi.is_aggregate() && !self.source_map.is_mutable_binding(name) { - return Err(Diagnostic::error( - "internal error: scalar name reached the aggregate address emitter", - self.node_range(source), - )); - } - if let Some(local_index) = slot.local_index { - function.instruction(&Instruction::LocalGet(local_index)); - return Ok(()); - } - self.emit_dest_addr(function, self.dest_for_slot(slot)?) - } - - fn copy_aggregate_expr_to_dest( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &FunctionKernelValue<'db>, - size: u32, - ) -> Result<(), Diagnostic> { - self.emit_dest_addr(function, dest)?; - self.emit_aggregate_addr(function, expr)?; - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - Ok(()) - } - - fn copy_dest_to_dest( - &mut self, - function: &mut WasmFunction, - dest: Dest, - src: Dest, - size: u32, - ) -> Result<(), Diagnostic> { - self.emit_dest_addr(function, dest)?; - self.emit_dest_addr(function, src)?; - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - Ok(()) - } - - fn emit_owned_aggregate_field_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &FunctionKernelValue<'db>, - base: &FunctionKernelValue<'db>, - field: &FieldLayout, - layout: &AggregateLayout, - ) -> Result<(), Diagnostic> { - let scratch = self.scratch_i32_local(expr.source)?; - self.emit_value_addr(function, base)?; - function.instruction(&Instruction::LocalSet(scratch)); - - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::LocalGet(scratch)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - function.instruction(&Instruction::I32Const(layout.size as i32)); - function.instruction(&Instruction::MemoryCopy { src_mem: 0, dst_mem: 0 }); - - if expr.abi.contains_heap_refs() { - self.retain_aggregate_at_dest(function, dest, &expr.abi, expr.source)?; - } - self.emit_release_value_from_local(function, scratch, &base.abi, expr.source) - } - - fn emit_tuple_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - layout: &AggregateLayout, - fields: &[FunctionKernelFieldValue<'db>], - ) -> Result<(), Diagnostic> { - self.zero_dest(function, dest, layout.size)?; - for field in fields { - self.store_expr_to_dest( - function, - dest.with_offset(field.field.offset), - &field.value, - &field.field.ty, - )?; - } - Ok(()) - } - - fn emit_struct_expr_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - layout: &AggregateLayout, - fields: &[FunctionKernelFieldValue<'db>], - ) -> Result<(), Diagnostic> { - self.zero_dest(function, dest, layout.size)?; - for field in fields { - self.store_expr_to_dest( - function, - dest.with_offset(field.field.offset), - &field.value, - &field.field.ty, - )?; - } - Ok(()) - } - - fn emit_union_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - layout: &AggregateLayout, - variant: &VariantLayout, - value: Option<&FunctionKernelValue<'db>>, - ) -> Result<(), Diagnostic> { - self.zero_dest(function, dest, layout.size)?; - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::I32Const(variant.tag)); - MemAccess::enum_tag(0).emit_store(function); - if let (Some(field), Some(value)) = (variant.fields.first(), value) { - self.store_expr_to_dest(function, dest.with_offset(field.offset), value, &field.ty)?; - } - Ok(()) - } - - fn emit_variant_value_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - layout: &AggregateLayout, - variant: &VariantLayout, - ) -> Result<(), Diagnostic> { - self.zero_dest(function, dest, layout.size)?; - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::I32Const(variant.tag)); - MemAccess::enum_tag(0).emit_store(function); - Ok(()) - } - - fn emit_variant_call_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - layout: &AggregateLayout, - variant: &VariantLayout, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - self.zero_dest(function, dest, layout.size)?; - - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::I32Const(variant.tag)); - MemAccess::enum_tag(0).emit_store(function); - - for (field, arg) in variant.fields.iter().zip(args.iter()) { - self.store_expr_to_dest(function, dest.with_offset(field.offset), arg, &field.ty)?; - } - - Ok(()) - } - - fn emit_call_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - source: ExprId, - target: &BackendCallTarget<'db>, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let call_site = self.legalized_call_site(source).cloned(); - if let Some(site) = call_site.as_ref() { - let LegalizedCallKind::Direct(legalized_target) = &site.kind else { - return Err(Diagnostic::error( - "internal error: indirect call legalization reached direct aggregate emission", - self.node_range(source), - )); - }; - if legalized_target.signature != target.signature { - return Err(Diagnostic::error( - "internal error: direct aggregate call legalization drifted from the call \ - signature", - self.node_range(source), - )); - } - } - let result = call_site - .as_ref() - .map_or_else(|| result_passing_for_abi(&target.signature.result), |site| site.result); - if !matches!(result, LegalizedResultPassing::IndirectOutPtr) { - return Err(Diagnostic::error( - "internal error: scalar call result reached the aggregate Wasm emitter", - self.node_range(source), - )); - } - - self.emit_dest_addr(function, dest)?; - for (index, arg) in args.iter().enumerate() { - let passing = call_site - .as_ref() - .and_then(|site| site.arg_passings.get(index)) - .copied() - .unwrap_or_else(|| arg_passing_for_abi(&arg.abi)); - self.emit_legalized_call_arg(function, arg, passing)?; - } - function.instruction(&Instruction::Call(self.call_index(target, source)?)); - Ok(()) - } - - fn emit_indirect_call( - &mut self, - function: &mut WasmFunction, - source: ExprId, - callee: &FunctionKernelValue<'db>, - signature: &FunctionSignature, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let call_site = self.legalized_call_site(source).cloned(); - let (indirect_signature, callable_representation, result) = match call_site.as_ref() { - Some(site) => { - let LegalizedCallKind::Indirect(indirect_signature) = &site.kind else { - return Err(Diagnostic::error( - "internal error: direct call legalization reached indirect emission", - self.node_range(source), - )); - }; - (indirect_signature, site.callable_representation, site.result) - } - None => ( - signature, - self.legalized_callable_representation(), - result_passing_for_abi(&signature.result), - ), - }; - if matches!(result, LegalizedResultPassing::IndirectOutPtr) { - return Err(Diagnostic::error( - "internal error: aggregate indirect call result reached the scalar Wasm emitter", - self.node_range(source), - )); - } - - let scratch = self.scratch_i32_local(source)?; - self.emit_aggregate_addr(function, callee)?; - function.instruction(&Instruction::LocalSet(scratch)); - - Self::emit_callable_env_arg(function, scratch, callable_representation); - for (index, arg) in args.iter().enumerate() { - let passing = call_site - .as_ref() - .and_then(|site| site.arg_passings.get(index)) - .copied() - .unwrap_or_else(|| arg_passing_for_abi(&arg.abi)); - self.emit_legalized_call_arg(function, arg, passing)?; - } - Self::emit_callable_table_index(function, scratch, callable_representation); - function.instruction(&Instruction::CallIndirect { - type_index: self.callable_type_index(indirect_signature, source)?, - table_index: 0, - }); - if callee.ownership.is_owned() && callee.abi.contains_heap_refs() { - self.release_aggregate_at_local(function, scratch, &callee.abi, source)?; - } - if matches!(result, LegalizedResultPassing::DirectScalar(BackendTy::Bool)) { - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Ne); - } - Ok(()) - } - - fn emit_indirect_call_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - source: ExprId, - callee: &FunctionKernelValue<'db>, - signature: &FunctionSignature, - args: &[FunctionKernelValue<'db>], - ) -> Result<(), Diagnostic> { - let call_site = self.legalized_call_site(source).cloned(); - let (indirect_signature, callable_representation, result) = match call_site.as_ref() { - Some(site) => { - let LegalizedCallKind::Indirect(indirect_signature) = &site.kind else { - return Err(Diagnostic::error( - "internal error: direct call legalization reached indirect aggregate \ - emission", - self.node_range(source), - )); - }; - (indirect_signature, site.callable_representation, site.result) - } - None => ( - signature, - self.legalized_callable_representation(), - result_passing_for_abi(&signature.result), - ), - }; - if !matches!(result, LegalizedResultPassing::IndirectOutPtr) { - return Err(Diagnostic::error( - "internal error: scalar indirect call result reached the aggregate Wasm emitter", - self.node_range(source), - )); - } - - let scratch = self.scratch_i32_local(source)?; - self.emit_aggregate_addr(function, callee)?; - function.instruction(&Instruction::LocalSet(scratch)); - - Self::emit_callable_env_arg(function, scratch, callable_representation); - self.emit_dest_addr(function, dest)?; - for (index, arg) in args.iter().enumerate() { - let passing = call_site - .as_ref() - .and_then(|site| site.arg_passings.get(index)) - .copied() - .unwrap_or_else(|| arg_passing_for_abi(&arg.abi)); - self.emit_legalized_call_arg(function, arg, passing)?; - } - Self::emit_callable_table_index(function, scratch, callable_representation); - function.instruction(&Instruction::CallIndirect { - type_index: self.callable_type_index(indirect_signature, source)?, - table_index: 0, - }); - if callee.ownership.is_owned() && callee.abi.contains_heap_refs() { - self.release_aggregate_at_local(function, scratch, &callee.abi, source)?; - } - Ok(()) - } - - fn emit_function_value_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - source: ExprId, - target: &FunctionValueTarget<'db>, - ) -> Result<(), Diagnostic> { - match self.legalized_callable_representation() { - LegalizedCallableRepresentation::HandleAndTable => self.emit_function_value_pair( - function, - dest, - self.table_slot(target, source)?, - None, - ), - } - } - - fn emit_closure_value_into( - &mut self, - function: &mut WasmFunction, - dest: Dest, - source: ExprId, - target: &ClosureInstanceKey<'db>, - env: &FunctionKernelClosureEnvInit<'db>, - ) -> Result<(), Diagnostic> { - if !matches!( - self.legalized_callable_representation(), - LegalizedCallableRepresentation::HandleAndTable - ) { - return Err(Diagnostic::error( - "internal error: unsupported callable representation for closure value emission", - self.node_range(source), - )); - } - let scratch = self.scratch_i32_local(source)?; - if env.layout.size == 0 { - return self.emit_function_value_pair( - function, - dest, - self.table_slot(&FunctionValueTarget::Closure(target.clone()), source)?, - None, - ); - } - - let alloc = self.runtime_index(RuntimeFunction::Alloc, source)?; - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + env.layout.size) as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(alloc)); - function.instruction(&Instruction::LocalTee(scratch)); - function.instruction(&Instruction::I32Const(1)); - MemAccess::arc_ref_count().emit_store(function); - function.instruction(&Instruction::LocalGet(scratch)); - function.instruction(&Instruction::I32Const(0)); - MemAccess::arc_type_bits().emit_store(function); - function.instruction(&Instruction::LocalGet(scratch)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(scratch)); - - self.zero_dest(function, Dest::pointer_local(scratch), env.layout.size)?; - for field in &env.fields { - self.store_expr_to_dest( - function, - Dest::pointer_local(scratch).with_offset(field.field.offset), - &field.value, - &field.field.ty, - )?; - } - - self.emit_function_value_pair( - function, - dest, - self.table_slot(&FunctionValueTarget::Closure(target.clone()), source)?, - Some(scratch), - ) - } - - fn emit_function_value_pair( - &self, - function: &mut WasmFunction, - dest: Dest, - table_slot: u32, - env_ptr_local: Option, - ) -> Result<(), Diagnostic> { - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::I32Const(table_slot as i32)); - MemAccess::function_word(0).emit_store(function); - self.emit_dest_addr(function, dest.with_offset(4))?; - match env_ptr_local { - Some(local) => { - function.instruction(&Instruction::LocalGet(local)); - } - None => { - function.instruction(&Instruction::I32Const(0)); - } - }; - MemAccess::function_word(0).emit_store(function); - Ok(()) - } - - fn emit_callable_env_arg( - function: &mut WasmFunction, - callable_local: u32, - callable_representation: LegalizedCallableRepresentation, - ) { - match callable_representation { - LegalizedCallableRepresentation::HandleAndTable => { - function.instruction(&Instruction::LocalGet(callable_local)); - MemAccess::function_word(4).emit_load(function); - } - } - } - - fn emit_callable_table_index( - function: &mut WasmFunction, - callable_local: u32, - callable_representation: LegalizedCallableRepresentation, - ) { - match callable_representation { - LegalizedCallableRepresentation::HandleAndTable => { - function.instruction(&Instruction::LocalGet(callable_local)); - MemAccess::function_word(0).emit_load(function); - } - } - } - - fn expr_scalar_ty(&self, expr: &FunctionKernelValue<'db>) -> Result { - match expr.abi { - AbiTy::Scalar(ty) => Ok(ty), - AbiTy::Aggregate(_) => Err(Diagnostic::error( - "internal error: aggregate expression reached a scalar type query", - self.node_range(expr.source), - )), - } - } - - fn store_expr_to_dest( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &FunctionKernelValue<'db>, - _ty: &AbiTy, - ) -> Result<(), Diagnostic> { - self.emit_expr_into_owner(function, dest, expr) - } - - fn emit_dest_addr(&self, function: &mut WasmFunction, dest: Dest) -> Result<(), Diagnostic> { - match dest.base { - DestBase::PointerLocal(local) => { - function.instruction(&Instruction::LocalGet(local)); - if dest.offset != 0 { - function.instruction(&Instruction::I32Const(dest.offset as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - DestBase::FrameSlot(frame_slot) => { - let frame_base_local = self.layout.frame_base_local().ok_or_else(|| { - Diagnostic::error( - "internal error: frame-backed address requested without a frame base", - self.backend.function_range(self.location), - ) - })?; - function.instruction(&Instruction::LocalGet(frame_base_local)); - let frame_offset = self.layout.frame_slot_offset(frame_slot).ok_or_else(|| { - Diagnostic::error( - "internal error: frame-backed address referenced a missing frame slot", - self.backend.function_range(self.location), - ) - })?; - let total = frame_offset + dest.offset; - if total != 0 { - function.instruction(&Instruction::I32Const(total as i32)); - function.instruction(&Instruction::I32Add); - } - Ok(()) - } - } - } - - fn zero_dest( - &self, - function: &mut WasmFunction, - dest: Dest, - size: u32, - ) -> Result<(), Diagnostic> { - if size == 0 { - return Ok(()); - } - - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::I32Const(size as i32)); - function.instruction(&Instruction::MemoryFill(0)); - Ok(()) - } - - fn dest_for_slot(&self, slot: &LocalSlot) -> Result { - if let Some(local_index) = slot.local_index { - return Ok(Dest::pointer_local(local_index)); - } - if let Some(frame_slot) = slot.frame_slot { - return Ok(Dest::frame_slot(frame_slot)); - } - Err(Diagnostic::error( - "internal error: aggregate slot is missing storage", - self.backend.function_range(self.location), - )) - } - - fn temp_dest(&self, expr: ExprId) -> Result { - let temp = self.layout.temps.get(&expr).ok_or_else(|| { - Diagnostic::error( - "internal error: aggregate expression is missing a temporary slot", - self.node_range(expr), - ) - })?; - Ok(Dest::frame_slot(temp.frame_slot)) - } - - fn pattern_source_local(&self, expr: ExprId) -> Result { - self.layout.pattern_scalar_locals.get(&expr).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: scalar pattern source is missing a temporary local", - self.node_range(expr), - ) - }) - } - - fn call_index( - &self, - target: &BackendCallTarget<'db>, - source: ExprId, - ) -> Result { - if let Some(index) = self - .resolved_wasm_refs - .and_then(|resolved| resolved.direct_calls.get(&source)) - .map(|resolved| resolved.function_index) - { - return Ok(index); - } - match &target.callable { - BackendCallable::Runtime(function) => { - self.runtime_indices.get(function).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing function index for runtime call target", - self.node_range(source), - ) - }) - } - BackendCallable::StageIntrinsic(intrinsic) => { - self.stage_indices.get(intrinsic).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing function index for stage intrinsic call target", - self.node_range(source), - ) - }) - } - BackendCallable::Function(instance) => { - self.function_indices.get(instance).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing function index for lowered call target", - self.node_range(source), - ) - }) - } - } - } - - fn runtime_index(&self, runtime: RuntimeFunction, source: ExprId) -> Result { - self.runtime_indices.get(&runtime).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing runtime function index", - self.node_range(source), - ) - }) - } - - fn callable_type_index( - &self, - signature: &FunctionSignature, - source: ExprId, - ) -> Result { - if let Some(index) = self - .resolved_wasm_refs - .and_then(|resolved| resolved.indirect_calls.get(&source)) - .map(|resolved| resolved.type_index) - { - return Ok(index); - } - self.callable_type_indices.get(signature).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing callable type index for indirect call", - self.node_range(source), - ) - }) - } - - fn table_slot( - &self, - target: &FunctionValueTarget<'db>, - source: ExprId, - ) -> Result { - if let Some(slot) = self - .resolved_wasm_refs - .and_then(|resolved| resolved.callable_values.get(&source)) - .map(|resolved| resolved.table_slot) - { - return Ok(slot); - } - self.table_slots.get(target).copied().ok_or_else(|| { - Diagnostic::error( - "internal error: missing table slot for function value", - self.node_range(source), - ) - }) - } - - fn scratch_i32_local(&self, source: ExprId) -> Result { - self.layout.scratch_i32_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing scratch local for indirect call or closure emission", - self.node_range(source), - ) - }) - } - - fn result_i32_local(&self, source: ExprId) -> Result { - self.layout.object_i32_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing i32 result local for ARC-sensitive return emission", - self.node_range(source), - ) - }) - } - - fn object_local(&self, source: ExprId) -> Result { - self.layout.object_i32_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing object scratch local for heap emission", - self.node_range(source), - ) - }) - } - - fn scratch_i32_aux_local(&self, source: ExprId) -> Result { - self.layout.scratch_i32_aux_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing auxiliary scratch local for ARC emission", - self.node_range(source), - ) - }) - } - - fn scratch_f64_local(&self, source: ExprId) -> Result { - self.layout.scratch_f64_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing float scratch local for ARC emission", - self.node_range(source), - ) - }) - } - - fn scratch_i64_local(&self, source: ExprId) -> Result { - self.layout.scratch_i64_local().ok_or_else(|| { - Diagnostic::error( - "internal error: missing i64 scratch local for raw Wasm emission", - self.node_range(source), - ) - }) - } - - fn nominal_local(&self, source: ExprId) -> Result { - self.layout - .nominal_locals - .get(&source) - .copied() - .or(self.layout.object_i32_local()) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing nominal payload local for object emission", - self.node_range(source), - ) - }) - } - - fn array_repeat_local(&self, source: ExprId) -> Result { - self.layout - .array_repeat_locals - .get(&source) - .copied() - .or(self.layout.object_i32_local()) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing repeat-array local for Wasm emission", - self.node_range(source), - ) - }) - } - - fn helper_index(&self, helper: HelperFunction, source: ExprId) -> Result { - self.helper_indices.get(&helper).copied().ok_or_else(|| { - Diagnostic::error( - format!("internal error: missing function index for `{}` helper", helper.name()), - self.node_range(source), - ) - }) - } - - fn nominal_destroy_index(&self, bits: u32, source: ExprId) -> Result { - self.nominal_destroyers.get(&bits).copied().ok_or_else(|| { - let ty = Ty::from_id(salsa::Id::from_bits(u64::from(bits))); - Diagnostic::error( - format!( - "internal error: missing nominal destroyer for `{}`", - ty.display(self.backend.db) - ), - self.node_range(source), - ) - }) - } - - fn nominal_eq_index(&self, bits: u32, source: ExprId) -> Result { - self.nominal_eq_helpers.get(&bits).copied().ok_or_else(|| { - let ty = Ty::from_id(salsa::Id::from_bits(u64::from(bits))); - Diagnostic::error( - format!( - "internal error: missing nominal equality helper for `{}`", - ty.display(self.backend.db) - ), - self.node_range(source), - ) - }) - } - - fn array_destroy_index(&self, bits: u32, source: ExprId) -> Result { - self.array_destroyers.get(&bits).copied().ok_or_else(|| { - let ty = Ty::from_id(salsa::Id::from_bits(u64::from(bits))); - Diagnostic::error( - format!( - "internal error: missing array destroyer for `{}`", - ty.display(self.backend.db) - ), - self.node_range(source), - ) - }) - } - - fn array_eq_index(&self, bits: u32, source: ExprId) -> Result { - self.array_eq_helpers.get(&bits).copied().ok_or_else(|| { - let ty = Ty::from_id(salsa::Id::from_bits(u64::from(bits))); - Diagnostic::error( - format!( - "internal error: missing array equality helper for `{}`", - ty.display(self.backend.db) - ), - self.node_range(source), - ) - }) - } - - fn loop_branch_depth(&self, source: ExprId, break_branch: bool) -> Result { - let Some(targets) = self.loop_stack.last().copied() else { - return Err(Diagnostic::error( - "internal error: loop control reached the Wasm emitter outside a loop", - self.node_range(source), - )); - }; - - let target = if break_branch { targets.break_target } else { targets.continue_target }; - self.control_depth.checked_sub(target + 1).ok_or_else(|| { - Diagnostic::error("internal error: invalid loop branch depth", self.node_range(source)) - }) - } - - fn return_branch_depth(&self, source: ExprId) -> Result { - let Some(target) = self.return_target else { - return Err(Diagnostic::error( - "internal error: return reached the Wasm emitter outside a function body", - self.node_range(source), - )); - }; - - self.control_depth.checked_sub(target + 1).ok_or_else(|| { - Diagnostic::error( - "internal error: invalid return branch depth", - self.node_range(source), - ) - }) - } - - fn push_scope(&mut self) { - self.scope_stack.push(ScopeFrame::default()); - } - - fn register_param_locals(&mut self) -> Result<(), Diagnostic> { - let param_names = self.layout.param_names.clone(); - for name in param_names { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing parameter slot during ARC registration", - self.backend.function_range(self.location), - ) - })?; - self.register_scope_local(name, &slot.abi); - } - Ok(()) - } - - fn register_scope_local(&mut self, name: NameId, abi: &AbiTy) { - if !abi.contains_heap_refs() { - return; - } - if let Some(scope) = self.scope_stack.last_mut() { - scope.locals.push(ScopeLocal { name, abi: abi.clone() }); - } - } - - fn release_scope( - &mut self, - function: &mut WasmFunction, - source: ExprId, - ) -> Result<(), Diagnostic> { - let Some(scope) = self.scope_stack.pop() else { - return Ok(()); - }; - self.release_scope_frame(function, &scope, source) - } - - fn release_scopes_to( - &mut self, - function: &mut WasmFunction, - scope_depth: usize, - source: ExprId, - ) -> Result<(), Diagnostic> { - while self.scope_stack.len() > scope_depth { - self.release_scope(function, source)?; - } - Ok(()) - } - - fn release_scope_frame( - &mut self, - function: &mut WasmFunction, - scope: &ScopeFrame, - source: ExprId, - ) -> Result<(), Diagnostic> { - for local in scope.locals.iter().rev() { - self.release_local(function, local, source)?; - } - Ok(()) - } - - fn release_local( - &mut self, - function: &mut WasmFunction, - local: &ScopeLocal, - source: ExprId, - ) -> Result<(), Diagnostic> { - let slot = self.layout.slots.get(&local.name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing local slot during ARC release", - self.node_range(source), - ) - })?; - match &local.abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - let index = slot.local_index.ok_or_else(|| { - Diagnostic::error( - "internal error: scalar heap local is missing a Wasm local index", - self.node_range(source), - ) - })?; - function.instruction(&Instruction::LocalGet(index)); - self.release_heap_ref_from_stack(function, *ty, source) - } - AbiTy::Aggregate(_) => self.release_aggregate_at_dest( - function, - self.dest_for_slot(slot)?, - &local.abi, - source, - ), - _ => Ok(()), - } - } - - fn retain_heap_ref_on_stack( - &mut self, - function: &mut WasmFunction, - source: ExprId, - ) -> Result<(), Diagnostic> { - let scratch = self.scratch_i32_aux_local(source)?; - let helper = self.helper_index(HelperFunction::ArcRetain, source)?; - function.instruction(&Instruction::LocalTee(scratch)); - function.instruction(&Instruction::LocalGet(scratch)); - function.instruction(&Instruction::Call(helper)); - Ok(()) - } - - fn release_heap_ref_from_stack( - &mut self, - function: &mut WasmFunction, - ty: BackendTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - let scratch = self.scratch_i32_local(source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_heap_ref_from_local(function, scratch, ty, source) - } - - fn release_heap_ref_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - ty: BackendTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - if !ty.is_heap_ref() { - return Ok(()); - } - - let helper = self.helper_index(HelperFunction::ArcRelease, source)?; - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::Call(helper)); - function.instruction(&Instruction::If(BlockType::Empty)); - match ty { - BackendTy::Ref(RefKind::String) => { - self.dealloc_string_from_local(function, local, source)? - } - BackendTy::Ref(RefKind::Array(bits)) => { - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::Call(self.array_destroy_index(bits, source)?)); - } - BackendTy::Ref(RefKind::Nominal(bits)) => { - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::Call(self.nominal_destroy_index(bits, source)?)); - } - BackendTy::Ref(RefKind::Opaque) => {} - _ => {} - } - function.instruction(&Instruction::End); - Ok(()) - } - - fn dealloc_string_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - source: ExprId, - ) -> Result<(), Diagnostic> { - let base = self.scratch_i32_other_local(source, local)?; - let dealloc = self.runtime_index(RuntimeFunction::Dealloc, source)?; - - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalSet(base)); - - function.instruction(&Instruction::LocalGet(base)); - function.instruction(&Instruction::LocalGet(local)); - MemAccess::arc_ref_count().emit_load(function); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + 4) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(dealloc)); - Ok(()) - } - - fn dealloc_array_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - layout: &ArrayRuntimeLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - let base = self.scratch_i32_other_local(source, local)?; - let dealloc = self.runtime_index(RuntimeFunction::Dealloc, source)?; - - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalSet(base)); - - function.instruction(&Instruction::LocalGet(base)); - function.instruction(&Instruction::LocalGet(local)); - MemAccess::array_capacity().emit_load(function); - function.instruction(&Instruction::I32Const(layout.item_stride as i32)); - function.instruction(&Instruction::I32Mul); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + layout.data_offset) as i32)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::I32Const(layout.object_align() as i32)); - function.instruction(&Instruction::Call(dealloc)); - Ok(()) - } - - fn dealloc_env_from_local( - &mut self, - function: &mut WasmFunction, - local: u32, - payload_size: u32, - source: ExprId, - ) -> Result<(), Diagnostic> { - let base = self.scratch_i32_other_local(source, local)?; - let dealloc = self.runtime_index(RuntimeFunction::Dealloc, source)?; - - function.instruction(&Instruction::LocalGet(local)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalSet(base)); - function.instruction(&Instruction::LocalGet(base)); - function.instruction(&Instruction::I32Const((ARC_HEADER_SIZE + payload_size) as i32)); - function.instruction(&Instruction::I32Const(ARC_ALIGN as i32)); - function.instruction(&Instruction::Call(dealloc)); - Ok(()) - } - - fn release_aggregate_at_dest( - &mut self, - function: &mut WasmFunction, - dest: Dest, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - self.emit_dest_addr(function, dest)?; - let scratch = self.scratch_i32_local(source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.release_aggregate_at_local(function, scratch, abi, source) - } - - fn release_aggregate_at_local( - &mut self, - function: &mut WasmFunction, - base_local: u32, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - let AbiTy::Aggregate(layout) = abi else { - return Ok(()); - }; - - if layout.is_function_value() { - return self.release_function_value_at_local(function, base_local, source); - } - - match &layout.kind { - AggregateKind::Fields(fields) => { - for field in fields { - self.release_field_from_local(function, base_local, field, source)?; - } - } - AggregateKind::Enum(enum_layout) => { - let tag_local = self.scratch_i32_other_local(source, base_local)?; - function.instruction(&Instruction::LocalGet(base_local)); - MemAccess::enum_tag(0).emit_load(function); - function.instruction(&Instruction::LocalSet(tag_local)); - - for variant in &enum_layout.variants { - function.instruction(&Instruction::LocalGet(tag_local)); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - for field in &variant.fields { - self.release_field_from_local(function, base_local, field, source)?; - } - function.instruction(&Instruction::End); - } - } - AggregateKind::FunctionValue => unreachable!("function values are handled above"), - } - - Ok(()) - } - - fn release_field_from_local( - &mut self, - function: &mut WasmFunction, - base_local: u32, - field: &FieldLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - match &field.ty { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - let scratch = self.scratch_i32_other_local(source, base_local)?; - function.instruction(&Instruction::LocalGet(base_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - emit_scalar_load(function, *ty, 0); - function.instruction(&Instruction::LocalSet(scratch)); - match ty { - BackendTy::Ref(RefKind::String | RefKind::Array(_) | RefKind::Nominal(_)) => { - self.release_heap_ref_from_local(function, scratch, *ty, source)? - } - BackendTy::Ref(RefKind::Opaque) => {} - _ => {} - } - } - AbiTy::Aggregate(_) if field.ty.contains_heap_refs() => { - let nested = self.scratch_i32_other_local(source, base_local)?; - function.instruction(&Instruction::LocalGet(base_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - function.instruction(&Instruction::LocalSet(nested)); - self.release_aggregate_at_local(function, nested, &field.ty, source)?; - } - _ => {} - } - Ok(()) - } - - fn retain_aggregate_at_dest( - &mut self, - function: &mut WasmFunction, - dest: Dest, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - self.emit_dest_addr(function, dest)?; - let scratch = self.scratch_i32_local(source)?; - function.instruction(&Instruction::LocalSet(scratch)); - self.retain_aggregate_at_local(function, scratch, abi, source) - } - - fn retain_aggregate_at_local( - &mut self, - function: &mut WasmFunction, - base_local: u32, - abi: &AbiTy, - source: ExprId, - ) -> Result<(), Diagnostic> { - let AbiTy::Aggregate(layout) = abi else { - return Ok(()); - }; - - if layout.is_function_value() { - function.instruction(&Instruction::LocalGet(base_local)); - MemAccess::function_word(4).emit_load(function); - self.retain_heap_ref_on_stack(function, source)?; - function.instruction(&Instruction::Drop); - return Ok(()); - } - - match &layout.kind { - AggregateKind::Fields(fields) => { - for field in fields { - self.retain_field_from_local(function, base_local, field, source)?; - } - } - AggregateKind::Enum(_enum_layout) => { - let tag_local = self.scratch_i32_other_local(source, base_local)?; - function.instruction(&Instruction::LocalGet(base_local)); - MemAccess::enum_tag(0).emit_load(function); - function.instruction(&Instruction::LocalSet(tag_local)); - - for variant in match &layout.kind { - AggregateKind::Enum(enum_layout) => &enum_layout.variants, - AggregateKind::Fields(_) => unreachable!(), - AggregateKind::FunctionValue => unreachable!(), - } { - function.instruction(&Instruction::LocalGet(tag_local)); - function.instruction(&Instruction::I32Const(variant.tag)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - for field in &variant.fields { - self.retain_field_from_local(function, base_local, field, source)?; - } - function.instruction(&Instruction::End); - } - } - AggregateKind::FunctionValue => unreachable!("function values are handled above"), - } - - Ok(()) - } - - fn retain_field_from_local( - &mut self, - function: &mut WasmFunction, - base_local: u32, - field: &FieldLayout, - source: ExprId, - ) -> Result<(), Diagnostic> { - match &field.ty { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - function.instruction(&Instruction::LocalGet(base_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - emit_scalar_load(function, *ty, 0); - self.retain_heap_ref_on_stack(function, source)?; - function.instruction(&Instruction::Drop); - } - AbiTy::Aggregate(_) if field.ty.contains_heap_refs() => { - let nested = self.scratch_i32_other_local(source, base_local)?; - function.instruction(&Instruction::LocalGet(base_local)); - if field.offset != 0 { - function.instruction(&Instruction::I32Const(field.offset as i32)); - function.instruction(&Instruction::I32Add); - } - function.instruction(&Instruction::LocalSet(nested)); - self.retain_aggregate_at_local(function, nested, &field.ty, source)?; - } - _ => {} - } - Ok(()) - } - - fn emit_expr_into_owner( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - match &expr.abi { - AbiTy::Scalar(BackendTy::Unit) => self.expr(function, expr), - AbiTy::Scalar(ty) => { - self.expr(function, expr)?; - if expr.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - let scratch = match ty { - BackendTy::Float => self.scratch_f64_local(expr.source)?, - BackendTy::I64 => self.scratch_i64_local(expr.source)?, - _ => self.scratch_i32_aux_local(expr.source)?, - }; - function.instruction(&Instruction::LocalSet(scratch)); - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::LocalGet(scratch)); - emit_scalar_store(function, *ty, 0); - Ok(()) - } - AbiTy::Aggregate(layout) => { - if expr.ownership.is_borrowed() { - self.copy_aggregate_expr_to_dest(function, dest, expr, layout.size)?; - if expr.abi.contains_heap_refs() { - self.retain_aggregate_at_dest(function, dest, &expr.abi, expr.source)?; - } - Ok(()) - } else { - self.emit_expr_into(function, dest, expr) - } - } - } - } - - fn emit_structured_wasm_expr_into_owner( - &mut self, - function: &mut WasmFunction, - dest: Dest, - expr: &StructuredWasmExpr<'db>, - ) -> Result<(), Diagnostic> { - match &expr.abi { - AbiTy::Scalar(BackendTy::Unit) => self.structured_wasm_expr(function, expr), - AbiTy::Scalar(ty) => { - self.structured_wasm_expr(function, expr)?; - if expr.ownership.is_borrowed() && ty.is_heap_ref() { - self.retain_heap_ref_on_stack(function, expr.source)?; - } - let scratch = match ty { - BackendTy::Float => self.scratch_f64_local(expr.source)?, - BackendTy::I64 => self.scratch_i64_local(expr.source)?, - _ => self.scratch_i32_aux_local(expr.source)?, - }; - function.instruction(&Instruction::LocalSet(scratch)); - self.emit_dest_addr(function, dest)?; - function.instruction(&Instruction::LocalGet(scratch)); - emit_scalar_store(function, *ty, 0); - Ok(()) - } - AbiTy::Aggregate(layout) => { - if expr.ownership.is_borrowed() { - let temp = self.temp_dest(expr.source)?; - self.emit_structured_wasm_expr_into(function, temp, expr)?; - self.copy_dest_to_dest(function, dest, temp, layout.size)?; - if expr.abi.contains_heap_refs() { - self.retain_aggregate_at_dest(function, dest, &expr.abi, expr.source)?; - } - Ok(()) - } else { - self.emit_structured_wasm_expr_into(function, dest, expr) - } - } - } - } - - fn legalized_signature(&self) -> Option<&FunctionLegalization<'db>> { - self.function_legalization - } - - fn legalized_result_passing(&self) -> LegalizedResultPassing { - self.legalized_signature().map_or_else( - || match self.function_result { - AbiTy::Scalar(BackendTy::Unit) => LegalizedResultPassing::Unit, - AbiTy::Scalar(ty) => LegalizedResultPassing::DirectScalar(*ty), - AbiTy::Aggregate(_) => LegalizedResultPassing::IndirectOutPtr, - }, - |legalization| legalization.signature.result, - ) - } - - fn legalized_result_ptr_local(&self) -> Option { - self.legalized_signature() - .and_then(|legalization| legalization.signature.hidden_result_local) - .or_else(|| self.layout.result_ptr_local()) - } - - fn legalized_env_ptr_local(&self) -> Option { - self.legalized_signature() - .and_then(|legalization| legalization.signature.hidden_env_local) - .or_else(|| self.layout.env_ptr_local()) - } - - fn legalized_call_site(&self, source: ExprId) -> Option<&LegalizedCallSite<'db>> { - self.legalized_signature().and_then(|legalization| legalization.call_sites.get(&source)) - } - - fn legalized_callable_representation(&self) -> LegalizedCallableRepresentation { - self.legalized_signature() - .map_or(LegalizedCallableRepresentation::HandleAndTable, |legalization| { - legalization.callable_representation - }) - } - - fn emit_local_ownership_ops( - &mut self, - function: &mut WasmFunction, - local: u32, - abi: &AbiTy, - source: ExprId, - ownership_ops: &[StructuredWasmOwnershipOp], - ) -> Result<(), Diagnostic> { - for op in ownership_ops { - match op { - StructuredWasmOwnershipOp::Retain => match abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - function.instruction(&Instruction::LocalGet(local)); - self.retain_heap_ref_on_stack(function, source)?; - function.instruction(&Instruction::Drop); - } - AbiTy::Aggregate(_) => { - self.retain_aggregate_at_local(function, local, abi, source)?; - } - AbiTy::Scalar(_) => {} - }, - StructuredWasmOwnershipOp::Release | StructuredWasmOwnershipOp::Destroy => { - match abi { - AbiTy::Scalar(ty) if ty.is_heap_ref() => { - self.release_heap_ref_from_local(function, local, *ty, source)?; - } - AbiTy::Aggregate(_) => { - self.release_aggregate_at_local(function, local, abi, source)?; - } - AbiTy::Scalar(_) => {} - } - } - StructuredWasmOwnershipOp::Copy | StructuredWasmOwnershipOp::Move => {} - } - } - Ok(()) - } - - fn emit_dest_ownership_ops( - &mut self, - function: &mut WasmFunction, - dest: Dest, - abi: &AbiTy, - source: ExprId, - ownership_ops: &[StructuredWasmOwnershipOp], - ) -> Result<(), Diagnostic> { - for op in ownership_ops { - match op { - StructuredWasmOwnershipOp::Retain if abi.contains_heap_refs() => { - self.retain_aggregate_at_dest(function, dest, abi, source)?; - } - StructuredWasmOwnershipOp::Release | StructuredWasmOwnershipOp::Destroy - if abi.contains_heap_refs() => - { - self.release_aggregate_at_dest(function, dest, abi, source)?; - } - StructuredWasmOwnershipOp::Copy - | StructuredWasmOwnershipOp::Move - | StructuredWasmOwnershipOp::Retain - | StructuredWasmOwnershipOp::Release - | StructuredWasmOwnershipOp::Destroy => {} - } - } - Ok(()) - } - - fn emit_stack_ownership_ops( - &mut self, - function: &mut WasmFunction, - ty: BackendTy, - source: ExprId, - ownership_ops: &[StructuredWasmOwnershipOp], - ) -> Result { - let mut consumed = false; - for op in ownership_ops { - match op { - StructuredWasmOwnershipOp::Retain if ty.is_heap_ref() => { - self.retain_heap_ref_on_stack(function, source)?; - } - StructuredWasmOwnershipOp::Release | StructuredWasmOwnershipOp::Destroy - if ty.is_heap_ref() => - { - self.release_heap_ref_from_stack(function, ty, source)?; - consumed = true; - } - StructuredWasmOwnershipOp::Copy - | StructuredWasmOwnershipOp::Move - | StructuredWasmOwnershipOp::Retain - | StructuredWasmOwnershipOp::Release - | StructuredWasmOwnershipOp::Destroy => {} - } - } - Ok(consumed) - } - - fn release_function_value_at_local( - &mut self, - function: &mut WasmFunction, - value_local: u32, - source: ExprId, - ) -> Result<(), Diagnostic> { - let env_local = self.scratch_i32_other_local(source, value_local)?; - - function.instruction(&Instruction::LocalGet(value_local)); - MemAccess::function_word(4).emit_load(function); - function.instruction(&Instruction::LocalSet(env_local)); - - function.instruction(&Instruction::LocalGet(env_local)); - let helper = self.helper_index(HelperFunction::ArcRelease, source)?; - function.instruction(&Instruction::Call(helper)); - function.instruction(&Instruction::If(BlockType::Empty)); - - for (slot, destroy_index) in self.closure_destroyers { - function.instruction(&Instruction::LocalGet(value_local)); - MemAccess::function_word(0).emit_load(function); - function.instruction(&Instruction::I32Const(*slot as i32)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::LocalGet(env_local)); - function.instruction(&Instruction::Call(*destroy_index)); - function.instruction(&Instruction::End); - } - - function.instruction(&Instruction::End); - Ok(()) - } - - fn scratch_i32_other_local(&self, source: ExprId, local: u32) -> Result { - let primary = self.scratch_i32_local(source)?; - let aux = self.scratch_i32_aux_local(source)?; - if local == primary { Ok(aux) } else { Ok(primary) } - } - - fn node_range(&self, expr: ExprId) -> mitki_errors::TextRange { - self.source_map - .try_node_syntax(expr) - .map_or_else(|| self.backend.function_range(self.location), |ptr| ptr.range) - } -} - -fn arg_passing_for_abi(abi: &AbiTy) -> LegalizedArgPassing { - match abi { - AbiTy::Scalar(_) => LegalizedArgPassing::Direct, - AbiTy::Aggregate(_) => LegalizedArgPassing::ByAddress, - } -} - -fn result_passing_for_abi(abi: &AbiTy) -> LegalizedResultPassing { - match abi { - AbiTy::Scalar(BackendTy::Unit) => LegalizedResultPassing::Unit, - AbiTy::Scalar(ty) => LegalizedResultPassing::DirectScalar(*ty), - AbiTy::Aggregate(_) => LegalizedResultPassing::IndirectOutPtr, - } -} diff --git a/crates/mitki-backend-wasm/src/emit/mod.rs b/crates/mitki-backend-wasm/src/emit/mod.rs deleted file mode 100644 index f859486..0000000 --- a/crates/mitki-backend-wasm/src/emit/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub(super) mod boundary; -pub(super) mod function; -pub(super) mod module; - -use mitki_errors::Diagnostic; -use wasm_encoder::{BlockType, Function as WasmFunction, Instruction, ValType}; - -pub(super) use self::function as backend_ir; -use super::boundary::BoundarySig; -use super::lowering::wrapper_mir; -use super::{planning as plan, registry, validation as validate, *}; diff --git a/crates/mitki-backend-wasm/src/emit/module.rs b/crates/mitki-backend-wasm/src/emit/module.rs deleted file mode 100644 index 53c41a8..0000000 --- a/crates/mitki-backend-wasm/src/emit/module.rs +++ /dev/null @@ -1,5230 +0,0 @@ -use std::borrow::Cow; -#[cfg(test)] -use std::sync::Arc; - -use mitki_abi::{ - AbiScalar, AbiValue, ArrayElements, CanonicalGraph, CanonicalNode, - LinkageKind as AbiV2LinkageKind, PackedScalarKind, RawExportRecord, RawImportRecord, SigId, - ValueRef, validate_wasm_module_contract, -}; -use mitki_abi_lower::{MITKI_ABI_V2_CUSTOM_SECTION, encode_module_abi_v2}; -use mitki_analysis::ownership; -use mitki_hir::hir::ParamId; -use mitki_lower::item::scope::enum_variants; -use mitki_resolve::{BindingId, resolve_method_for_receiver}; -use mitki_span::IntoSymbol as _; -use rustc_hash::FxHashMap; -use wasm_encoder::{ - CodeSection, CustomSection, DataSection, ElementSection, Elements, EntityType, ExportKind, - ExportSection, Function as WasmFunction, FunctionSection, GlobalSection, GlobalType, - ImportSection, Instruction, MemorySection, MemoryType, Module, TableSection, TableType, - TypeSection, -}; - -use super::super::*; -use super::backend_ir; -use super::boundary::{ - emit_alloc_helper, emit_blob_release_helper, emit_bool_param_normalization, - emit_export_wrapper_v2, emit_function_value_wrapper, emit_handle_invoke_trampoline_v2, - emit_handle_release_helper, emit_handle_retain_helper, emit_import_thunk_v2, -}; -use super::function_kernel::{ - FunctionKernelBindingInit as KernelBindingInit, FunctionKernelBindingSource, - FunctionKernelClosureEnvInit as KernelClosureEnvInit, - FunctionKernelFieldValue as KernelFieldValue, FunctionKernelLowered, - FunctionKernelMatchArm as KernelMatchArm, FunctionKernelStmt as KernelStmt, - FunctionKernelValidator, FunctionKernelValue as KernelExpr, - FunctionKernelValueKind as KernelExprKind, -}; -use super::function_legalize::CallConvValidator; -use super::function_ownership::OwnershipValidator; -use super::function_wasm_ir::StructuredWasmValidator; -use super::plan::HelperNeed; -use super::registry::SectionExportTarget; -use super::validate::{BoundaryPlanValidator, ModulePlanValidator, StoragePlanValidator}; -use super::wrapper_mir::{WrapperMirValidator, abi_v2_wasm_signature}; -use crate::abi::backend_ty_value_type; -use crate::layout::VariantLayout; - -struct BoundaryInvokeTrampoline<'db> { - signature_id: SigId, - location: FunctionLocation<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, -} - -#[derive(Clone, Copy)] -struct ResolvedMethodCall<'db> { - receiver: ExprId, - function: FunctionLocation<'db>, -} - -impl<'db> Backend<'db> { - pub fn emit_module(&self) -> Result, Diagnostic> { - let module_plan = self.build_validated_module_plan()?; - let static_data = self.build_static_data_for(&module_plan.reachability)?; - self.serialize_module_from_plan(&module_plan, &static_data) - } - - pub fn emit_module_from_plan( - &self, - module_plan: &plan::ModulePlan<'db>, - ) -> Result, Diagnostic> { - BoundaryPlanValidator::validate(self, &module_plan.boundary)?; - ModulePlanValidator::validate(self, module_plan)?; - WrapperMirValidator::validate(self, module_plan, &module_plan.wrapper_mir)?; - let function_kernel = module_plan.function_kernel.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: module plan is missing function kernel for ordinary functions", - self.file_range(), - ) - })?; - FunctionKernelValidator::validate(self, module_plan, function_kernel)?; - OwnershipValidator::validate(self, module_plan, function_kernel)?; - CallConvValidator::validate(self, module_plan, function_kernel)?; - let function_wasm_ir = module_plan.function_wasm_ir.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: module plan is missing structured Wasm IR for ordinary functions", - self.file_range(), - ) - })?; - StructuredWasmValidator::validate(self, module_plan, function_wasm_ir)?; - let static_data = self.build_static_data_for(&module_plan.reachability)?; - self.serialize_module_from_plan(module_plan, &static_data) - } - - fn build_validated_module_plan(&self) -> Result, Diagnostic> { - let module_plan = self.build_module_plan()?; - BoundaryPlanValidator::validate(self, &module_plan.boundary)?; - ModulePlanValidator::validate(self, &module_plan)?; - WrapperMirValidator::validate(self, &module_plan, &module_plan.wrapper_mir)?; - let function_kernel = module_plan.function_kernel.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: module plan is missing function kernel for ordinary functions", - self.file_range(), - ) - })?; - FunctionKernelValidator::validate(self, &module_plan, function_kernel)?; - OwnershipValidator::validate(self, &module_plan, function_kernel)?; - CallConvValidator::validate(self, &module_plan, function_kernel)?; - let function_wasm_ir = module_plan.function_wasm_ir.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: module plan is missing structured Wasm IR for ordinary functions", - self.file_range(), - ) - })?; - StructuredWasmValidator::validate(self, &module_plan, function_wasm_ir)?; - Ok(module_plan) - } - - fn serialize_module_from_plan( - &self, - module_plan: &plan::ModulePlan<'db>, - static_data: &StaticData<'db>, - ) -> Result, Diagnostic> { - let reachable_functions = module_plan.reachability.functions.clone(); - let reachable_closures = module_plan.reachability.closures.clone(); - let reachable_closure_infos = reachable_closures - .iter() - .map(|closure| { - let hir_function = closure.owner.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = closure.owner.infer(self.db); - self.closure_info(closure, function, inference) - .map(|info| ReachableClosureInfo { closure: closure.clone(), info }) - }) - .collect::, _>>()?; - let reachable_arrays = module_plan.obligations.reachable_arrays.clone(); - let reachable_nominals = module_plan.obligations.reachable_nominals.clone(); - let boundary_plan = &module_plan.boundary; - let mut abi_v2 = module_plan.abi_preview.clone(); - let mut handle_invoke_trampolines = Vec::new(); - for trampoline in &module_plan.callables.invoke_trampolines { - let instance = &trampoline.instance; - let hir_function = instance.location.hir_function(self.db); - handle_invoke_trampolines.push(BoundaryInvokeTrampoline { - signature_id: trampoline.signature_id, - location: instance.location, - source_map: hir_function.source_map(self.db), - }); - } - let stack_base = align_to(static_data.bytes.len() as u32, 4); - let needs_abi_v2_blob_helpers = module_plan.helpers.contains(HelperNeed::AbiAlloc); - let needs_abi_v2_handle_helpers = module_plan.helpers.contains(HelperNeed::AbiHandleRetain); - let section_plan = &module_plan.sections; - let callable_registry = &module_plan.callables; - let import_provider = self.import_provider(); - let callable_lowering = self.callable_lowering_strategy(); - let memory_model = self.memory_model_strategy(); - let signature_strategy = self.signature_strategy(); - let word_type = memory_model.word_type(); - let runtime_imports = section_plan.runtime_imports.clone(); - let stage_imports = section_plan.stage_intrinsics.clone(); - let helper_functions = section_plan.builtin_helpers.clone(); - let mut type_section = TypeSection::new(); - let mut import_section = ImportSection::new(); - let mut function_section = FunctionSection::new(); - let mut table_section = TableSection::new(); - let mut memory_section = MemorySection::new(); - let mut global_section = GlobalSection::new(); - let mut export_section = ExportSection::new(); - let mut element_section = ElementSection::new(); - let mut code_section = CodeSection::new(); - let mut data_section = DataSection::new(); - let runtime_type_indices = §ion_plan.runtime_type_indices; - let stage_type_indices = §ion_plan.stage_type_indices; - let helper_type_indices = §ion_plan.helper_type_indices; - let direct_type_indices = §ion_plan.direct_type_indices; - let external_type_indices = §ion_plan.external_type_indices; - let callable_type_indices = §ion_plan.callable_type_indices; - let runtime_indices = §ion_plan.runtime_function_indices; - let stage_indices = §ion_plan.stage_function_indices; - let helper_indices = §ion_plan.helper_function_indices; - let raw_import_function_indices = §ion_plan.raw_import_function_indices; - let direct_function_indices = §ion_plan.direct_function_indices; - let closure_destroy_indices = §ion_plan.closure_destroy_indices; - let array_destroyer_indices = §ion_plan.array_destroyer_indices; - let array_eq_indices = §ion_plan.array_eq_indices; - let nominal_destroyer_indices = §ion_plan.nominal_destroyer_indices; - let nominal_eq_indices = §ion_plan.nominal_eq_indices; - let table_slots = §ion_plan.table_slots; - let table_elements = §ion_plan.table_elements; - let nominal_destroy_type_index = section_plan.nominal_destroy_type_index; - let nominal_eq_type_index = section_plan.nominal_eq_type_index; - let array_destroy_type_index = section_plan.array_destroy_type_index; - let array_eq_type_index = section_plan.array_eq_type_index; - let closure_destroy_type_index = section_plan.closure_destroy_type_index; - let closure_destroyers = §ion_plan.closure_destroyer_pairs; - - for runtime in &runtime_imports { - let signature = runtime_function_signature(*runtime); - let lowered = signature_strategy.direct_signature(&signature); - type_section.ty().function(lowered.params, lowered.results); - } - for intrinsic in &stage_imports { - let signature = stage_intrinsic_signature(*intrinsic); - let lowered = signature_strategy.direct_signature(&signature); - type_section.ty().function(lowered.params, lowered.results); - } - - for helper in &helper_functions { - let signature = helper_function_signature(*helper); - let lowered = signature_strategy.direct_signature(&signature); - type_section.ty().function(lowered.params, lowered.results); - } - if nominal_destroy_type_index.is_some() { - type_section.ty().function([word_type], []); - } - if nominal_eq_type_index.is_some() { - type_section.ty().function([word_type, word_type], [ValType::I32]); - } - if array_destroy_type_index.is_some() { - type_section.ty().function([word_type], []); - } - if array_eq_type_index.is_some() { - type_section.ty().function([word_type, word_type], [ValType::I32]); - } - - for instance in &reachable_functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = instance.location.infer(self.db); - let signature = self.function_signature(instance, function, inference)?; - let lowered = signature_strategy.direct_signature(&signature); - type_section.ty().function(lowered.params, lowered.results); - } - for instance in &reachable_functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = instance.location.infer(self.db); - if let Some(&metadata_index) = boundary_plan - .import_indices - .get(instance) - .or_else(|| boundary_plan.export_indices.get(instance)) - { - let built = &abi_v2.functions[metadata_index]; - let signature = &abi_v2.graph.signatures[built.signature_id.0 as usize]; - let (params, results) = abi_v2_wasm_signature(&abi_v2.graph, signature)?; - type_section.ty().function(params, results); - } else { - let signature = self.function_signature(instance, function, inference)?; - let lowered = signature_strategy.direct_signature(&signature); - type_section.ty().function(lowered.params, lowered.results); - } - } - - for callable in &callable_registry.signatures { - let lowered = callable_lowering.callable_signature( - signature_strategy, - memory_model, - &callable.signature, - ); - type_section.ty().function(lowered.params, lowered.results); - } - type_section.ty().function([word_type], []); - for runtime in &runtime_imports { - let (module, field) = import_provider.runtime_import(*runtime); - import_section.import( - module, - field, - EntityType::Function(runtime_type_indices[runtime]), - ); - } - for intrinsic in &stage_imports { - let (module, field) = import_provider.stage_intrinsic_import(*intrinsic); - import_section.import( - module, - field, - EntityType::Function(stage_type_indices[intrinsic]), - ); - } - for instance in §ion_plan.raw_imports { - let function = instance.location.hir_function(self.db).function(self.db); - let (module, import_name) = match function.linkage() { - WasmLinkage::Import { module } => ( - module.text(self.db), - boundary_plan.import_indices.get(instance).map_or_else( - || { - instance - .location - .source(self.db) - .name() - .expect("reachable import should have a name") - .as_str() - }, - |&metadata_index| abi_v2.functions[metadata_index].wasm_field_name.as_str(), - ), - ), - WasmLinkage::RawImport { module } => ( - module.text(self.db), - instance - .location - .source(self.db) - .name() - .expect("reachable import should have a name") - .as_str(), - ), - _ => unreachable!(), - }; - import_section.import( - module, - import_name, - EntityType::Function(external_type_indices[instance]), - ); - } - - for helper in &helper_functions { - function_section.function(helper_type_indices[helper]); - let wasm_function = emit_helper_function(*helper, helper_indices); - code_section.function(&wasm_function); - } - if let Some(helper_location) = reachable_functions.first().map(|instance| instance.location) - { - let helper_source_map = helper_location.hir_function(self.db).source_map(self.db); - let destroy_layout = scratch_only_layout(1, word_type); - let eq_layout = scratch_only_layout(2, word_type); - let destroy_result = AbiTy::Scalar(BackendTy::Unit); - let eq_result = AbiTy::Scalar(BackendTy::Bool); - - for ty in &reachable_nominals { - let payload_layout = - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.db, - *ty, - "Wasm backend does not support this reachable value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(helper_location)) - })?; - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: helper_location, - source_map: helper_source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &destroy_layout, - function_result: &destroy_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section.function( - nominal_destroy_type_index.expect("destroy helper type should exist"), - ); - let mut wasm_function = - WasmFunction::new(destroy_layout.wasm_locals().iter().copied()); - emitter.emit_nominal_destructor( - &mut wasm_function, - &payload_layout, - 0, - ExprId::ZERO, - )?; - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - - for ty in &reachable_arrays { - let layout = crate::capability::supported_array_runtime_layout_or_message( - self.db, - *ty, - "Wasm backend does not support this reachable value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(helper_location)) - })?; - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: helper_location, - source_map: helper_source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &destroy_layout, - function_result: &destroy_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section - .function(array_destroy_type_index.expect("array destroy helper type")); - let mut wasm_function = - WasmFunction::new(destroy_layout.wasm_locals().iter().copied()); - emitter.emit_array_destructor(&mut wasm_function, &layout, ExprId::ZERO)?; - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - - for ty in &reachable_nominals { - let payload_layout = - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.db, - *ty, - "Wasm backend does not support this reachable value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(helper_location)) - })?; - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: helper_location, - source_map: helper_source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &eq_layout, - function_result: &eq_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section - .function(nominal_eq_type_index.expect("eq helper type should exist")); - let mut wasm_function = WasmFunction::new(eq_layout.wasm_locals().iter().copied()); - emitter.emit_nominal_equality( - &mut wasm_function, - &payload_layout, - 0, - 1, - ExprId::ZERO, - )?; - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - - for ty in &reachable_arrays { - let layout = crate::capability::supported_array_runtime_layout_or_message( - self.db, - *ty, - "Wasm backend does not support this reachable value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(helper_location)) - })?; - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: helper_location, - source_map: helper_source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &eq_layout, - function_result: &eq_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section.function(array_eq_type_index.expect("array eq helper type")); - let mut wasm_function = WasmFunction::new(eq_layout.wasm_locals().iter().copied()); - emitter.emit_array_equality(&mut wasm_function, &layout, ExprId::ZERO)?; - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - } - - for instance in &reachable_functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = instance.location.infer(self.db); - function_section.function(direct_type_indices[instance]); - if matches!(function.linkage(), WasmLinkage::Import { .. }) { - let mir = module_plan - .wrapper_mir - .import(instance) - .expect("planned import wrapper MIR should exist"); - let wasm_function = emit_import_thunk_v2( - self, - instance.location, - hir_function.source_map(self.db), - mir, - &abi_v2.graph, - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyer_indices, - nominal_eq_indices, - array_destroyer_indices, - array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - )?; - code_section.function(&wasm_function); - continue; - } - if matches!(function.linkage(), WasmLinkage::RawImport { .. }) { - let signature = self.function_signature(instance, function, inference)?; - let wasm_function = emit_raw_import_forwarder( - self.function_range(instance.location), - raw_import_function_indices[instance], - &signature, - )?; - code_section.function(&wasm_function); - continue; - } - - let signature = self.function_signature(instance, function, inference)?; - let emit_function = module_plan - .function_wasm_ir - .as_ref() - .and_then(|bundle| bundle.direct(instance)) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing planned structured Wasm IR for a reachable \ - function", - self.function_range(instance.location), - ) - })?; - let layout = emit_function.layout.clone(); - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: instance.location, - source_map: hir_function.source_map(self.db), - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: Some(emit_function.resolved()), - function_legalization: emit_function.legalization.as_ref(), - layout: &layout, - function_result: &signature.result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - let wasm_locals = emit_function.wasm_locals(); - let mut wasm_function = WasmFunction::new(wasm_locals.iter().copied()); - emit_bool_param_normalization(&mut wasm_function, function, &signature, &layout); - emitter.emit_prologue(&mut wasm_function); - emitter.structured_wasm_body(&mut wasm_function, emit_function)?; - emitter.emit_epilogue(&mut wasm_function); - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - - for instance in &reachable_functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = instance.location.infer(self.db); - let signature = self.function_signature(instance, function, inference)?; - function_section.function(callable_type_indices[&signature]); - let mir = module_plan - .wrapper_mir - .callable_adapter(instance) - .expect("planned callable wrapper MIR should exist"); - let wasm_function = emit_function_value_wrapper( - self, - instance.location, - hir_function.source_map(self.db), - mir, - direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyer_indices, - nominal_eq_indices, - array_destroyer_indices, - array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - )?; - code_section.function(&wasm_function); - } - - for closure in &reachable_closure_infos { - let hir_function = closure.closure.owner.hir_function(self.db); - let source_map = hir_function.source_map(self.db); - let emit_function = module_plan - .function_wasm_ir - .as_ref() - .and_then(|bundle| bundle.closure(&closure.closure)) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing planned structured Wasm IR for a reachable \ - closure", - self.function_range(closure.closure.owner), - ) - })?; - let layout = emit_function.layout.clone(); - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: closure.closure.owner, - source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: Some(emit_function.resolved()), - function_legalization: emit_function.legalization.as_ref(), - layout: &layout, - function_result: &closure.info.signature.result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section.function(callable_type_indices[&closure.info.signature]); - let wasm_locals = emit_function.wasm_locals(); - let mut wasm_function = WasmFunction::new(wasm_locals.iter().copied()); - emitter.emit_prologue(&mut wasm_function); - emitter.structured_wasm_body(&mut wasm_function, emit_function)?; - emitter.emit_epilogue(&mut wasm_function); - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - let closure_destroy_layout = closure_destroy_layout(word_type); - let closure_destroy_result = AbiTy::Scalar(BackendTy::Unit); - for closure in &reachable_closure_infos { - if !closure_destroy_indices.contains_key(&closure.closure) { - continue; - } - - let hir_function = closure.closure.owner.hir_function(self.db); - let source_map = hir_function.source_map(self.db); - let mut emitter = backend_ir::BackendEmitter { - backend: self, - location: closure.closure.owner, - source_map, - function_indices: direct_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyers: nominal_destroyer_indices, - nominal_eq_helpers: nominal_eq_indices, - array_destroyers: array_destroyer_indices, - array_eq_helpers: array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - resolved_wasm_refs: None, - function_legalization: None, - layout: &closure_destroy_layout, - function_result: &closure_destroy_result, - control_depth: 0, - loop_stack: Vec::new(), - scope_stack: Vec::new(), - return_target: None, - }; - function_section.function(closure_destroy_type_index); - let mut wasm_function = - WasmFunction::new(closure_destroy_layout.wasm_locals().iter().copied()); - emitter.emit_closure_env_destructor( - &mut wasm_function, - &closure.info.env_layout, - closure.closure.closure, - )?; - wasm_function.instruction(&Instruction::End); - code_section.function(&wasm_function); - } - if !table_elements.is_empty() { - table_section.table(TableType { - element_type: callable_lowering.table_element_type(), - table64: callable_lowering.table64(), - minimum: table_elements.len() as u64, - maximum: None, - shared: false, - }); - element_section.active( - None, - &memory_model.const_expr_from_u32(0), - Elements::Functions(Cow::Owned(table_elements.clone())), - ); - } - - memory_section.memory(MemoryType { - minimum: memory_min_pages(stack_base as usize + 65_536), - maximum: None, - memory64: memory_model.memory64(), - shared: false, - page_size_log2: None, - }); - global_section.global( - GlobalType { val_type: word_type, mutable: true, shared: false }, - &memory_model.const_expr_from_u32(stack_base), - ); - if !static_data.bytes.is_empty() { - data_section.active( - 0, - &memory_model.const_expr_from_u32(0), - static_data.bytes.iter().copied(), - ); - } - - for runtime in &runtime_imports { - let (module_name, field_name) = import_provider.runtime_import(*runtime); - let module = abi_v2.graph.insert_string(module_name); - let field = abi_v2.graph.insert_string(field_name); - abi_v2.graph.raw_imports.push(RawImportRecord { - module, - field, - symbol: None, - signature: None, - }); - } - for intrinsic in &stage_imports { - let (module_name, field_name) = import_provider.stage_intrinsic_import(*intrinsic); - let module = abi_v2.graph.insert_string(module_name); - let field = abi_v2.graph.insert_string(field_name); - abi_v2.graph.raw_imports.push(RawImportRecord { - module, - field, - symbol: None, - signature: None, - }); - } - for (metadata_index, built) in abi_v2.functions.iter().enumerate() { - match built.linkage { - AbiV2LinkageKind::WasmImport => { - let instance = &abi_v2.graph.function_instances[metadata_index]; - let module = instance - .wasm_module_name - .expect("v2 import instance should record its module name"); - let field = instance - .wasm_field_name - .expect("v2 import instance should record its field name"); - abi_v2.graph.raw_imports.push(RawImportRecord { - module, - field, - symbol: Some(built.symbol_id), - signature: Some(built.signature_id), - }); - } - AbiV2LinkageKind::WasmExport => { - let typed_name = abi_v2.graph.insert_string(&built.wasm_field_name); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: typed_name, - symbol: Some(built.symbol_id), - signature: Some(built.signature_id), - }); - } - AbiV2LinkageKind::StageEntry | AbiV2LinkageKind::RawImport => {} - } - } - if needs_abi_v2_blob_helpers { - let alloc_export_name = abi_v2.graph.insert_string(mitki_abi::ABI_V2_ALLOC_EXPORT); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: alloc_export_name, - symbol: None, - signature: None, - }); - let blob_release_export_name = - abi_v2.graph.insert_string(mitki_abi::ABI_V2_BLOB_RELEASE_EXPORT); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: blob_release_export_name, - symbol: None, - signature: None, - }); - } - if needs_abi_v2_handle_helpers { - for helper_name in - [mitki_abi::ABI_V2_HANDLE_RETAIN_EXPORT, mitki_abi::ABI_V2_HANDLE_RELEASE_EXPORT] - { - let helper_export_name = abi_v2.graph.insert_string(helper_name); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: helper_export_name, - symbol: None, - signature: None, - }); - } - for trampoline in &handle_invoke_trampolines { - let invoke_name = abi_v2 - .graph - .insert_string(mitki_abi::handle_invoke_export_name(trampoline.signature_id)); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: invoke_name, - symbol: None, - signature: Some(trampoline.signature_id), - }); - } - } - let memory_name = abi_v2.graph.insert_string("memory"); - abi_v2.graph.raw_exports.push(RawExportRecord { - name: memory_name, - symbol: None, - signature: None, - }); - - if let Some(alloc_helper_type_index) = section_plan.abi_alloc_type_index { - let alloc_signature = memory_model.alloc_helper_signature(); - type_section.ty().function(alloc_signature.params, alloc_signature.results); - function_section.function(alloc_helper_type_index); - let alloc_helper = emit_alloc_helper(runtime_indices)?; - code_section.function(&alloc_helper); - } - if let Some(blob_release_type_index) = section_plan.abi_blob_release_type_index { - type_section.ty().function([word_type], []); - function_section.function(blob_release_type_index); - let blob_release = - emit_blob_release_helper(helper_indices, runtime_indices, closure_destroyers)?; - code_section.function(&blob_release); - } - if needs_abi_v2_handle_helpers { - if let Some(handle_retain_type_index) = section_plan.abi_handle_retain_type_index { - let lowered = callable_lowering.handle_retain_signature(memory_model); - type_section.ty().function(lowered.params, lowered.results); - function_section.function(handle_retain_type_index); - let handle_retain = emit_handle_retain_helper(helper_indices)?; - code_section.function(&handle_retain); - } - - if let Some(handle_release_type_index) = section_plan.abi_handle_release_type_index { - let lowered = callable_lowering.handle_release_signature(memory_model); - type_section.ty().function(lowered.params, lowered.results); - function_section.function(handle_release_type_index); - let handle_release = emit_handle_release_helper( - helper_indices, - runtime_indices, - closure_destroyers, - )?; - code_section.function(&handle_release); - } - - for (trampoline, (_, function_index)) in - handle_invoke_trampolines.iter().zip(section_plan.invoke_trampoline_indices.iter()) - { - let signature = &abi_v2.graph.signatures[trampoline.signature_id.0 as usize]; - let (params, results) = abi_v2_wasm_signature(&abi_v2.graph, signature)?; - let lowered = callable_lowering.boundary_invoke_signature( - memory_model, - PhysicalWasmSignature::new(params, results), - ); - let type_index = - section_plan.invoke_trampoline_type_indices[&trampoline.signature_id]; - type_section.ty().function(lowered.params, lowered.results); - function_section.function(type_index); - debug_assert_eq!( - *function_index, - section_plan - .invoke_trampoline_indices - .iter() - .find_map(|(signature_id, index)| { - (*signature_id == trampoline.signature_id).then_some(*index) - }) - .expect("planned handle trampoline should have a function index") - ); - let mir = module_plan - .wrapper_mir - .trampoline(trampoline.signature_id) - .expect("planned trampoline wrapper MIR should exist"); - let trampoline_function = emit_handle_invoke_trampoline_v2( - self, - trampoline.location, - trampoline.source_map, - mir, - &abi_v2.graph, - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyer_indices, - nominal_eq_indices, - array_destroyer_indices, - array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - )?; - code_section.function(&trampoline_function); - } - } - - for export in &boundary_plan.exports { - let metadata_index = export.metadata_index; - let built = &abi_v2.functions[metadata_index]; - let signature = &abi_v2.graph.signatures[built.signature_id.0 as usize]; - let (params, results) = abi_v2_wasm_signature(&abi_v2.graph, signature)?; - let type_index = section_plan.export_wrapper_type_indices[&export.instance]; - type_section.ty().function(params, results); - let hir_function = export.instance.location.hir_function(self.db); - let source_map = hir_function.source_map(self.db); - let mir = module_plan - .wrapper_mir - .export(&export.instance) - .expect("planned export wrapper MIR should exist"); - let wrapper = emit_export_wrapper_v2( - self, - export.instance.location, - source_map, - mir, - &abi_v2.graph, - direct_function_indices, - raw_import_function_indices, - runtime_indices, - stage_indices, - helper_indices, - nominal_destroyer_indices, - nominal_eq_indices, - array_destroyer_indices, - array_eq_indices, - callable_type_indices, - table_slots, - closure_destroyers, - )?; - function_section.function(type_index); - code_section.function(&wrapper); - } - - for export in §ion_plan.exports { - match export.target { - SectionExportTarget::Func(index) => { - export_section.export(&export.name, ExportKind::Func, index); - } - SectionExportTarget::Memory(index) => { - export_section.export(&export.name, ExportKind::Memory, index); - } - } - } - - let mut module = Module::new(); - module.section(&type_section); - if !import_section.is_empty() { - module.section(&import_section); - } - module.section(&function_section); - if !table_section.is_empty() { - module.section(&table_section); - } - module.section(&memory_section); - module.section(&global_section); - module.section(&export_section); - if !element_section.is_empty() { - module.section(&element_section); - } - module.section(&code_section); - if !data_section.is_empty() { - module.section(&data_section); - } - let encoded_v2 = encode_module_abi_v2(&abi_v2.graph).map_err(|error| { - Diagnostic::error( - format!("internal error: failed to encode Wasm ABI v2 metadata: {error}"), - self.file_range(), - ) - })?; - module.section(&CustomSection { - name: MITKI_ABI_V2_CUSTOM_SECTION.into(), - data: Cow::Owned(encoded_v2), - }); - let bytes = module.finish(); - validate_wasm_module_contract(&bytes, &abi_v2.graph).map_err(|error| { - Diagnostic::error( - format!("internal ABI contract validation failed: {error}"), - self.file_range(), - ) - })?; - Ok(bytes) - } - - pub(in crate::backend) fn build_layout( - &self, - instance: &ReachableInstance<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - signature: &FunctionSignature, - ) -> Result { - let (params, body, has_env_param) = match instance { - ReachableInstance::Function(_) => (function.params().to_vec(), function.body(), false), - ReachableInstance::Closure(closure) => { - let info = self.closure_info(closure, function, inference)?; - (info.params, info.body, true) - } - }; - let owner_instance = instance.owner_instance(); - let mut builder = LayoutBuilder::new( - self, - &owner_instance, - function, - inference, - signature, - params, - has_env_param, - ); - builder.add_params()?; - if body != ExprId::ZERO { - builder.visit(body.into())?; - } - let layout = builder.finish(); - StoragePlanValidator::validate( - self, - &layout, - self.function_range(owner_instance.location), - )?; - Ok(layout) - } - - pub(in crate::backend) fn build_lowered_kernel_function( - &self, - instance: &ReachableInstance<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - layout: &FunctionLayout, - static_data: &StaticData<'db>, - ) -> Result, Diagnostic> { - let mut lowerer = BackendLowerer::new( - self, - instance, - function, - source_map, - inference, - layout, - static_data, - )?; - lowerer.lower() - } - - pub(in crate::backend) fn build_static_data_for( - &self, - reachability: &plan::ReachabilityGraph<'db>, - ) -> Result, Diagnostic> { - let mut builder = StaticDataBuilder::default(); - for instance in &reachability.functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let source_map = hir_function.source_map(self.db); - if function.body() != ExprId::ZERO { - builder.expr(self, instance.location, function, source_map, function.body())?; - } - } - Ok(builder.finish()) - } -} - -struct LayoutBuilder<'a, 'db> { - backend: &'a Backend<'db>, - instance: &'a InstanceKey<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - resolver: Resolver<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - signature: &'a FunctionSignature, - params: Vec, - local_plan: LocalPlanBuilder, - frame_plan: FramePlanBuilder, - slots: FxHashMap, - param_names: Vec, - raw_params: Vec, - temps: FxHashMap, - pattern_scalar_locals: FxHashMap, - nominal_locals: FxHashMap, - array_repeat_locals: FxHashMap, -} - -impl<'a, 'db> LayoutBuilder<'a, 'db> { - fn new( - backend: &'a Backend<'db>, - instance: &'a InstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - signature: &'a FunctionSignature, - params: Vec, - has_env_param: bool, - ) -> Self { - let mut local_plan = LocalPlanBuilder::new(0); - let word_type = backend.memory_model_strategy().word_type(); - if has_env_param { - local_plan.add_param(LocalPurpose::EnvPtrParam, None, Some(word_type)); - } - if signature.result.is_aggregate() { - local_plan.add_param(LocalPurpose::ResultPtrParam, None, Some(word_type)); - } - Self { - backend, - instance, - location: instance.location, - function, - resolver: Resolver::new(backend.db, instance.location), - inference, - signature, - params, - local_plan, - frame_plan: FramePlanBuilder::default(), - slots: FxHashMap::default(), - param_names: Vec::new(), - raw_params: Vec::new(), - temps: FxHashMap::default(), - pattern_scalar_locals: FxHashMap::default(), - nominal_locals: FxHashMap::default(), - array_repeat_locals: FxHashMap::default(), - } - } - - fn add_params(&mut self) -> Result<(), Diagnostic> { - let nodes = self.function.node_store(); - let params = self.params.clone(); - let signature_params = self.signature.params.clone(); - for (ordinal, (param, abi)) in params.into_iter().zip(signature_params).enumerate() { - let (pattern, _) = nodes.param(param); - let word_type = self.backend.memory_model_strategy().word_type(); - let value_type = match &abi { - AbiTy::Scalar(ty) => backend_ty_value_type(*ty), - AbiTy::Aggregate(_) => Some(word_type), - }; - let raw_slot = LocalSlot { - abi: abi.clone(), - local_index: self.local_plan.add_param( - LocalPurpose::RawParam { ordinal }, - Some(abi.clone()), - value_type, - ), - frame_slot: None, - }; - self.raw_params.push(raw_slot.clone()); - - if let Some(name) = self.simple_binding_name(pattern) { - self.param_names.push(name); - let slot = if self.param_binding_needs_stable_slot(name)? { - LocalSlot { - abi: abi.clone(), - local_index: Some(self.local_plan.alloc_user_local( - LocalPurpose::UserBinding { name }, - Some(abi.clone()), - value_type.expect("mutable aggregate param should lower to a pointer"), - )), - frame_slot: None, - } - } else { - raw_slot - }; - self.slots.insert(name, slot); - continue; - } - - for name in nodes.pattern_binding_names(pattern) { - self.param_names.push(name); - self.ensure_binding_slot(name)?; - } - } - Ok(()) - } - - fn param_binding_needs_stable_slot(&self, name: NameId) -> Result { - let source_map = self.location.hir_function(self.backend.db).source_map(self.backend.db); - if !source_map.is_mutable_binding(name) { - return Ok(false); - } - - Ok(self.node_abi(name.into())?.is_aggregate()) - } - - fn finish(mut self) -> FunctionLayout { - let word_type = self.backend.memory_model_strategy().word_type(); - self.local_plan.alloc_scratch(ScratchLocalKind::ScratchI32, word_type); - self.local_plan.alloc_scratch(ScratchLocalKind::ScratchI32Aux, word_type); - self.local_plan.alloc_scratch(ScratchLocalKind::ObjectI32, word_type); - self.local_plan.alloc_scratch(ScratchLocalKind::ScratchF64, ValType::F64); - self.local_plan.alloc_scratch(ScratchLocalKind::ScratchI64, ValType::I64); - - let frame_plan = self.frame_plan.finish(); - if frame_plan.size > 0 { - self.local_plan.alloc_scratch(ScratchLocalKind::FrameBase, word_type); - } - - FunctionLayout::new( - self.local_plan.finish(), - frame_plan, - FunctionLayoutLookups { - slots: self.slots, - param_names: self.param_names, - raw_params: self.raw_params, - temps: self.temps, - pattern_scalar_locals: self.pattern_scalar_locals, - nominal_locals: self.nominal_locals, - array_repeat_locals: self.array_repeat_locals, - }, - ) - } - - fn visit(&mut self, node: StmtId) -> Result<(), Diagnostic> { - let nodes = self.function.node_store(); - if nodes.node_kind(node) == NodeKind::ReturnStmt { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(node).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.maybe_alloc_temp(value)?; - } - } else if let Some(expr) = stmt_as_expr(nodes, node) { - self.maybe_alloc_temp(expr)?; - } - - match nodes.node_kind(node) { - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(node).expect("LocalVar node mismatch")); - for name in nodes.pattern_binding_names(var.pattern) { - self.ensure_binding_slot(name)?; - } - if !self.is_simple_binding_pattern(var.pattern) && var.initializer != ExprId::ZERO { - self.ensure_pattern_source_storage(var.initializer)?; - } - - if var.initializer != ExprId::ZERO { - self.visit(var.initializer.into())?; - } - } - NodeKind::AssignStmt => { - let (target, value) = - nodes.assign_stmt(nodes.as_assign_stmt(node).expect("AssignStmt mismatch")); - self.visit(target.into())?; - self.visit(value.into())?; - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(node).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.visit(value.into())?; - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(node).expect("Block node mismatch")); - for stmt in stmts.iter() { - self.visit(stmt)?; - } - if tail != ExprId::ZERO { - self.visit(tail.into())?; - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(node).expect("Call node mismatch")); - self.visit(callee.into())?; - for arg in args.iter() { - self.visit(arg.into())?; - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(node).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.visit(item.into())?; - } - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(node).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.visit(base.into())?; - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(node).expect("Binary node mismatch")); - self.visit(binary.lhs.into())?; - self.visit(binary.rhs.into())?; - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(node).expect("Prefix node mismatch")); - self.visit(prefix.expr.into())?; - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(node).expect("If node mismatch")); - self.visit(if_expr.cond.into())?; - if if_expr.then_branch != ExprId::ZERO { - self.visit(if_expr.then_branch.into())?; - } - if if_expr.else_branch != ExprId::ZERO { - self.visit(if_expr.else_branch.into())?; - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(node).expect("Match node mismatch")); - self.ensure_pattern_source_storage(scrutinee)?; - self.visit(scrutinee.into())?; - for arm in arms.iter() { - let (pattern, expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - for name in nodes.pattern_binding_names(pattern) { - self.ensure_binding_slot(name)?; - } - self.visit(expr.into())?; - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(node).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.visit(body.into())?; - } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(node).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - self.visit(body.into())?; - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(node).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.visit(items.get(index).unwrap().into())?; - index += 2; - } - } - _ => {} - } - - Ok(()) - } - - fn resolve_name(&self, expr: ExprId) -> Result, Diagnostic> { - let nodes = self.function.node_store(); - let Some(name) = nodes.as_name(expr) else { - return Err(Diagnostic::error( - "internal error: expected name expression during layout planning", - self.backend.function_range(self.location), - )); - }; - let symbol = nodes.name(name); - let mut resolver = self.resolver.clone(); - let guard = resolver.scopes_for_node(expr); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - resolution.ok_or_else(|| { - Diagnostic::error( - "internal error: unresolved name during layout planning", - self.backend.function_range(self.location), - ) - }) - } - - fn stack_alloc_count(&self, expr: ExprId) -> Result, Diagnostic> { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Call { - return Ok(None); - } - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - if args.len() != 1 { - return Ok(None); - } - if !matches!( - self.resolve_name(callee), - Ok(BindingId::CompilerIntrinsic(CompilerIntrinsic::StackAlloc)) - ) { - return Ok(None); - } - let count_expr = args.iter().next().expect("single argument"); - if nodes.node_kind(count_expr) != NodeKind::Int { - return Err(Diagnostic::error( - "`stack_alloc` currently requires a constant integer count", - self.backend.function_range(self.location), - )); - } - let count = parse_int_literal( - nodes.int(nodes.as_int(count_expr).expect("Int node mismatch")), - self.backend.db, - ) - .map_err(|message| { - Diagnostic::error(message, self.backend.function_range(self.location)) - })?; - u32::try_from(count).map(Some).map_err(|_overflow| { - Diagnostic::error( - "`stack_alloc` count must fit in `u32`", - self.backend.function_range(self.location), - ) - }) - } - - fn maybe_alloc_temp(&mut self, expr: ExprId) -> Result<(), Diagnostic> { - if !self.temps.contains_key(&expr) - && let Some(count) = self.stack_alloc_count(expr)? - { - let ty = self - .inference - .type_of_node(expr) - .map(|ty| self.backend.specialize_ty(self.instance, ty)) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred `stack_alloc` type", - self.backend.function_range(self.location), - ) - })?; - let TyKind::Pointer { pointee, .. } = ty.kind(self.backend.db) else { - return Err(Diagnostic::error( - "internal error: `stack_alloc` did not infer a pointer type", - self.backend.function_range(self.location), - )); - }; - let pointee_abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - *pointee, - "Wasm backend does not support this `stack_alloc` pointee type", - ) - .map_err(|message| { - Diagnostic::error(message, self.backend.function_range(self.location)) - })?; - let pointee_layout = crate::layout::abi_layout(&pointee_abi).ok_or_else(|| { - Diagnostic::error( - "internal error: missing `stack_alloc` pointee layout", - self.backend.function_range(self.location), - ) - })?; - let size = pointee_layout.size.checked_mul(count).ok_or_else(|| { - Diagnostic::error( - "`stack_alloc` size overflowed the current frame allocator", - self.backend.function_range(self.location), - ) - })?; - let frame_slot = self.frame_plan.alloc_slot( - FrameSlotPurpose::StackAlloc(expr), - size, - pointee_layout.align.max(1), - ); - self.temps.insert(expr, TempSlot { frame_slot }); - return Ok(()); - } - - let Some(abi) = self - .inference - .type_of_node(expr) - .map(|ty| self.backend.specialize_ty(self.instance, ty)) - .and_then(|ty| crate::capability::supported_value_abi(self.backend.db, ty)) - else { - return Ok(()); - }; - let AbiTy::Aggregate(layout) = abi else { - if matches!(abi, AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(_) | RefKind::Array(_)))) - && !self.nominal_locals.contains_key(&expr) - { - let local = self.local_plan.alloc_spill( - LocalPurpose::NominalTemp { expr }, - Some(abi.clone()), - ValType::I32, - ); - self.nominal_locals.insert(expr, local); - } - if self.function.node_store().node_kind(expr) == NodeKind::ArrayRepeat - && !self.array_repeat_locals.contains_key(&expr) - { - let local = self.local_plan.alloc_spill( - LocalPurpose::ArrayRepeatTemp { expr }, - Some(abi.clone()), - ValType::I32, - ); - self.array_repeat_locals.insert(expr, local); - } - return Ok(()); - }; - - if !self.temps.contains_key(&expr) { - let frame_slot = - self.frame_plan.alloc_slot(FrameSlotPurpose::Temp(expr), layout.size, layout.align); - self.temps.insert(expr, TempSlot { frame_slot }); - } - Ok(()) - } - - fn node_abi(&self, expr: ExprId) -> Result { - let ty = self.inference.type_of_node(expr).ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred expression type", - self.backend.function_range(self.location), - ) - })?; - let ty = self.backend.specialize_ty(self.instance, ty); - crate::capability::supported_value_abi_or_message( - self.backend.db, - ty, - "Wasm backend does not support this value type", - ) - .map_err(|message| Diagnostic::error(message, self.backend.function_range(self.location))) - } - - fn simple_binding_name(&self, pattern: PatId) -> Option { - let nodes = self.function.node_store(); - let binding = nodes.as_pat_binding(pattern)?; - let (name, _) = nodes.pat_binding(binding); - Some(name) - } - - fn is_simple_binding_pattern(&self, pattern: PatId) -> bool { - self.simple_binding_name(pattern).is_some() - } - - fn ensure_binding_slot(&mut self, name: NameId) -> Result<(), Diagnostic> { - if self.slots.contains_key(&name) { - return Ok(()); - } - let abi = self.node_abi(name.into())?; - let (local_index, frame_slot) = match &abi { - AbiTy::Scalar(ty) => { - let local_index = backend_ty_value_type(*ty).map(|value_type| { - self.local_plan.alloc_user_local( - LocalPurpose::UserBinding { name }, - Some(abi.clone()), - value_type, - ) - }); - (local_index, None) - } - AbiTy::Aggregate(layout) => ( - None, - Some(self.frame_plan.alloc_slot( - FrameSlotPurpose::Binding(name), - layout.size, - layout.align, - )), - ), - }; - self.slots.insert(name, LocalSlot { abi, local_index, frame_slot }); - Ok(()) - } - - fn ensure_pattern_source_storage(&mut self, expr: ExprId) -> Result<(), Diagnostic> { - match self.node_abi(expr)? { - AbiTy::Scalar(BackendTy::Unit) => Ok(()), - AbiTy::Scalar(ty) => { - if !self.pattern_scalar_locals.contains_key(&expr) { - let local = self.local_plan.alloc_spill( - LocalPurpose::PatternSource { expr }, - Some(AbiTy::Scalar(ty)), - backend_ty_value_type(ty) - .expect("non-unit scalar should have a value type"), - ); - self.pattern_scalar_locals.insert(expr, local); - } - Ok(()) - } - AbiTy::Aggregate(_) => self.maybe_alloc_temp(expr), - } - } -} - -struct BackendLowerer<'a, 'db> { - backend: &'a Backend<'db>, - owner_instance: InstanceKey<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - resolver: Resolver<'db>, - layout: &'a FunctionLayout, - static_data: &'a StaticData<'db>, - params: Vec, - param_tys: Vec>, - result_ty: Ty<'db>, - body: ExprId, - capture_fields: FxHashMap, -} - -impl<'a, 'db> BackendLowerer<'a, 'db> { - fn new( - backend: &'a Backend<'db>, - instance: &ReachableInstance<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - layout: &'a FunctionLayout, - static_data: &'a StaticData<'db>, - ) -> Result { - let owner_instance = instance.owner_instance(); - let (location, params, param_tys, result_ty, body, capture_fields) = match &instance { - ReachableInstance::Function(instance) => { - let (param_tys, result_ty) = - backend.function_signature_types(instance, function, inference)?; - ( - instance.location, - function.params().to_vec(), - param_tys, - result_ty, - function.body(), - FxHashMap::default(), - ) - } - ReachableInstance::Closure(closure) => { - let info = backend.closure_info(closure, function, inference)?; - let closure_ty = backend - .specialized_expr_ty(&owner_instance, inference, closure.closure) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred closure type during backend lowering", - backend.function_range(closure.owner), - ) - })?; - let TyKind::Function { inputs, output } = closure_ty.kind(backend.db) else { - return Err(Diagnostic::error( - "internal error: closure value did not lower to a function type", - backend.function_range(closure.owner), - )); - }; - let capture_fields = info - .captures - .into_iter() - .map(|capture| (capture.binding, capture.field)) - .collect(); - (closure.owner, info.params, inputs.clone(), *output, info.body, capture_fields) - } - }; - Ok(Self { - backend, - owner_instance, - location, - function, - source_map, - inference, - resolver: Resolver::new(backend.db, location), - layout, - static_data, - params, - param_tys, - result_ty, - body, - capture_fields, - }) - } - - fn lower(&mut self) -> Result, Diagnostic> { - let mut param_inits = Vec::new(); - for (index, (¶m, ty)) in - self.params.iter().zip(self.param_tys.iter().copied()).enumerate() - { - let (pattern, _) = self.function.node_store().param(param); - if self.function.node_store().as_pat_binding(pattern).is_some() { - let name = self - .function - .node_store() - .pat_binding( - self.function.node_store().as_pat_binding(pattern).expect("PatBinding"), - ) - .0; - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing parameter binding slot during backend lowering", - self.node_range(name.into()), - ) - })?; - let raw_abi = - self.layout.raw_params.get(index).map(|slot| slot.abi.clone()).ok_or_else( - || { - Diagnostic::error( - "internal error: missing raw parameter slot during backend \ - lowering", - self.backend.function_range(self.location), - ) - }, - )?; - if slot.abi == raw_abi - && slot.local_index == self.layout.raw_params[index].local_index - && slot.frame_slot == self.layout.raw_params[index].frame_slot - { - continue; - } - param_inits.push(KernelBindingInit { - pattern: self.lower_pattern(pattern, ty, &slot.abi, ExprId::ZERO)?, - source: FunctionKernelBindingSource::Param { - index, - abi: raw_abi, - source: ExprId::ZERO, - }, - }); - continue; - } - let abi = self.layout.raw_params.get(index).map(|slot| slot.abi.clone()).ok_or_else( - || { - Diagnostic::error( - "internal error: missing raw parameter slot during backend lowering", - self.backend.function_range(self.location), - ) - }, - )?; - param_inits.push(KernelBindingInit { - pattern: self.lower_pattern(pattern, ty, &abi, ExprId::ZERO)?, - source: FunctionKernelBindingSource::Param { index, abi, source: ExprId::ZERO }, - }); - } - let body = (self.body != ExprId::ZERO) - .then(|| self.lower_expr_as(self.body, Some(self.result_ty))) - .transpose()?; - Ok(FunctionKernelLowered { param_inits, body }) - } - - fn lower_stmt(&mut self, stmt: StmtId) -> Result, Diagnostic> { - let nodes = self.function.node_store(); - if nodes.node_kind(stmt) == NodeKind::LocalVar { - let var = nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar node mismatch")); - if let Some(binding) = nodes.as_pat_binding(var.pattern) { - let (name, _) = nodes.pat_binding(binding); - let abi = - self.layout.slots.get(&name).map(|slot| slot.abi.clone()).ok_or_else(|| { - Diagnostic::error( - "internal error: missing local slot during backend lowering", - self.node_range(name.into()), - ) - })?; - let binding_ty = self - .backend - .specialized_expr_ty(&self.owner_instance, self.inference, name.into()) - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - let initializer = (var.initializer != ExprId::ZERO) - .then(|| self.lower_expr_as(var.initializer, Some(binding_ty))) - .transpose()?; - return Ok(KernelStmt::Local { name, abi, initializer }); - } - let initializer = (var.initializer != ExprId::ZERO) - .then(|| self.lower_expr(var.initializer)) - .transpose()?; - let source_ty = self - .backend - .specialized_expr_ty(&self.owner_instance, self.inference, var.initializer) - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - let source_abi = self.expr_abi(var.initializer)?; - let Some(initializer) = initializer else { - return Err(Diagnostic::error( - "internal error: non-binding local pattern is missing an initializer", - self.node_range(stmt.node_id()), - )); - }; - return Ok(KernelStmt::Pattern(KernelBindingInit { - pattern: self.lower_pattern( - var.pattern, - source_ty, - &source_abi, - var.initializer, - )?, - source: FunctionKernelBindingSource::Value(initializer), - })); - } - if nodes.node_kind(stmt) == NodeKind::ReturnStmt { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - let value = (value != ExprId::ZERO) - .then(|| self.lower_expr_as(value, Some(self.result_ty))) - .transpose()?; - return Ok(KernelStmt::Return { source: stmt.node_id(), value }); - } - - if nodes.node_kind(stmt) == NodeKind::AssignStmt { - let (target, value) = - nodes.assign_stmt(nodes.as_assign_stmt(stmt).expect("AssignStmt mismatch")); - let target_ty = self.lowerable_expr_ty(target)?; - let target_abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - target_ty, - "Wasm backend does not support this assignment target type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(target)))?; - let value = self.lower_expr_as(value, Some(target_ty))?; - - if nodes.node_kind(target) == NodeKind::Name { - return match self.resolve_name(target)? { - BindingId::Local(name) => { - Ok(KernelStmt::Assign { name, abi: target_abi, value }) - } - BindingId::Param(_) => { - let addr = self.lower_place_address(target)?; - Ok(KernelStmt::Expr(KernelExpr { - source: stmt.node_id(), - abi: AbiTy::Scalar(BackendTy::Unit), - ownership: ValueOwnership::None, - kind: KernelExprKind::MemoryWrite { - addr: Box::new(addr), - value: Box::new(value), - }, - })) - } - _ => Err(Diagnostic::error( - "internal error: invalid assignment target reached backend lowering", - self.node_range(target), - )), - }; - } - - let addr = self.lower_place_address(target)?; - return Ok(KernelStmt::Expr(KernelExpr { - source: stmt.node_id(), - abi: AbiTy::Scalar(BackendTy::Unit), - ownership: ValueOwnership::None, - kind: KernelExprKind::MemoryWrite { addr: Box::new(addr), value: Box::new(value) }, - })); - } - - let expr = stmt_as_expr(nodes, stmt).ok_or_else(|| { - Diagnostic::error( - "internal error: unsupported statement reached backend lowering", - self.backend.function_range(self.location), - ) - })?; - Ok(KernelStmt::Expr(self.lower_expr(expr)?)) - } - - fn lower_expr(&mut self, expr: ExprId) -> Result, Diagnostic> { - self.lower_expr_as(expr, None) - } - - fn lower_expr_as( - &mut self, - expr: ExprId, - expected_ty: Option>, - ) -> Result, Diagnostic> { - let inferred_ty = self.lowerable_expr_ty(expr)?; - let actual_ty = match (inferred_ty.kind(self.backend.db), expected_ty) { - (TyKind::Unknown, Some(expected)) => expected, - (TyKind::Tuple(items), Some(expected)) - if items.is_empty() - && !matches!(expected.kind(self.backend.db), TyKind::Tuple(expected_items) if expected_items.is_empty()) => - { - expected - } - _ => inferred_ty, - }; - let abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - actual_ty, - "Wasm backend does not support this value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let nodes = self.function.node_store(); - let kind = match nodes.node_kind(expr) { - NodeKind::Name => self.lower_name_expr(expr)?, - NodeKind::True => KernelExprKind::Bool(true), - NodeKind::False => KernelExprKind::Bool(false), - NodeKind::Int => { - let literal = nodes.int(nodes.as_int(expr).expect("Int node mismatch")); - KernelExprKind::Int( - parse_int_literal(literal, self.backend.db) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?, - ) - } - NodeKind::Float => { - let literal = nodes.float(nodes.as_float(expr).expect("Float node mismatch")); - KernelExprKind::Float( - parse_float_literal(literal, self.backend.db) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?, - ) - } - NodeKind::String => { - let literal = nodes.string(nodes.as_string(expr).expect("String node mismatch")); - let offset = self.static_data.offset(literal).ok_or_else(|| { - Diagnostic::error( - "internal error: missing static string data during backend lowering", - self.node_range(expr), - ) - })?; - KernelExprKind::String(offset) - } - NodeKind::Char => { - let literal = nodes.char(nodes.as_char(expr).expect("Char node mismatch")); - KernelExprKind::Char( - decode_char_literal(literal, self.backend.db) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?, - ) - } - NodeKind::Tuple if matches!(abi, AbiTy::Scalar(BackendTy::Unit)) => { - KernelExprKind::Unit - } - NodeKind::Tuple => { - let layout = abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: tuple expression is missing aggregate layout", - self.node_range(expr), - ) - })?; - let fields = layout.fields().ok_or_else(|| { - Diagnostic::error( - "internal error: tuple layout is missing field metadata", - self.node_range(expr), - ) - })?; - let items = nodes - .tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")) - .iter() - .collect::>(); - let item_tys = match actual_ty.kind(self.backend.db) { - TyKind::Tuple(items) => items.clone(), - _ => vec![Ty::new(self.backend.db, TyKind::Unknown); items.len()], - }; - let fields = fields - .iter() - .zip(items.iter().zip(item_tys.iter())) - .map(|(field, (&item, &item_ty))| { - self.lower_expr_as(item, Some(item_ty)) - .map(|value| KernelFieldValue { field: field.clone(), value }) - }) - .collect::, _>>()?; - KernelExprKind::Tuple { fields } - } - NodeKind::Array => { - let layout = self.array_layout_for_expr(expr)?; - let item_ty = match actual_ty.kind(self.backend.db) { - TyKind::Array(item_ty) => Some(*item_ty), - _ => None, - }; - let items = nodes - .array(nodes.as_array(expr).expect("Array node mismatch")) - .iter() - .map(|item| self.lower_expr_as(item, item_ty)) - .collect::, _>>()?; - KernelExprKind::Array { layout, items } - } - NodeKind::ArrayRepeat => { - let layout = self.array_layout_for_expr(expr)?; - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - let item_ty = match actual_ty.kind(self.backend.db) { - TyKind::Array(item_ty) => Some(*item_ty), - _ => None, - }; - KernelExprKind::ArrayRepeat { - layout, - value: Box::new(self.lower_expr_as(value, item_ty)?), - len: Box::new(self.lower_expr(len)?), - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - let mut stmt_ids = stmts.iter().collect::>(); - let expected_value = expected_ty.filter(|expected| { - !matches!(expected.kind(self.backend.db), TyKind::Tuple(items) if items.is_empty()) - }); - let tail_expr = if tail != ExprId::ZERO { - Some(tail) - } else if expected_value.is_some() { - let candidate = stmt_ids.last().and_then(|stmt| stmt_as_expr(nodes, *stmt)); - if candidate.is_some() { - stmt_ids.pop(); - } - candidate - } else { - None - }; - let stmts = stmt_ids - .iter() - .map(|stmt| self.lower_stmt(*stmt)) - .collect::, _>>()?; - let tail = tail_expr - .map(|tail| self.lower_expr_as(tail, Some(actual_ty))) - .transpose()? - .map(Self::clone_if_borrowed) - .map(Box::new); - KernelExprKind::Block { stmts, tail } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - if body == ExprId::ZERO { - KernelExprKind::Unit - } else { - return self.lower_expr_as(body, expected_ty); - } - } - NodeKind::Call => { - if let Some(intrinsic_kind) = self.lower_compiler_intrinsic_call(expr, &abi)? { - intrinsic_kind - } else if self.is_variant_constructor_call(expr)? { - let (callee, args) = - nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - let variant = self.enum_variant_layout_for_call(expr, callee)?; - let args = args - .iter() - .map(|arg| self.lower_expr(arg)) - .collect::, _>>()?; - KernelExprKind::VariantCall { variant, args } - } else { - let (callee, args) = - nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - let param_tys = self.call_param_tys(callee)?; - if self.is_direct_call(callee)? { - let target = self.lower_call_target(callee, expr)?; - let target_function = self.direct_call_target_function(callee)?; - let call_args = if let Some(method) = self.resolve_method_call(callee)? { - let mut call_args = Vec::with_capacity(args.len() + 1); - call_args.push(method.receiver); - call_args.extend(args.iter()); - call_args - } else { - args.iter().collect::>() - }; - let args = self.lower_call_args( - call_args.as_slice(), - ¶m_tys, - target_function, - )?; - KernelExprKind::Call { target, args } - } else { - let signature = self.call_signature(callee)?; - let args = self.lower_call_args( - args.iter().collect::>().as_slice(), - ¶m_tys, - None, - )?; - KernelExprKind::IndirectCall { - callee: Box::new(self.lower_expr(callee)?), - signature, - args, - } - } - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - KernelExprKind::Binary { - op: self.lower_binary_op(binary.op)?, - lhs: Box::new(self.lower_expr(binary.lhs)?), - rhs: Box::new(self.lower_expr(binary.rhs)?), - } - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - KernelExprKind::Prefix { - op: self.lower_prefix_op(prefix.op)?, - expr: Box::new(self.lower_expr(prefix.expr)?), - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - let body = (body != ExprId::ZERO) - .then(|| self.lower_expr(body)) - .transpose()? - .map(Box::new); - KernelExprKind::Loop { body } - } - NodeKind::BreakExpr => KernelExprKind::Break, - NodeKind::ContinueExpr => KernelExprKind::Continue, - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - KernelExprKind::If { - cond: Box::new(self.lower_expr(if_expr.cond)?), - then_branch: (if_expr.then_branch != ExprId::ZERO) - .then(|| self.lower_expr_as(if_expr.then_branch, Some(actual_ty))) - .transpose()? - .map(Self::clone_if_borrowed) - .map(Box::new), - else_branch: (if_expr.else_branch != ExprId::ZERO) - .then(|| self.lower_expr_as(if_expr.else_branch, Some(actual_ty))) - .transpose()? - .map(Self::clone_if_borrowed) - .map(Box::new), - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - let scrutinee_ty = self - .backend - .specialized_expr_ty(&self.owner_instance, self.inference, scrutinee) - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - let scrutinee_abi = self.expr_abi(scrutinee)?; - let arms = arms - .iter() - .map(|arm| { - let (pattern, body) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - Ok(KernelMatchArm { - pattern: self.lower_pattern( - pattern, - scrutinee_ty, - &scrutinee_abi, - expr, - )?, - body: Self::clone_if_borrowed( - self.lower_expr_as(body, Some(actual_ty))?, - ), - }) - }) - .collect::, Diagnostic>>()?; - KernelExprKind::Match { scrutinee: Box::new(self.lower_expr(scrutinee)?), arms } - } - NodeKind::Field if self.is_enum_variant_ref(expr)? => { - KernelExprKind::VariantValue { variant: self.enum_variant_layout(expr)? } - } - NodeKind::Field => { - let (base, field) = self.project_field(expr)?; - if self.should_lower_field_via_stable_address(base)? { - let ownership = if field.ty.contains_heap_refs() { - ValueOwnership::Borrowed - } else { - ValueOwnership::None - }; - let (addr, access) = if field.ty.is_aggregate() { - (self.lower_place_address(expr)?, None) - } else { - (self.lower_place_address(base)?, abi_mem_access(&field.ty, field.offset)) - }; - return Ok(KernelExpr { - source: expr, - abi: field.ty.clone(), - ownership, - kind: KernelExprKind::MemoryRead { addr: Box::new(addr), access }, - }); - } - let base = self.lower_expr(base)?; - let ownership = if field.ty.contains_heap_refs() { - if base.ownership.is_owned() { - ValueOwnership::Owned - } else { - ValueOwnership::Borrowed - } - } else { - ValueOwnership::None - }; - return Ok(KernelExpr { - source: expr, - abi: field.ty.clone(), - ownership, - kind: KernelExprKind::Field { base: Box::new(base), field }, - }); - } - NodeKind::StructExpr => { - let layout = if let Some(layout) = abi.aggregate() { - layout.clone() - } else { - self.nominal_payload_layout(expr)? - }; - let fields = layout.fields().ok_or_else(|| { - Diagnostic::error( - "internal error: struct or record layout is missing field metadata", - self.node_range(expr), - ) - })?; - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut values = FxHashMap::default(); - let mut index = if has_struct_name { 1 } else { 0 }; - while index + 1 < items.len() { - let field_name = items.get(index).unwrap(); - let value_expr = items.get(index + 1).unwrap(); - index += 2; - - let name = nodes.name(nodes.as_name(field_name).expect("field name")); - values.insert(symbol_bits(name), value_expr); - } - - let fields = fields - .iter() - .filter_map(|field| { - Some((field.clone(), values.get(&field.name_bits?)?.to_owned())) - }) - .map(|(field, value_expr)| { - self.lower_expr(value_expr).map(|value| KernelFieldValue { field, value }) - }) - .collect::, _>>()?; - KernelExprKind::Struct { fields } - } - NodeKind::Closure => self.lower_closure_expr(expr)?, - kind => { - return Err(Diagnostic::error( - format!("internal error: unsupported node in backend lowering: {kind:?}"), - self.node_range(expr), - )); - } - }; - - let ownership = Self::expr_ownership(&kind, &abi); - let lowered = KernelExpr { source: expr, abi, ownership, kind }; - let lowered = self.coerce_expr_to_expected(lowered, actual_ty, expected_ty)?; - self.require_copyable_value_use(expr, actual_ty, &lowered)?; - Ok(lowered) - } - - fn array_layout_for_expr(&self, expr: ExprId) -> Result { - let ty = self - .backend - .specialized_expr_ty(&self.owner_instance, self.inference, expr) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred array type during backend lowering", - self.node_range(expr), - ) - })?; - crate::capability::supported_array_runtime_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this array value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr))) - } - - fn clone_if_borrowed(expr: KernelExpr<'db>) -> KernelExpr<'db> { - if !expr.abi.contains_heap_refs() || !expr.ownership.is_borrowed() { - return expr; - } - - KernelExpr { - source: expr.source, - abi: expr.abi.clone(), - ownership: ValueOwnership::Owned, - kind: KernelExprKind::Clone { value: Box::new(expr) }, - } - } - - fn coerce_expr_to_expected( - &self, - expr: KernelExpr<'db>, - actual_ty: Ty<'db>, - expected_ty: Option>, - ) -> Result, Diagnostic> { - let Some(expected_ty) = expected_ty else { - return Ok(expr); - }; - if expected_ty == actual_ty { - return Ok(expr); - } - - let Some((union_abi, variant)) = - self.union_coercion_target(actual_ty, expected_ty, expr.source)? - else { - return Ok(expr); - }; - - match expr.kind { - KernelExprKind::Block { stmts, tail } => { - let tail = match tail { - Some(tail) => { - Some(Box::new(self.coerce_lowered_expr_to_expected(*tail, expected_ty)?)) - } - None => Some(Box::new(Self::unit_union_expr( - expr.source, - union_abi.clone(), - variant.clone(), - ))), - }; - let kind = KernelExprKind::Block { stmts, tail }; - let ownership = Self::expr_ownership(&kind, &union_abi); - Ok(KernelExpr { source: expr.source, abi: union_abi, ownership, kind }) - } - KernelExprKind::If { cond, then_branch, else_branch } => { - let then_branch = then_branch - .map(|branch| self.coerce_lowered_expr_to_expected(*branch, expected_ty)) - .transpose()? - .map(Box::new); - let else_branch = else_branch - .map(|branch| self.coerce_lowered_expr_to_expected(*branch, expected_ty)) - .transpose()? - .map(Box::new); - let kind = KernelExprKind::If { cond, then_branch, else_branch }; - let ownership = Self::expr_ownership(&kind, &union_abi); - Ok(KernelExpr { source: expr.source, abi: union_abi, ownership, kind }) - } - KernelExprKind::Match { scrutinee, arms } => { - let arms = arms - .into_iter() - .map(|arm| { - Ok(KernelMatchArm { - pattern: arm.pattern, - body: self.coerce_lowered_expr_to_expected(arm.body, expected_ty)?, - }) - }) - .collect::, Diagnostic>>()?; - let kind = KernelExprKind::Match { scrutinee, arms }; - let ownership = Self::expr_ownership(&kind, &union_abi); - Ok(KernelExpr { source: expr.source, abi: union_abi, ownership, kind }) - } - _ => { - let source = expr.source; - let value = if matches!(expr.abi, AbiTy::Scalar(BackendTy::Unit)) { - None - } else { - Some(Box::new(expr)) - }; - let kind = KernelExprKind::Union { variant, value }; - let ownership = Self::expr_ownership(&kind, &union_abi); - Ok(KernelExpr { source, abi: union_abi, ownership, kind }) - } - } - } - - fn coerce_lowered_expr_to_expected( - &self, - expr: KernelExpr<'db>, - expected_ty: Ty<'db>, - ) -> Result, Diagnostic> { - let actual_ty = self - .backend - .specialized_expr_ty(&self.owner_instance, self.inference, expr.source) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred type during union coercion", - self.node_range(expr.source), - ) - })?; - self.coerce_expr_to_expected(expr, actual_ty, Some(expected_ty)) - } - - fn require_copyable_value_use( - &self, - expr: ExprId, - actual_ty: Ty<'db>, - lowered: &KernelExpr<'db>, - ) -> Result<(), Diagnostic> { - if !lowered.ownership.is_borrowed() || ownership::is_copyable(self.backend.db, actual_ty) { - return Ok(()); - } - - Err(Diagnostic::error( - format!( - "non-copy value `{}` cannot be used by value yet", - actual_ty.display(self.backend.db) - ), - self.node_range(expr), - )) - } - - fn union_coercion_target( - &self, - actual_ty: Ty<'db>, - expected_ty: Ty<'db>, - source: ExprId, - ) -> Result, Diagnostic> { - let TyKind::Union(members) = expected_ty.kind(self.backend.db) else { - return Ok(None); - }; - let Some(arm_index) = members.iter().position(|member| *member == actual_ty) else { - return Ok(None); - }; - let union_abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - expected_ty, - "Wasm backend does not support this union value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(source)))?; - let layout = union_abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: union value is missing aggregate layout metadata", - self.node_range(source), - ) - })?; - let AggregateKind::Enum(enum_layout) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: union value is missing enum-style aggregate metadata", - self.node_range(source), - )); - }; - let variant = enum_layout.variants.get(arm_index).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: union arm index was out of range during backend lowering", - self.node_range(source), - ) - })?; - Ok(Some((union_abi, variant))) - } - - fn unit_union_expr(source: ExprId, abi: AbiTy, variant: VariantLayout) -> KernelExpr<'db> { - let kind = KernelExprKind::Union { variant, value: None }; - let ownership = Self::expr_ownership(&kind, &abi); - KernelExpr { source, abi, ownership, kind } - } - - fn expr_ownership(kind: &KernelExprKind<'db>, abi: &AbiTy) -> ValueOwnership { - if !abi.contains_heap_refs() { - return ValueOwnership::None; - } - - match kind { - KernelExprKind::Local(_) - | KernelExprKind::Capture(_) - | KernelExprKind::Field { .. } => ValueOwnership::Borrowed, - KernelExprKind::Block { tail, .. } => { - tail.as_ref().map_or(ValueOwnership::None, |tail| tail.ownership) - } - KernelExprKind::Clone { .. } - | KernelExprKind::FunctionValue { .. } - | KernelExprKind::ClosureValue { .. } - | KernelExprKind::String(_) - | KernelExprKind::StringFromBytes { .. } - | KernelExprKind::MemoryRead { .. } - | KernelExprKind::Array { .. } - | KernelExprKind::ArrayRepeat { .. } - | KernelExprKind::Call { .. } - | KernelExprKind::IndirectCall { .. } - | KernelExprKind::If { .. } - | KernelExprKind::Match { .. } - | KernelExprKind::Tuple { .. } - | KernelExprKind::Struct { .. } - | KernelExprKind::Union { .. } - | KernelExprKind::VariantValue { .. } - | KernelExprKind::VariantCall { .. } => ValueOwnership::Owned, - KernelExprKind::Bool(_) - | KernelExprKind::Int(_) - | KernelExprKind::Float(_) - | KernelExprKind::Char(_) - | KernelExprKind::Unit - | KernelExprKind::StackAddr { .. } - | KernelExprKind::AddrOffset { .. } - | KernelExprKind::MemoryWrite { .. } - | KernelExprKind::PointerAdd { .. } - | KernelExprKind::Binary { .. } - | KernelExprKind::Prefix { .. } - | KernelExprKind::Loop { .. } - | KernelExprKind::Break - | KernelExprKind::Continue => ValueOwnership::None, - } - } - - fn lower_pattern( - &self, - pattern: PatId, - ty: Ty<'db>, - abi: &AbiTy, - anchor: ExprId, - ) -> Result { - if let Some(pattern) = self.lower_selected_union_pattern(pattern, ty, abi, anchor)? { - return Ok(pattern); - } - - self.lower_pattern_inner(pattern, ty, abi, anchor) - } - - fn lower_selected_union_pattern( - &self, - pattern: PatId, - ty: Ty<'db>, - abi: &AbiTy, - anchor: ExprId, - ) -> Result, Diagnostic> { - let TyKind::Union(members) = ty.kind(self.backend.db) else { - return Ok(None); - }; - let Some(selected_member) = self.inference.selected_union_member(pattern) else { - return Ok(None); - }; - - let selected_member = self.backend.specialize_ty(&self.owner_instance, selected_member); - let layout = self.pattern_aggregate_layout(ty, abi, anchor)?; - let AggregateKind::Enum(enum_layout) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: union pattern is missing enum-style layout metadata", - self.node_range(anchor), - )); - }; - let arm_index = - members.iter().position(|member| *member == selected_member).ok_or_else(|| { - Diagnostic::error( - "internal error: selected union member was not present in the scrutinee type", - self.node_range(anchor), - ) - })?; - let variant = enum_layout.variants.get(arm_index).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: selected union arm was out of range during pattern lowering", - self.node_range(anchor), - ) - })?; - let payload_abi = - variant.fields.first().map_or(AbiTy::Scalar(BackendTy::Unit), |field| field.ty.clone()); - let nested = self.lower_pattern_inner(pattern, selected_member, &payload_abi, anchor)?; - let fields = variant - .fields - .first() - .cloned() - .map(|field| vec![BackendPatternField { field, pattern: nested }]) - .unwrap_or_default(); - Ok(Some(BackendPattern::Variant { variant, fields })) - } - - fn lower_pattern_inner( - &self, - pattern: PatId, - ty: Ty<'db>, - abi: &AbiTy, - anchor: ExprId, - ) -> Result { - let nodes = self.function.node_store(); - if pattern == PatId::ZERO { - return Ok(BackendPattern::Wildcard); - } - - match nodes.node_kind(pattern) { - NodeKind::PatBinding => { - let (name, _) = - nodes.pat_binding(nodes.as_pat_binding(pattern).expect("PatBinding mismatch")); - Ok(BackendPattern::Binding(name)) - } - NodeKind::PatWildcard => Ok(BackendPattern::Wildcard), - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - self.lower_pattern(inner, ty, abi, anchor) - } - NodeKind::PatTrue => Ok(BackendPattern::Literal(BackendPatternLiteral::Bool(true))), - NodeKind::PatFalse => Ok(BackendPattern::Literal(BackendPatternLiteral::Bool(false))), - NodeKind::PatInt => { - let literal = nodes.pat_int(nodes.as_pat_int(pattern).expect("PatInt mismatch")); - Ok(BackendPattern::Literal(BackendPatternLiteral::Int( - parse_int_literal(literal, self.backend.db) - .map_err(|message| Diagnostic::error(message, self.node_range(anchor)))?, - ))) - } - NodeKind::PatString => { - let literal = - nodes.pat_string(nodes.as_pat_string(pattern).expect("PatString mismatch")); - let offset = self.static_data.offset(literal).ok_or_else(|| { - Diagnostic::error( - "internal error: missing static string data during pattern lowering", - self.node_range(anchor), - ) - })?; - Ok(BackendPattern::Literal(BackendPatternLiteral::String(offset))) - } - NodeKind::PatChar => { - let literal = nodes.pat_char(nodes.as_pat_char(pattern).expect("PatChar mismatch")); - Ok(BackendPattern::Literal(BackendPatternLiteral::Char( - decode_char_literal(literal, self.backend.db) - .map_err(|message| Diagnostic::error(message, self.node_range(anchor)))?, - ))) - } - NodeKind::PatFloat => Err(Diagnostic::error( - "Wasm backend does not support float patterns", - self.node_range(anchor), - )), - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.lower_pattern(inner, ty, abi, anchor) - } - NodeKind::PatTuple => { - let layout = self.pattern_aggregate_layout(ty, abi, anchor)?; - let fields = layout.fields().ok_or_else(|| { - Diagnostic::error( - "internal error: tuple pattern is missing field layout metadata", - self.node_range(anchor), - ) - })?; - let item_ids = nodes - .pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple mismatch")) - .iter() - .collect::>(); - let item_tys = match ty.kind(self.backend.db) { - TyKind::Tuple(items) => items.clone(), - _ => vec![Ty::new(self.backend.db, TyKind::Unknown); item_ids.len()], - }; - let items = fields - .iter() - .zip(item_ids.iter()) - .enumerate() - .map(|(index, (field, &item))| { - let item_ty = item_tys - .get(index) - .copied() - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - Ok(BackendPatternField { - field: field.clone(), - pattern: self.lower_pattern(item, item_ty, &field.ty, anchor)?, - }) - }) - .collect::, Diagnostic>>()?; - Ok(BackendPattern::Tuple(items)) - } - NodeKind::PatStruct => { - let layout = self.pattern_aggregate_layout(ty, abi, anchor)?; - let field_tys = match ty.kind(self.backend.db) { - TyKind::Struct(struct_ty) => struct_fields(self.backend.db, *struct_ty) - .iter() - .copied() - .collect::>(), - TyKind::Record(fields) => fields.iter().copied().collect::>(), - _ => FxHashMap::default(), - }; - let (_, fields) = - nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct mismatch")); - let items = fields - .iter() - .map(|field| { - let (name, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField mismatch"), - ); - let field_sym = nodes.name(name); - let field_layout = layout - .field_named(symbol_bits(field_sym)) - .cloned() - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing field layout during pattern lowering", - self.node_range(name.into()), - ) - })?; - let field_ty = field_tys - .get(&field_sym) - .copied() - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - let nested = if pat != PatId::ZERO { - self.lower_pattern(pat, field_ty, &field_layout.ty, name.into())? - } else { - BackendPattern::Binding(name) - }; - Ok(BackendPatternField { field: field_layout, pattern: nested }) - }) - .collect::, Diagnostic>>()?; - Ok(BackendPattern::Struct(items)) - } - NodeKind::PatVariant => { - let layout = self.pattern_aggregate_layout(ty, abi, anchor)?; - let AggregateKind::Enum(enum_layout) = &layout.kind else { - return Err(Diagnostic::error( - "internal error: enum pattern is missing enum layout metadata", - self.node_range(anchor), - )); - }; - let (path, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant mismatch")); - let field_id = nodes.as_field(path).ok_or_else(|| { - Diagnostic::error( - "internal error: variant pattern is missing its path", - self.node_range(anchor), - ) - })?; - let (_, variant_name_expr) = nodes.field(field_id); - let variant_name = - nodes.as_name(variant_name_expr).expect("variant name should lower to Name"); - let variant_sym = nodes.name(variant_name); - let variant = enum_layout - .variants - .iter() - .find(|variant| variant.name_bits == symbol_bits(variant_sym)) - .cloned() - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing enum variant layout during pattern lowering", - self.node_range(variant_name_expr), - ) - })?; - let payload_tys = match ty.kind(self.backend.db) { - TyKind::Enum(enum_ty) => enum_variants(self.backend.db, *enum_ty) - .iter() - .find(|(name, _)| *name == variant_sym) - .map(|(_, payload)| payload.clone()) - .unwrap_or_default(), - _ => Vec::new(), - }; - let arg_ids = args.iter().collect::>(); - let fields = variant - .fields - .iter() - .zip(arg_ids.iter()) - .enumerate() - .map(|(index, (field, &arg))| { - let field_ty = payload_tys - .get(index) - .copied() - .unwrap_or_else(|| Ty::new(self.backend.db, TyKind::Unknown)); - Ok(BackendPatternField { - field: field.clone(), - pattern: self.lower_pattern( - arg, - field_ty, - &field.ty, - variant_name_expr, - )?, - }) - }) - .collect::, Diagnostic>>()?; - Ok(BackendPattern::Variant { variant, fields }) - } - kind => Err(Diagnostic::error( - format!("internal error: unsupported pattern `{kind:?}` reached backend lowering"), - self.node_range(anchor), - )), - } - } - - fn pattern_aggregate_layout( - &self, - ty: Ty<'db>, - abi: &AbiTy, - source: ExprId, - ) -> Result { - if let Some(layout) = abi.aggregate() { - return Ok(layout.clone()); - } - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this nominal value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(source))) - } - - fn lower_name_expr(&self, expr: ExprId) -> Result, Diagnostic> { - match self.resolve_name(expr)? { - BindingId::Local(name) | BindingId::Param(name) => Ok(self - .capture_fields - .get(&name) - .cloned() - .map_or(KernelExprKind::Local(name), KernelExprKind::Capture)), - BindingId::Function(target) => { - let instance = self.backend.instance_for_function_value( - &self.owner_instance, - self.inference, - target, - expr, - self.node_range(expr), - )?; - Ok(KernelExprKind::FunctionValue { - target: FunctionValueTarget::Function(instance), - }) - } - BindingId::RuntimeFunction(_) => Err(Diagnostic::error( - "internal error: runtime functions are not supported as first-class values", - self.node_range(expr), - )), - BindingId::CompilerIntrinsic(intrinsic) => Err(Diagnostic::error( - format!( - "internal error: Backend intrinsic `{}` is not supported as a first-class \ - value", - intrinsic.source_name() - ), - self.node_range(expr), - )), - BindingId::Struct(_) - | BindingId::Enum(_) - | BindingId::EnumVariant(_) - | BindingId::BuiltinType(_) => Err(Diagnostic::error( - "internal error: type values are not supported in backend lowering", - self.node_range(expr), - )), - } - } - - fn lower_compiler_intrinsic_call( - &mut self, - expr: ExprId, - abi: &AbiTy, - ) -> Result>, Diagnostic> { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Call { - return Ok(None); - } - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - let Some(name_id) = nodes.as_name(callee) else { - return Ok(None); - }; - let symbol = nodes.name(name_id); - let Some(BindingId::CompilerIntrinsic(intrinsic)) = self.resolve_name(callee).ok() else { - return Ok(None); - }; - - if intrinsic == CompilerIntrinsic::Comptime { - self.lower_comptime_call(expr, abi).map(Some) - } else if intrinsic.is_reflection() { - if !self.backend.is_stage_mode() { - return Err(Diagnostic::error( - format!( - "`{}` is only supported during `comptime` execution", - symbol.text(self.backend.db) - ), - self.node_range(expr), - )); - } - self.lower_reflection_call(expr, intrinsic, args.iter().collect::>().as_slice()) - .map(Some) - } else { - if self.backend.is_stage_mode() { - return Err(Diagnostic::error( - format!( - "`{}` is not supported during `comptime` execution", - symbol.text(self.backend.db) - ), - self.node_range(expr), - )); - } - self.lower_unsafe_intrinsic_call( - expr, - abi, - intrinsic, - args.iter().collect::>().as_slice(), - ) - .map(Some) - } - } - - fn lower_comptime_call( - &mut self, - expr: ExprId, - _abi: &AbiTy, - ) -> Result, Diagnostic> { - let target = resolve_comptime_target( - self.backend, - self.location, - self.function, - expr, - self.node_range(expr), - )? - .ok_or_else(|| { - Diagnostic::error( - "internal error: malformed `comptime` call reached backend lowering", - self.node_range(expr), - ) - })?; - let value = self - .backend - .comptime_evaluator - .eval_comptime_function(self.backend.db, target) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let ty = self.expr_ty(expr)?; - let static_value = self.static_data.comptime_value(self.location, expr); - let value = comptime_value_from_abi(self.backend.db, ty, value, self.node_range(expr))?; - Ok(self.lower_comptime_value_expr(expr, ty, value, static_value)?.kind) - } - - fn lower_comptime_value_expr( - &mut self, - expr: ExprId, - ty: Ty<'db>, - value: ComptimeValue, - static_value: Option<&ComptimeStaticValue>, - ) -> Result, Diagnostic> { - let abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - ty, - "Wasm backend does not support this comptime result type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let kind = match (ty.kind(self.backend.db), value) { - (TyKind::Tuple(items), ComptimeValue::Unit) if items.is_empty() => KernelExprKind::Unit, - (TyKind::Int, ComptimeValue::I32(value)) => KernelExprKind::Int(i64::from(value)), - (TyKind::ExactInt(_), ComptimeValue::I32(value)) => { - KernelExprKind::Int(i64::from(value)) - } - (TyKind::Bool, ComptimeValue::Bool(value)) => KernelExprKind::Bool(value), - (TyKind::Float, ComptimeValue::F64(value)) => KernelExprKind::Float(value), - (TyKind::Char, ComptimeValue::Char(value)) => KernelExprKind::Char(value), - (TyKind::String, ComptimeValue::String(_value)) => { - let Some(ComptimeStaticValue::String(offset)) = static_value else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime string result", - self.node_range(expr), - )); - }; - KernelExprKind::String(*offset) - } - (TyKind::Array(item_ty), ComptimeValue::Array(items)) => { - let layout = crate::capability::supported_array_runtime_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this comptime result type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let Some(ComptimeStaticValue::Array(static_items)) = static_value else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime array result", - self.node_range(expr), - )); - }; - if static_items.len() != items.len() { - return Err(Diagnostic::error( - "internal error: comptime array static data length did not match", - self.node_range(expr), - )); - } - let items = items - .into_iter() - .zip(static_items.iter()) - .map(|(item, static_item)| { - self.lower_comptime_value_expr(expr, *item_ty, item, Some(static_item)) - }) - .collect::, _>>()?; - KernelExprKind::Array { layout, items } - } - (TyKind::Tuple(item_tys), ComptimeValue::Tuple(items)) => { - let layout = abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: missing tuple layout for comptime result", - self.node_range(expr), - ) - })?; - let fields = layout.fields().unwrap_or(&[]); - let Some(ComptimeStaticValue::Tuple(static_items)) = static_value else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime tuple result", - self.node_range(expr), - )); - }; - if static_items.len() != items.len() { - return Err(Diagnostic::error( - "internal error: comptime tuple static data length did not match", - self.node_range(expr), - )); - } - let fields = fields - .iter() - .zip(item_tys.iter().zip(items).zip(static_items.iter())) - .map(|(field, ((&item_ty, value), static_item))| { - self.lower_comptime_value_expr(expr, item_ty, value, Some(static_item)) - .map(|value| KernelFieldValue { field: field.clone(), value }) - }) - .collect::, _>>()?; - KernelExprKind::Tuple { fields } - } - (TyKind::Record(field_tys), ComptimeValue::Record(values)) => { - let layout = abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: missing record layout for comptime result", - self.node_range(expr), - ) - })?; - let fields = layout.fields().unwrap_or(&[]); - let mut field_tys = field_tys.clone(); - field_tys.sort_by_key(|(name, _)| name.text(self.backend.db).to_owned()); - let Some(ComptimeStaticValue::Record(static_values)) = static_value else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime record result", - self.node_range(expr), - )); - }; - if static_values.len() != values.len() { - return Err(Diagnostic::error( - "internal error: comptime record static data length did not match", - self.node_range(expr), - )); - } - let fields = fields - .iter() - .zip(field_tys.iter().zip(values).zip(static_values.iter())) - .map(|(field, (((_, field_ty), value), static_value))| { - self.lower_comptime_value_expr(expr, *field_ty, value, Some(static_value)) - .map(|value| KernelFieldValue { field: field.clone(), value }) - }) - .collect::, _>>()?; - KernelExprKind::Struct { fields } - } - (TyKind::Struct(struct_ty), ComptimeValue::Struct(values)) => { - let layout = - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this comptime result type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let fields = layout.fields().unwrap_or(&[]); - let field_tys = struct_fields(self.backend.db, *struct_ty); - let Some(ComptimeStaticValue::Struct(static_values)) = static_value else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime struct result", - self.node_range(expr), - )); - }; - if static_values.len() != values.len() { - return Err(Diagnostic::error( - "internal error: comptime struct static data length did not match", - self.node_range(expr), - )); - } - let fields = fields - .iter() - .zip(field_tys.iter().zip(values).zip(static_values.iter())) - .map(|(field, (((_, field_ty), value), static_value))| { - self.lower_comptime_value_expr(expr, *field_ty, value, Some(static_value)) - .map(|value| KernelFieldValue { field: field.clone(), value }) - }) - .collect::, _>>()?; - KernelExprKind::Struct { fields } - } - (TyKind::Enum(enum_ty), ComptimeValue::Enum { variant_index, fields }) => { - let layout = - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this comptime result type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr)))?; - let AggregateKind::Enum(enum_layout) = layout.kind else { - return Err(Diagnostic::error( - "internal error: expected enum layout for comptime result", - self.node_range(expr), - )); - }; - let variant = - enum_layout.variants.get(variant_index).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: comptime enum variant index was out of range", - self.node_range(expr), - ) - })?; - let (_, field_tys) = enum_variants(self.backend.db, *enum_ty) - .get(variant_index) - .ok_or_else(|| { - Diagnostic::error( - "internal error: comptime enum variant metadata was missing", - self.node_range(expr), - ) - })?; - if fields.is_empty() { - KernelExprKind::VariantValue { variant } - } else { - let Some(ComptimeStaticValue::Enum { - variant_index: static_variant_index, - fields: static_fields, - }) = static_value - else { - return Err(Diagnostic::error( - "internal error: missing static data for comptime enum result", - self.node_range(expr), - )); - }; - if *static_variant_index != variant_index { - return Err(Diagnostic::error( - "internal error: comptime enum static data did not match the variant", - self.node_range(expr), - )); - } - if static_fields.len() != fields.len() { - return Err(Diagnostic::error( - "internal error: comptime enum static data length did not match", - self.node_range(expr), - )); - } - let args = field_tys - .iter() - .copied() - .zip(fields.into_iter().zip(static_fields.iter())) - .map(|(field_ty, (value, static_value))| { - self.lower_comptime_value_expr( - expr, - field_ty, - value, - Some(static_value), - ) - }) - .collect::, _>>()?; - KernelExprKind::VariantCall { variant, args } - } - } - _ => { - return Err(Diagnostic::error( - format!( - "internal error: comptime result did not match `{}`", - ty.display(self.backend.db) - ), - self.node_range(expr), - )); - } - }; - let ownership = Self::expr_ownership(&kind, &abi); - Ok(KernelExpr { source: expr, abi, ownership, kind }) - } - - fn lower_reflection_call( - &mut self, - expr: ExprId, - intrinsic: CompilerIntrinsic, - args: &[ExprId], - ) -> Result, Diagnostic> { - let stage_intrinsic = - StageIntrinsic::from_compiler_intrinsic(intrinsic).ok_or_else(|| { - Diagnostic::error( - "internal error: non-reflection intrinsic reached stage lowering", - self.node_range(expr), - ) - })?; - let mut lowered_args = Vec::with_capacity(args.len()); - match stage_intrinsic { - StageIntrinsic::TypeName - | StageIntrinsic::FieldCount - | StageIntrinsic::FieldName - | StageIntrinsic::VariantCount - | StageIntrinsic::VariantName => { - lowered_args.push(self.encoded_type_arg(args[0])?); - } - StageIntrinsic::FunctionParamCount - | StageIntrinsic::FunctionParamTypeName - | StageIntrinsic::FunctionReturnTypeName => { - lowered_args.push(self.encoded_function_arg(args[0])?); - } - } - if args.len() > 1 { - lowered_args.push(self.lower_expr(args[1])?); - } - Ok(KernelExprKind::Call { - target: BackendCallTarget { - callable: BackendCallable::StageIntrinsic(stage_intrinsic), - signature: stage_intrinsic_signature(stage_intrinsic), - }, - args: lowered_args, - }) - } - - fn lower_unsafe_intrinsic_call( - &mut self, - expr: ExprId, - abi: &AbiTy, - intrinsic: CompilerIntrinsic, - args: &[ExprId], - ) -> Result, Diagnostic> { - match intrinsic { - CompilerIntrinsic::StackAlloc => { - let temp = self.layout.temps.get(&expr).ok_or_else(|| { - Diagnostic::error( - "internal error: `stack_alloc` is missing planned frame storage", - self.node_range(expr), - ) - })?; - Ok(KernelExprKind::StackAddr { frame_slot: temp.frame_slot }) - } - CompilerIntrinsic::PtrRead => { - let addr = self.lower_expr(args[0])?; - let ty = self.expr_ty(args[0])?; - let TyKind::Pointer { pointee, .. } = ty.kind(self.backend.db) else { - return Err(Diagnostic::error( - "internal error: `ptr_read` did not infer a pointer type", - self.node_range(expr), - )); - }; - Ok(KernelExprKind::MemoryRead { - addr: Box::new(addr), - access: pointee_mem_access(self.backend.db, *pointee, abi, 0), - }) - } - CompilerIntrinsic::PtrWrite => { - let addr = self.lower_expr(args[0])?; - let value = self.lower_expr(args[1])?; - Ok(KernelExprKind::MemoryWrite { addr: Box::new(addr), value: Box::new(value) }) - } - CompilerIntrinsic::PtrAdd => { - let ptr = self.lower_expr(args[0])?; - let count = self.lower_expr(args[1])?; - let ty = self.expr_ty(args[0])?; - let TyKind::Pointer { pointee, .. } = ty.kind(self.backend.db) else { - return Err(Diagnostic::error( - "internal error: `ptr_add` did not infer a pointer type", - self.node_range(expr), - )); - }; - let stride = pointee_stride(self.backend.db, *pointee).ok_or_else(|| { - Diagnostic::error( - "Wasm backend does not support this `ptr_add` pointee type", - self.node_range(expr), - ) - })?; - Ok(KernelExprKind::PointerAdd { - ptr: Box::new(ptr), - count: Box::new(count), - stride, - }) - } - CompilerIntrinsic::StrBytes => { - let value = self.lower_expr(args[0])?; - self.lower_byte_view(expr, abi, &value, false) - } - CompilerIntrinsic::StrFromUtf8Unchecked => { - let ptr = self.lower_expr(args[0])?; - let len = self.lower_expr(args[1])?; - Ok(KernelExprKind::StringFromBytes { ptr: Box::new(ptr), len: Box::new(len) }) - } - CompilerIntrinsic::ArrayMutBytes => { - let value = self.lower_expr(args[0])?; - self.lower_byte_view(expr, abi, &value, true) - } - CompilerIntrinsic::Comptime - | CompilerIntrinsic::TypeName - | CompilerIntrinsic::FieldCount - | CompilerIntrinsic::FieldName - | CompilerIntrinsic::VariantCount - | CompilerIntrinsic::VariantName - | CompilerIntrinsic::FunctionParamCount - | CompilerIntrinsic::FunctionParamTypeName - | CompilerIntrinsic::FunctionReturnTypeName => unreachable!("handled above"), - } - } - - fn lower_byte_view( - &mut self, - expr: ExprId, - abi: &AbiTy, - value: &KernelExpr<'db>, - mutable: bool, - ) -> Result, Diagnostic> { - let layout = abi.aggregate().ok_or_else(|| { - Diagnostic::error( - "internal error: byte view result is missing aggregate layout", - self.node_range(expr), - ) - })?; - let fields = layout.fields().ok_or_else(|| { - Diagnostic::error( - "internal error: byte view result is missing field metadata", - self.node_range(expr), - ) - })?; - let ptr_bits = symbol_bits("ptr".into_symbol(self.backend.db)); - let len_bits = symbol_bits("len".into_symbol(self.backend.db)); - let (ptr_offset, len_offset) = match value.abi { - AbiTy::Scalar(BackendTy::Ref(RefKind::String)) => (4, 0), - AbiTy::Scalar(BackendTy::Ref(RefKind::Array(_))) if mutable => { - let layout = self.array_layout_for_expr(value.source)?; - (layout.data_offset, ARRAY_LEN_OFFSET) - } - _ => { - return Err(Diagnostic::error( - "internal error: unexpected byte-view base type", - self.node_range(expr), - )); - } - }; - let values = fields - .iter() - .map(|field| { - let value = match field.name_bits { - Some(bits) if bits == ptr_bits => KernelExpr { - source: expr, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: KernelExprKind::AddrOffset { - base: Box::new(value.clone()), - offset: ptr_offset, - }, - }, - Some(bits) if bits == len_bits => KernelExpr { - source: expr, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: KernelExprKind::MemoryRead { - addr: Box::new(value.clone()), - access: Some(MemAccess::i32(len_offset, 2)), - }, - }, - _ => { - return Err(Diagnostic::error( - "internal error: unexpected byte-view field layout", - self.node_range(expr), - )); - } - }; - Ok(KernelFieldValue { field: field.clone(), value }) - }) - .collect::, _>>()?; - Ok(KernelExprKind::Struct { fields: values }) - } - - fn encoded_type_arg(&self, expr: ExprId) -> Result, Diagnostic> { - let ty = self.resolve_name_ty(expr).map_err(|_diagnostic| { - Diagnostic::error( - "internal error: reflection type argument did not resolve to a type", - self.node_range(expr), - ) - })?; - let bits = u32::try_from(ty.as_id().as_bits()).map_err(|_error| { - Diagnostic::error( - "internal error: reflection type id exceeded the supported range", - self.node_range(expr), - ) - })?; - Ok(KernelExpr { - source: expr, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: KernelExprKind::Int(i64::from(bits)), - }) - } - - fn encoded_function_arg(&self, expr: ExprId) -> Result, Diagnostic> { - let BindingId::Function(function) = self.resolve_name(expr)? else { - return Err(Diagnostic::error( - "internal error: reflection function argument did not resolve to a function", - self.node_range(expr), - )); - }; - let bits = u32::try_from(function.as_id().as_bits()).map_err(|_error| { - Diagnostic::error( - "internal error: reflection function id exceeded the supported range", - self.node_range(expr), - ) - })?; - Ok(KernelExpr { - source: expr, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: KernelExprKind::Int(i64::from(bits)), - }) - } - - fn lower_closure_expr(&mut self, expr: ExprId) -> Result, Diagnostic> { - let closure = ClosureInstanceKey { - owner: self.location, - type_args: self.owner_instance.type_args.clone(), - closure: expr, - }; - let info = self.backend.closure_info(&closure, self.function, self.inference)?; - let fields = info - .captures - .into_iter() - .map(|capture| { - self.value_expr_for_binding(capture.binding, expr) - .map(|value| KernelFieldValue { field: capture.field, value }) - }) - .collect::, _>>()?; - Ok(KernelExprKind::ClosureValue { - target: closure, - env: KernelClosureEnvInit { layout: info.env_layout, fields }, - }) - } - - fn value_expr_for_binding( - &self, - binding: NameId, - source: ExprId, - ) -> Result, Diagnostic> { - let ty = self - .inference - .type_of_node(binding.into()) - .ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred binding type during closure lowering", - self.node_range(source), - ) - }) - .map(|ty| self.backend.specialize_ty(&self.owner_instance, ty))?; - let abi = crate::capability::supported_value_abi_or_message( - self.backend.db, - ty, - "Wasm backend does not support capturing this value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(source)))?; - let kind = self - .capture_fields - .get(&binding) - .cloned() - .map_or(KernelExprKind::Local(binding), KernelExprKind::Capture); - let ownership = Self::expr_ownership(&kind, &abi); - Ok(KernelExpr { source, abi, ownership, kind }) - } - - fn is_direct_call(&self, callee: ExprId) -> Result { - if self.function.node_store().node_kind(callee) == NodeKind::Name { - return Ok(matches!( - self.resolve_name(callee), - Ok(BindingId::RuntimeFunction(_) | BindingId::Function(_)) - )); - } - - Ok(self.resolve_method_call(callee)?.is_some()) - } - - fn call_signature(&self, callee: ExprId) -> Result { - let Some(ty) = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, callee) - else { - return Err(Diagnostic::error( - "internal error: missing inferred callee type during indirect call lowering", - self.node_range(callee), - )); - }; - crate::capability::supported_function_signature_or_message( - self.backend.db, - ty, - "Wasm backend does not support this function value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(callee))) - } - - fn lower_call_args( - &mut self, - args: &[ExprId], - param_tys: &[Ty<'db>], - target_function: Option>, - ) -> Result>, Diagnostic> { - args.iter() - .enumerate() - .map(|(index, &arg)| { - if target_function.is_some_and(|function| { - self.function_param_is_mutable(function, index) - && self - .param_ty_needs_indirect_mutable_passing(param_tys.get(index).copied()) - .unwrap_or(false) - }) { - self.lower_place_address(arg) - } else { - self.lower_expr_as(arg, param_tys.get(index).copied()) - } - }) - .collect() - } - - fn param_ty_needs_indirect_mutable_passing( - &self, - ty: Option>, - ) -> Result { - let Some(ty) = ty else { - return Ok(false); - }; - Ok(crate::capability::supported_value_abi_or_message( - self.backend.db, - ty, - "Wasm backend does not support this value type", - ) - .map_err(|message| Diagnostic::error(message, self.backend.function_range(self.location)))? - .is_aggregate()) - } - - fn call_param_tys(&self, callee: ExprId) -> Result>, Diagnostic> { - let Some(ty) = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, callee) - else { - return Err(Diagnostic::error( - "internal error: missing inferred callee type during call lowering", - self.node_range(callee), - )); - }; - - let TyKind::Function { inputs, .. } = ty.kind(self.backend.db) else { - return Err(Diagnostic::error( - "internal error: non-function callee reached call argument lowering", - self.node_range(callee), - )); - }; - - Ok(inputs.clone()) - } - - fn lower_call_target( - &self, - callee: ExprId, - call_expr: ExprId, - ) -> Result, Diagnostic> { - if let Some(method) = self.resolve_method_call(callee)? { - let nodes = self.function.node_store(); - let (_, args) = nodes.call(nodes.as_call(call_expr).expect("Call node mismatch")); - let mut call_args = Vec::with_capacity(args.len() + 1); - call_args.push(method.receiver); - call_args.extend(args.iter()); - let instance = self.backend.instance_for_call( - &self.owner_instance, - self.inference, - method.function, - call_expr, - call_args.as_slice(), - self.node_range(call_expr), - )?; - let hir_function = method.function.hir_function(self.backend.db); - let function = hir_function.function(self.backend.db); - let inference = method.function.infer(self.backend.db); - return Ok(BackendCallTarget { - callable: BackendCallable::Function(instance.clone()), - signature: self.backend.function_signature(&instance, function, inference)?, - }); - } - - match self.resolve_name(callee)? { - BindingId::RuntimeFunction(function) => Ok(BackendCallTarget { - callable: BackendCallable::Runtime(function), - signature: runtime_function_signature(function), - }), - BindingId::Function(target) => { - let nodes = self.function.node_store(); - let (_, args) = nodes.call(nodes.as_call(call_expr).expect("Call node mismatch")); - let args = args.iter().collect::>(); - let instance = self.backend.instance_for_call( - &self.owner_instance, - self.inference, - target, - call_expr, - args.as_slice(), - self.node_range(call_expr), - )?; - let hir_function = target.hir_function(self.backend.db); - let function = hir_function.function(self.backend.db); - let inference = target.infer(self.backend.db); - Ok(BackendCallTarget { - callable: BackendCallable::Function(instance.clone()), - signature: self.backend.function_signature(&instance, function, inference)?, - }) - } - _ => Err(Diagnostic::error( - "internal error: non-direct call reached backend lowering", - self.node_range(call_expr), - )), - } - } - - fn direct_call_target_function( - &self, - callee: ExprId, - ) -> Result>, Diagnostic> { - if let Some(method) = self.resolve_method_call(callee)? { - return Ok(Some(method.function)); - } - match self.resolve_name(callee)? { - BindingId::Function(target) => Ok(Some(target)), - BindingId::RuntimeFunction(_) => Ok(None), - _ => Err(Diagnostic::error( - "internal error: non-direct call reached target-function lookup", - self.node_range(callee), - )), - } - } - - fn resolve_method_call( - &self, - callee: ExprId, - ) -> Result>, Diagnostic> { - let nodes = self.function.node_store(); - let Some(field_id) = nodes.as_field(callee) else { - return Ok(None); - }; - let (receiver, field_name_expr) = nodes.field(field_id); - if receiver == ExprId::ZERO { - return Ok(None); - } - - let Some(field_name_id) = nodes.as_name(field_name_expr) else { - return Ok(None); - }; - let Some(receiver_ty) = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, receiver) - else { - return Err(Diagnostic::error( - "internal error: missing inferred receiver type during method call lowering", - self.node_range(callee), - )); - }; - - Ok(resolve_method_for_receiver(self.backend.db, receiver_ty, nodes.name(field_name_id)) - .map(|method| ResolvedMethodCall { receiver, function: method.function })) - } - - fn function_param_is_mutable(&self, function: FunctionLocation<'db>, index: usize) -> bool { - let hir_function = function.hir_function(self.backend.db); - function_param_binding_name(hir_function.function(self.backend.db), index) - .is_some_and(|name| hir_function.source_map(self.backend.db).is_mutable_binding(name)) - } - - fn should_lower_field_via_stable_address(&self, expr: ExprId) -> Result { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Name => match self.resolve_name(expr)? { - BindingId::Local(name) | BindingId::Param(name) => { - Ok(self.source_map.is_mutable_binding(name) - && self.expr_abi(expr)?.is_aggregate()) - } - _ => Ok(false), - }, - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base == ExprId::ZERO { - return Ok(false); - } - self.should_lower_field_via_stable_address(base) - } - _ => Ok(false), - } - } - - fn lower_place_address(&mut self, expr: ExprId) -> Result, Diagnostic> { - let abi = AbiTy::Scalar(match self.backend.memory_model_strategy().guest_word() { - target::GuestWord::I32 => BackendTy::Int, - target::GuestWord::I64 => BackendTy::I64, - }); - let kind = match self.function.node_store().node_kind(expr) { - NodeKind::Name => match self.resolve_name(expr)? { - BindingId::Local(name) | BindingId::Param(name) => { - let slot = self.layout.slots.get(&name).ok_or_else(|| { - Diagnostic::error( - "internal error: missing slot for addressable place", - self.node_range(expr), - ) - })?; - if slot.local_index.is_some() { - KernelExprKind::Local(name) - } else if let Some(frame_slot) = slot.frame_slot { - KernelExprKind::StackAddr { frame_slot } - } else { - return Err(Diagnostic::error( - "internal error: addressable place is missing storage", - self.node_range(expr), - )); - } - } - _ => { - return Err(Diagnostic::error( - "internal error: invalid place reached address lowering", - self.node_range(expr), - )); - } - }, - NodeKind::Field => { - let (base, field) = self.project_field(expr)?; - let base = self.lower_place_address(base)?; - KernelExprKind::AddrOffset { base: Box::new(base), offset: field.offset } - } - _ => { - return Err(Diagnostic::error( - "internal error: non-place reached address lowering", - self.node_range(expr), - )); - } - }; - Ok(KernelExpr { source: expr, abi, ownership: ValueOwnership::None, kind }) - } - - fn lower_binary_op(&self, op: ExprId) -> Result { - let nodes = self.function.node_store(); - let symbol = nodes.name(nodes.as_name(op).expect("binary op should be Name")); - match symbol.text(self.backend.db) { - "+" => Ok(BackendBinaryOp::Add), - "-" => Ok(BackendBinaryOp::Sub), - "*" => Ok(BackendBinaryOp::Mul), - "/" => Ok(BackendBinaryOp::Div), - "%" => Ok(BackendBinaryOp::Rem), - "<" => Ok(BackendBinaryOp::Lt), - ">" => Ok(BackendBinaryOp::Gt), - "<=" => Ok(BackendBinaryOp::Le), - ">=" => Ok(BackendBinaryOp::Ge), - "==" => Ok(BackendBinaryOp::Eq), - "!=" => Ok(BackendBinaryOp::Ne), - "&&" => Ok(BackendBinaryOp::And), - "||" => Ok(BackendBinaryOp::Or), - other => Err(Diagnostic::error( - format!("internal error: unsupported binary operator `{other}`"), - self.node_range(op), - )), - } - } - - fn lower_prefix_op(&self, op: ExprId) -> Result { - let nodes = self.function.node_store(); - let symbol = nodes.name(nodes.as_name(op).expect("prefix op should be Name")); - match symbol.text(self.backend.db) { - "!" => Ok(BackendPrefixOp::Not), - "-" => Ok(BackendPrefixOp::Neg), - other => Err(Diagnostic::error( - format!("internal error: unsupported prefix operator `{other}`"), - self.node_range(op), - )), - } - } - - fn resolve_name(&self, expr: ExprId) -> Result, Diagnostic> { - let nodes = self.function.node_store(); - let Some(name) = nodes.as_name(expr) else { - return Err(Diagnostic::error( - "internal error: expected name expression during backend lowering", - self.node_range(expr), - )); - }; - let symbol = nodes.name(name); - let mut resolver = self.resolver.clone(); - let guard = resolver.scopes_for_node(expr); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - resolution.ok_or_else(|| { - self.backend.diagnostic_at_function( - self.location, - "internal error: unresolved name during backend lowering", - self.node_range(expr), - ) - }) - } - - fn resolve_name_ty(&self, expr: ExprId) -> Result, Diagnostic> { - let nodes = self.function.node_store(); - let Some(name) = nodes.as_name(expr) else { - return Err(Diagnostic::error( - "internal error: expected name expression during backend lowering", - self.node_range(expr), - )); - }; - let symbol = nodes.name(name); - let mut resolver = self.resolver.clone(); - let guard = resolver.scopes_for_node(expr); - let ty = resolver - .resolve_type_binding(symbol) - .and_then(|binding| resolver.ty_for_binding(binding)); - resolver.reset(guard); - ty.ok_or_else(|| { - Diagnostic::error( - "internal error: unresolved type name during backend lowering", - self.node_range(expr), - ) - }) - } - - fn expr_abi(&self, expr: ExprId) -> Result { - let ty = self.lowerable_expr_ty(expr)?; - crate::capability::supported_value_abi_or_message( - self.backend.db, - ty, - "Wasm backend does not support this value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr))) - } - - fn expr_ty(&self, expr: ExprId) -> Result, Diagnostic> { - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, expr).ok_or_else( - || { - Diagnostic::error( - "internal error: missing inferred expression type during backend lowering", - self.node_range(expr), - ) - }, - ) - } - - fn lowerable_expr_ty(&self, expr: ExprId) -> Result, Diagnostic> { - let ty = self.expr_ty(expr)?; - Ok(self.concrete_expr_member_ty(expr, ty).unwrap_or(ty)) - } - - fn concrete_expr_member_ty(&self, expr: ExprId, ty: Ty<'db>) -> Option> { - let TyKind::Union(members) = ty.kind(self.backend.db) else { - return None; - }; - - let compatible = members - .iter() - .copied() - .filter(|member| self.expr_matches_ty(expr, *member)) - .collect::>(); - match compatible.as_slice() { - [selected] => Some(*selected), - _ => None, - } - } - - fn expr_matches_ty(&self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::True | NodeKind::False => matches!(ty.kind(self.backend.db), TyKind::Bool), - NodeKind::Int => matches!(ty.kind(self.backend.db), TyKind::Int | TyKind::ExactInt(_)), - NodeKind::Float => matches!(ty.kind(self.backend.db), TyKind::Float), - NodeKind::String => matches!(ty.kind(self.backend.db), TyKind::String), - NodeKind::Char => matches!(ty.kind(self.backend.db), TyKind::Char), - NodeKind::Tuple => { - let items = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - match ty.kind(self.backend.db) { - TyKind::Tuple(member_items) if member_items.len() == items.len() => items - .iter() - .zip(member_items.iter()) - .all(|(item, member_item)| self.expr_matches_ty(item, *member_item)), - _ => false, - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let struct_name = items.iter().next().unwrap_or(ExprId::ZERO); - if struct_name == ExprId::ZERO { - return matches!(ty.kind(self.backend.db), TyKind::Record(_)); - } - - matches!(self.resolve_name_ty(struct_name), Ok(struct_ty) if struct_ty == ty) - } - NodeKind::Field => self.expr_is_enum_variant(expr, ty), - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.expr_is_enum_variant(callee, ty) - && matches!(ty.kind(self.backend.db), TyKind::Enum(enum_ty) - if enum_variants(self.backend.db, *enum_ty) - .iter() - .find(|(name, payload)| { - let (_, variant_name_expr) = - nodes.field(nodes.as_field(callee).expect("variant call")); - let variant_name = nodes - .as_name(variant_name_expr) - .expect("variant name should lower to Name"); - *name == nodes.name(variant_name) && payload.len() == args.len() - }) - .is_some()) - } - _ => false, - } - } - - fn expr_is_enum_variant(&self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - let TyKind::Enum(enum_ty) = ty.kind(self.backend.db) else { - return false; - }; - if nodes.node_kind(expr) != NodeKind::Field { - return false; - } - - let (base, field_name_expr) = - nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - let variant_name = - nodes.as_name(field_name_expr).expect("variant name should lower to Name"); - let variant_sym = nodes.name(variant_name); - if !enum_variants(self.backend.db, *enum_ty).iter().any(|(name, _)| *name == variant_sym) { - return false; - } - if base == ExprId::ZERO { - return true; - } - - matches!(self.resolve_name_ty(base), Ok(base_ty) if base_ty == ty) - } - - fn project_field(&self, expr: ExprId) -> Result<(ExprId, FieldLayout), Diagnostic> { - let nodes = self.function.node_store(); - let (base, field_name_expr) = - nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base == ExprId::ZERO { - return Err(Diagnostic::error( - "internal error: bare enum variant reached field lowering", - self.node_range(expr), - )); - } - - let name = nodes.name(nodes.as_name(field_name_expr).expect("field name should be Name")); - let base_abi = self.expr_abi(base)?; - let layout = if let Some(layout) = base_abi.aggregate() { - layout.clone() - } else { - self.nominal_payload_layout(base)? - }; - let field = layout.field_named(symbol_bits(name)).ok_or_else(|| { - Diagnostic::error( - "internal error: missing field layout metadata during backend lowering", - self.node_range(expr), - ) - })?; - Ok((base, field.clone())) - } - - fn enum_variant_layout(&self, expr: ExprId) -> Result { - let nodes = self.function.node_store(); - let (_, field_name_expr) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - let name = nodes.name(nodes.as_name(field_name_expr).expect("field name should be Name")); - let abi = self.expr_abi(expr)?; - let layout = if let Some(layout) = abi.aggregate() { - layout.clone() - } else { - self.nominal_payload_layout(expr)? - }; - layout.variant(symbol_bits(name)).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: missing enum variant layout metadata during backend lowering", - self.node_range(expr), - ) - }) - } - - fn enum_variant_layout_for_call( - &self, - call_expr: ExprId, - callee: ExprId, - ) -> Result { - let nodes = self.function.node_store(); - let (_, field_name_expr) = - nodes.field(nodes.as_field(callee).expect("variant callee should be Field")); - let name = nodes.name(nodes.as_name(field_name_expr).expect("field name should be Name")); - let abi = self.expr_abi(call_expr)?; - let layout = if let Some(layout) = abi.aggregate() { - layout.clone() - } else { - self.nominal_payload_layout(call_expr)? - }; - layout.variant(symbol_bits(name)).cloned().ok_or_else(|| { - Diagnostic::error( - "internal error: missing enum variant layout metadata during backend lowering", - self.node_range(call_expr), - ) - }) - } - - fn is_variant_constructor_call(&self, expr: ExprId) -> Result { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Call { - return Ok(false); - } - let (callee, _) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - if nodes.node_kind(callee) != NodeKind::Field { - return Ok(false); - } - - let ty = self.lowerable_expr_ty(expr)?; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return Ok(false); - } - - let (base, _) = nodes.field(nodes.as_field(callee).expect("Field node mismatch")); - if base == ExprId::ZERO { - return Ok(true); - } - - Ok(self.resolve_name_ty(base).is_ok()) - } - - fn nominal_payload_layout(&self, expr: ExprId) -> Result { - let ty = self.lowerable_expr_ty(expr)?; - crate::capability::supported_internal_nominal_payload_layout_or_message( - self.backend.db, - ty, - "Wasm backend does not support this nominal value type", - ) - .map_err(|message| Diagnostic::error(message, self.node_range(expr))) - } - - fn is_enum_variant_ref(&self, expr: ExprId) -> Result { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Field { - return Ok(false); - } - - let ty = self.lowerable_expr_ty(expr)?; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return Ok(false); - } - - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base == ExprId::ZERO { - return Ok(true); - } - - Ok(self.resolve_name_ty(base).is_ok()) - } - - fn node_range(&self, expr: ExprId) -> mitki_errors::TextRange { - self.source_map - .try_node_syntax(expr) - .map_or_else(|| self.backend.function_range(self.location), |ptr| ptr.range) - } -} - -fn resolve_comptime_target<'db>( - backend: &Backend<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - expr: ExprId, - range: mitki_errors::TextRange, -) -> Result>, Diagnostic> { - let nodes = function.node_store(); - if nodes.node_kind(expr) != NodeKind::Call { - return Ok(None); - } - - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - let Some(name_id) = nodes.as_name(callee) else { - return Ok(None); - }; - let symbol = nodes.name(name_id); - let mut resolver = Resolver::new(backend.db, location); - let guard = resolver.scopes_for_node(callee); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - if !matches!(resolution, Some(BindingId::CompilerIntrinsic(CompilerIntrinsic::Comptime))) { - return Ok(None); - } - - if args.len() != 1 { - return Err(Diagnostic::error( - "internal error: malformed `comptime` call reached backend lowering", - range, - )); - } - let target_call = args.get(0).expect("single comptime arg"); - let Some(target_call_id) = nodes.as_call(target_call) else { - return Err(Diagnostic::error( - "internal error: comptime target is not a direct call", - range, - )); - }; - let (target_expr, _) = nodes.call(target_call_id); - let Some(target_name_id) = nodes.as_name(target_expr) else { - return Err(Diagnostic::error( - "internal error: comptime target is not a named function", - range, - )); - }; - let symbol = nodes.name(target_name_id); - let mut resolver = Resolver::new(backend.db, location); - let guard = resolver.scopes_for_node(target_expr); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - let Some(BindingId::Function(target)) = resolution else { - return Err(Diagnostic::error( - format!( - "internal error: comptime target `{}` did not resolve to a function", - symbol.text(backend.db) - ), - range, - )); - }; - - Ok(Some(target)) -} - -#[derive(Clone, Debug)] -enum ComptimeValue { - Unit, - I32(i32), - Bool(bool), - F64(f64), - Char(char), - String(String), - Array(Vec), - Tuple(Vec), - Record(Vec), - Struct(Vec), - Enum { variant_index: usize, fields: Vec }, -} - -#[derive(Clone, Debug)] -enum ComptimeStaticValue { - Unit, - I32, - Bool, - F64, - Char, - String(u32), - Array(Vec), - Tuple(Vec), - Record(Vec), - Struct(Vec), - Enum { variant_index: usize, fields: Vec }, -} - -fn comptime_value_from_abi<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - value: AbiValue, - range: mitki_errors::TextRange, -) -> Result { - match (ty.kind(db), value) { - (TyKind::Tuple(items), AbiValue::Immediate(AbiScalar::Unit)) if items.is_empty() => { - Ok(ComptimeValue::Unit) - } - (TyKind::Int, AbiValue::Immediate(AbiScalar::Int { value, .. })) => { - Ok(ComptimeValue::I32(i32::try_from(value).map_err(|_error| { - Diagnostic::error( - "internal error: comptime integer result exceeded i32 range", - range, - ) - })?)) - } - (TyKind::Bool, AbiValue::Immediate(AbiScalar::Bool(value))) => { - Ok(ComptimeValue::Bool(value)) - } - (TyKind::Float, AbiValue::Immediate(AbiScalar::Float { raw_bits, .. })) => { - Ok(ComptimeValue::F64(f64::from_bits(raw_bits))) - } - (TyKind::Char, AbiValue::Immediate(AbiScalar::Char { unicode_scalar })) => { - Ok(ComptimeValue::Char(char::from_u32(unicode_scalar).ok_or_else(|| { - Diagnostic::error("internal error: comptime char result was invalid", range) - })?)) - } - (TyKind::Enum(enum_ty), AbiValue::Immediate(AbiScalar::EnumTag { variant_index, .. })) => { - let variants = enum_variants(db, *enum_ty); - let variant_index = usize::try_from(variant_index).map_err(|_error| { - Diagnostic::error( - "internal error: comptime enum variant index exceeded usize", - range, - ) - })?; - let (_, fields) = variants.get(variant_index).ok_or_else(|| { - Diagnostic::error( - "internal error: comptime enum variant index was out of range", - range, - ) - })?; - if !fields.is_empty() { - return Err(Diagnostic::error( - "internal error: payload enum crossed the stage boundary as an immediate tag", - range, - )); - } - Ok(ComptimeValue::Enum { variant_index, fields: Vec::new() }) - } - (TyKind::String, AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::String { value, .. } = - comptime_node(&graph, &graph.root, "string", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime string result was not encoded as a string node", - range, - )); - }; - Ok(ComptimeValue::String(value.clone())) - } - (TyKind::Array(item_ty), AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::Array { elements, .. } = - comptime_node(&graph, &graph.root, "array", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime array result was not encoded as an array node", - range, - )); - }; - Ok(ComptimeValue::Array(comptime_array_items(db, *item_ty, &graph, elements, range)?)) - } - (TyKind::Tuple(item_tys), AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::Tuple { fields, .. } = - comptime_node(&graph, &graph.root, "tuple", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime tuple result was not encoded as a tuple node", - range, - )); - }; - Ok(ComptimeValue::Tuple(comptime_child_values(db, &graph, item_tys, fields, range)?)) - } - (TyKind::Record(field_tys), AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::Record { fields, .. } = - comptime_node(&graph, &graph.root, "record", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime record result was not encoded as a record node", - range, - )); - }; - let mut ordered = field_tys.clone(); - ordered.sort_by_key(|(name, _)| name.text(db).to_owned()); - let tys = ordered.iter().map(|(_, ty)| *ty).collect::>(); - Ok(ComptimeValue::Record(comptime_child_values(db, &graph, &tys, fields, range)?)) - } - (TyKind::Struct(struct_ty), AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::Struct { fields, .. } = - comptime_node(&graph, &graph.root, "struct", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime struct result was not encoded as a struct node", - range, - )); - }; - let tys = struct_fields(db, *struct_ty).iter().map(|(_, ty)| *ty).collect::>(); - Ok(ComptimeValue::Struct(comptime_child_values(db, &graph, &tys, fields, range)?)) - } - (TyKind::Enum(enum_ty), AbiValue::Canonical { graph, .. }) => { - let CanonicalNode::Enum { variant_index, fields, .. } = - comptime_node(&graph, &graph.root, "enum", range)? - else { - return Err(Diagnostic::error( - "internal error: comptime enum result was not encoded as an enum node", - range, - )); - }; - let variant_index = usize::try_from(*variant_index).map_err(|_error| { - Diagnostic::error( - "internal error: comptime enum variant index exceeded usize", - range, - ) - })?; - let (_, field_tys) = - enum_variants(db, *enum_ty).get(variant_index).ok_or_else(|| { - Diagnostic::error( - "internal error: comptime enum variant index was out of range", - range, - ) - })?; - Ok(ComptimeValue::Enum { - variant_index, - fields: comptime_child_values(db, &graph, field_tys, fields, range)?, - }) - } - (_, AbiValue::Handle { .. }) => Err(Diagnostic::error( - "internal error: comptime evaluation produced a capability handle", - range, - )), - _ => Err(Diagnostic::error( - format!("internal error: comptime result did not match `{}`", ty.display(db)), - range, - )), - } -} - -fn comptime_node<'a>( - graph: &'a CanonicalGraph, - value_ref: &ValueRef, - kind: &str, - range: mitki_errors::TextRange, -) -> Result<&'a CanonicalNode, Diagnostic> { - let ValueRef::NodeRef(node_id) = value_ref else { - return Err(Diagnostic::error( - format!("internal error: comptime {kind} result was not encoded as a canonical node"), - range, - )); - }; - graph.nodes.get(node_id.0 as usize).ok_or_else(|| { - Diagnostic::error( - format!("internal error: comptime {kind} node id was out of range"), - range, - ) - }) -} - -fn comptime_child_values<'db>( - db: &'db dyn salsa::Database, - graph: &CanonicalGraph, - tys: &[Ty<'db>], - refs: &[ValueRef], - range: mitki_errors::TextRange, -) -> Result, Diagnostic> { - if tys.len() != refs.len() { - return Err(Diagnostic::error( - "internal error: canonical child count did not match the semantic type", - range, - )); - } - tys.iter() - .copied() - .zip(refs.iter()) - .map(|(ty, value_ref)| comptime_value_from_ref(db, ty, graph, value_ref, range)) - .collect() -} - -fn comptime_value_from_ref<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - graph: &CanonicalGraph, - value_ref: &ValueRef, - range: mitki_errors::TextRange, -) -> Result { - match value_ref { - ValueRef::InlineScalar(scalar) => { - comptime_value_from_abi(db, ty, AbiValue::Immediate(scalar.clone()), range) - } - ValueRef::NodeRef(node_id) => { - let node = graph.nodes.get(node_id.0 as usize).ok_or_else(|| { - Diagnostic::error( - "internal error: canonical node reference was out of range", - range, - ) - })?; - comptime_value_from_abi( - db, - ty, - AbiValue::Canonical { - transport_type: node.transport_type(), - graph: CanonicalGraph { - root: ValueRef::NodeRef(*node_id), - nodes: graph.nodes.clone(), - handles: graph.handles.clone(), - }, - }, - range, - ) - } - ValueRef::HandleRef(_) => Err(Diagnostic::error( - "internal error: comptime evaluation produced a nested capability handle", - range, - )), - } -} - -fn comptime_array_items<'db>( - db: &'db dyn salsa::Database, - item_ty: Ty<'db>, - graph: &CanonicalGraph, - elements: &ArrayElements, - range: mitki_errors::TextRange, -) -> Result, Diagnostic> { - match elements { - ArrayElements::Values(values) => values - .iter() - .map(|value_ref| comptime_value_from_ref(db, item_ty, graph, value_ref, range)) - .collect(), - ArrayElements::PackedScalars { kind, len, bytes } => { - comptime_packed_scalar_array(db, item_ty, *kind, *len, bytes, range) - } - } -} - -fn comptime_packed_scalar_array<'db>( - db: &'db dyn salsa::Database, - item_ty: Ty<'db>, - kind: PackedScalarKind, - len: u32, - bytes: &[u8], - range: mitki_errors::TextRange, -) -> Result, Diagnostic> { - let len = usize::try_from(len).map_err(|_error| { - Diagnostic::error("internal error: packed scalar array length exceeded usize", range) - })?; - match (item_ty.kind(db), kind) { - (TyKind::Bool, PackedScalarKind::Bool) => { - if bytes.len() != len { - return Err(Diagnostic::error( - "internal error: packed bool array payload length was invalid", - range, - )); - } - Ok(bytes.iter().map(|byte| ComptimeValue::Bool(*byte != 0)).collect()) - } - (TyKind::Int, PackedScalarKind::I32) => { - if bytes.len() != len * 4 { - return Err(Diagnostic::error( - "internal error: packed int array payload length was invalid", - range, - )); - } - Ok(bytes - .chunks_exact(4) - .map(|chunk| { - ComptimeValue::I32(i32::from_le_bytes( - chunk.try_into().expect("i32 chunk should be 4 bytes"), - )) - }) - .collect()) - } - (TyKind::Float, PackedScalarKind::F64) => { - if bytes.len() != len * 8 { - return Err(Diagnostic::error( - "internal error: packed float array payload length was invalid", - range, - )); - } - Ok(bytes - .chunks_exact(8) - .map(|chunk| { - ComptimeValue::F64(f64::from_bits(u64::from_le_bytes( - chunk.try_into().expect("f64 chunk should be 8 bytes"), - ))) - }) - .collect()) - } - (TyKind::Char, PackedScalarKind::Char) => { - if bytes.len() != len * 4 { - return Err(Diagnostic::error( - "internal error: packed char array payload length was invalid", - range, - )); - } - bytes - .chunks_exact(4) - .map(|chunk| { - let scalar = - u32::from_le_bytes(chunk.try_into().expect("char chunk should be 4 bytes")); - char::from_u32(scalar).map(ComptimeValue::Char).ok_or_else(|| { - Diagnostic::error( - "internal error: packed char array contained an invalid scalar", - range, - ) - }) - }) - .collect() - } - _ => Err(Diagnostic::error( - "internal error: packed scalar array did not match the semantic element type", - range, - )), - } -} - -#[derive(Default)] -struct StaticDataBuilder<'db> { - bytes: Vec, - pooled_offsets: FxHashMap, u32>, - literal_offsets: FxHashMap, u32>, - comptime_values: FxHashMap, ComptimeStaticValue>, -} - -impl<'db> StaticDataBuilder<'db> { - fn finish(self) -> StaticData<'db> { - StaticData { - bytes: self.bytes, - literal_offsets: self.literal_offsets, - comptime_values: self.comptime_values, - } - } - - fn intern_string_bytes( - &mut self, - bytes: Vec, - range: mitki_errors::TextRange, - ) -> Result { - if let Some(offset) = self.pooled_offsets.get(&bytes).copied() { - return Ok(offset); - } - - let base = align_to(self.bytes.len() as u32, ARC_ALIGN); - self.bytes.resize(base as usize, 0); - let len = u32::try_from(bytes.len()).map_err(|_error| { - Diagnostic::error("Wasm backend string literal exceeds the supported size", range) - })?; - self.bytes.extend_from_slice(&ARC_IMMORTAL_REFCNT.to_le_bytes()); - self.bytes.extend_from_slice(&0u32.to_le_bytes()); - self.bytes.extend_from_slice(&len.to_le_bytes()); - self.bytes.extend_from_slice(&bytes); - let offset = base + ARC_HEADER_SIZE; - self.pooled_offsets.insert(bytes, offset); - Ok(offset) - } - - fn intern_comptime_static_value( - &mut self, - value: ComptimeValue, - range: mitki_errors::TextRange, - ) -> Result { - match value { - ComptimeValue::Unit => Ok(ComptimeStaticValue::Unit), - ComptimeValue::I32(_) => Ok(ComptimeStaticValue::I32), - ComptimeValue::Bool(_) => Ok(ComptimeStaticValue::Bool), - ComptimeValue::F64(_) => Ok(ComptimeStaticValue::F64), - ComptimeValue::Char(_) => Ok(ComptimeStaticValue::Char), - ComptimeValue::String(value) => { - let offset = self.intern_string_bytes(value.into_bytes(), range)?; - Ok(ComptimeStaticValue::String(offset)) - } - ComptimeValue::Array(items) => Ok(ComptimeStaticValue::Array( - items - .into_iter() - .map(|item| self.intern_comptime_static_value(item, range)) - .collect::, _>>()?, - )), - ComptimeValue::Tuple(items) => Ok(ComptimeStaticValue::Tuple( - items - .into_iter() - .map(|item| self.intern_comptime_static_value(item, range)) - .collect::, _>>()?, - )), - ComptimeValue::Record(values) => Ok(ComptimeStaticValue::Record( - values - .into_iter() - .map(|value| self.intern_comptime_static_value(value, range)) - .collect::, _>>()?, - )), - ComptimeValue::Struct(values) => Ok(ComptimeStaticValue::Struct( - values - .into_iter() - .map(|value| self.intern_comptime_static_value(value, range)) - .collect::, _>>()?, - )), - ComptimeValue::Enum { variant_index, fields } => Ok(ComptimeStaticValue::Enum { - variant_index, - fields: fields - .into_iter() - .map(|field| self.intern_comptime_static_value(field, range)) - .collect::, _>>()?, - }), - } - } - - fn maybe_store_comptime_value( - &mut self, - backend: &Backend<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - expr: ExprId, - ) -> Result { - let range = node_range(backend, location, source_map, expr); - let Some(target) = resolve_comptime_target(backend, location, function, expr, range)? - else { - return Ok(false); - }; - - let value = backend - .comptime_evaluator - .eval_comptime_function(backend.db, target) - .map_err(|message| Diagnostic::error(message, range))?; - let result_ty = comptime::classify_comptime_result(backend.db, target) - .map_err(|message| Diagnostic::error(message, range))?; - let value = comptime_value_from_abi(backend.db, result_ty, value, range)?; - let static_value = self.intern_comptime_static_value(value, range)?; - self.comptime_values.insert(ComptimeValueKey { location, expr }, static_value); - Ok(true) - } - - fn expr( - &mut self, - backend: &Backend<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - expr: ExprId, - ) -> Result<(), Diagnostic> { - let nodes = function.node_store(); - if self.maybe_store_comptime_value(backend, location, function, source_map, expr)? { - return Ok(()); - } - match nodes.node_kind(expr) { - NodeKind::String => { - let literal = nodes.string(nodes.as_string(expr).expect("String node mismatch")); - let bytes = decode_string_literal(literal, backend.db).map_err(|message| { - Diagnostic::error(message, node_range(backend, location, source_map, expr)) - })?; - let offset = self - .intern_string_bytes(bytes, node_range(backend, location, source_map, expr))?; - if let Some(literal) = literal { - self.literal_offsets.insert(literal, offset); - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - for stmt in stmts.iter() { - self.stmt(backend, location, function, source_map, stmt)?; - } - if tail != ExprId::ZERO { - self.expr(backend, location, function, source_map, tail)?; - } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - self.expr(backend, location, function, source_map, body)?; - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.expr(backend, location, function, source_map, callee)?; - for arg in args.iter() { - self.expr(backend, location, function, source_map, arg)?; - } - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.expr(backend, location, function, source_map, base)?; - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.expr(backend, location, function, source_map, binary.lhs)?; - self.expr(backend, location, function, source_map, binary.rhs)?; - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.expr(backend, location, function, source_map, prefix.expr)?; - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.expr(backend, location, function, source_map, if_expr.cond)?; - if if_expr.then_branch != ExprId::ZERO { - self.expr(backend, location, function, source_map, if_expr.then_branch)?; - } - if if_expr.else_branch != ExprId::ZERO { - self.expr(backend, location, function, source_map, if_expr.else_branch)?; - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.expr(backend, location, function, source_map, scrutinee)?; - for arm in arms.iter() { - let (pattern, body) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - self.pattern(backend, location, function, source_map, pattern)?; - self.expr(backend, location, function, source_map, body)?; - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.expr(backend, location, function, source_map, body)?; - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.expr(backend, location, function, source_map, item)?; - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(expr).expect("Array node mismatch")); - for item in array.iter() { - self.expr(backend, location, function, source_map, item)?; - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - self.expr(backend, location, function, source_map, value)?; - self.expr(backend, location, function, source_map, len)?; - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.expr(backend, location, function, source_map, items.get(index).unwrap())?; - index += 2; - } - } - _ => {} - } - - Ok(()) - } - - fn stmt( - &mut self, - backend: &Backend<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - stmt: StmtId, - ) -> Result<(), Diagnostic> { - let nodes = function.node_store(); - if nodes.node_kind(stmt) == NodeKind::LocalVar { - let var = nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar node mismatch")); - self.pattern(backend, location, function, source_map, var.pattern)?; - if var.initializer != ExprId::ZERO { - self.expr(backend, location, function, source_map, var.initializer)?; - } - } else if nodes.node_kind(stmt) == NodeKind::AssignStmt { - let (target, value) = - nodes.assign_stmt(nodes.as_assign_stmt(stmt).expect("AssignStmt mismatch")); - self.expr(backend, location, function, source_map, target)?; - self.expr(backend, location, function, source_map, value)?; - } else if nodes.node_kind(stmt) == NodeKind::ReturnStmt { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.expr(backend, location, function, source_map, value)?; - } - } else if let Some(expr) = stmt_as_expr(nodes, stmt) { - self.expr(backend, location, function, source_map, expr)?; - } - Ok(()) - } - - fn pattern( - &mut self, - backend: &Backend<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - pattern: PatId, - ) -> Result<(), Diagnostic> { - if pattern == PatId::ZERO { - return Ok(()); - } - let nodes = function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatString => { - let literal = - nodes.pat_string(nodes.as_pat_string(pattern).expect("PatString mismatch")); - let bytes = decode_string_literal(literal, backend.db).map_err(|message| { - let range = source_map - .try_pat_syntax(pattern) - .map_or_else(|| backend.function_range(location), |ptr| ptr.range); - Diagnostic::error(message, range) - })?; - let range = source_map - .try_pat_syntax(pattern) - .map_or_else(|| backend.function_range(location), |ptr| ptr.range); - let offset = self.intern_string_bytes(bytes, range)?; - if let Some(literal) = literal { - self.literal_offsets.insert(literal, offset); - } - } - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - self.pattern(backend, location, function, source_map, inner)?; - } - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.pattern(backend, location, function, source_map, inner)?; - } - NodeKind::PatTuple => { - for item in nodes.pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")).iter() { - self.pattern(backend, location, function, source_map, item)?; - } - } - NodeKind::PatVariant => { - let (_, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - for arg in args.iter() { - self.pattern(backend, location, function, source_map, arg)?; - } - } - NodeKind::PatStruct => { - let (_, fields) = - nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - for field in fields.iter() { - let (_, pat) = - nodes.pat_struct_field(nodes.as_pat_struct_field(field).expect("field")); - self.pattern(backend, location, function, source_map, pat)?; - } - } - _ => {} - } - Ok(()) - } -} - -pub(in crate::backend) struct StaticData<'db> { - bytes: Vec, - literal_offsets: FxHashMap, u32>, - comptime_values: FxHashMap, ComptimeStaticValue>, -} - -impl<'db> StaticData<'db> { - fn offset(&self, literal: Option>) -> Option { - literal.and_then(|literal| self.literal_offsets.get(&literal).copied()) - } - - fn comptime_value( - &self, - location: FunctionLocation<'db>, - expr: ExprId, - ) -> Option<&ComptimeStaticValue> { - self.comptime_values.get(&ComptimeValueKey { location, expr }) - } -} - -fn function_param_binding_name<'db>(function: &Function<'db>, index: usize) -> Option { - let ¶m = function.params().get(index)?; - let (pattern, _) = function.node_store().param(param); - let binding = function.node_store().as_pat_binding(pattern)?; - let (name, _) = function.node_store().pat_binding(binding); - Some(name) -} - -fn helper_function_signature(helper: HelperFunction) -> FunctionSignature { - match helper { - HelperFunction::MemoryEq => FunctionSignature { - params: vec![ - AbiTy::Scalar(BackendTy::Int), - AbiTy::Scalar(BackendTy::Int), - AbiTy::Scalar(BackendTy::Int), - ], - result: AbiTy::Scalar(BackendTy::Bool), - }, - HelperFunction::StringEq => FunctionSignature { - params: vec![ - AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - ], - result: AbiTy::Scalar(BackendTy::Bool), - }, - HelperFunction::ArcRetain => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Ref(RefKind::Opaque))], - result: AbiTy::Scalar(BackendTy::Unit), - }, - HelperFunction::ArcRelease => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Ref(RefKind::Opaque))], - result: AbiTy::Scalar(BackendTy::Bool), - }, - } -} - -fn emit_helper_function( - helper: HelperFunction, - helper_indices: &FxHashMap, -) -> WasmFunction { - let mut function = match helper { - HelperFunction::MemoryEq | HelperFunction::StringEq => { - WasmFunction::new(vec![(1, ValType::I32)]) - } - HelperFunction::ArcRetain | HelperFunction::ArcRelease => { - WasmFunction::new(vec![(2, ValType::I32)]) - } - }; - - match helper { - HelperFunction::MemoryEq => emit_memory_eq_helper(&mut function), - HelperFunction::StringEq => { - let memory_eq = helper_indices - .get(&HelperFunction::MemoryEq) - .copied() - .expect("string_eq helper depends on memory_eq"); - emit_string_eq_helper(&mut function, memory_eq); - } - HelperFunction::ArcRetain => emit_arc_retain_helper(&mut function), - HelperFunction::ArcRelease => emit_arc_release_helper(&mut function), - } - - function.instruction(&Instruction::End); - function -} - -fn emit_memory_eq_helper(function: &mut WasmFunction) { - const LHS_PTR: u32 = 0; - const RHS_PTR: u32 = 1; - const LEN: u32 = 2; - const INDEX: u32 = 3; - - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::LocalSet(INDEX)); - - function.instruction(&Instruction::Block(BlockType::Empty)); - function.instruction(&Instruction::Loop(BlockType::Empty)); - - function.instruction(&Instruction::LocalGet(INDEX)); - function.instruction(&Instruction::LocalGet(LEN)); - function.instruction(&Instruction::I32GeU); - function.instruction(&Instruction::BrIf(1)); - - function.instruction(&Instruction::LocalGet(LHS_PTR)); - function.instruction(&Instruction::LocalGet(INDEX)); - function.instruction(&Instruction::I32Add); - MemAccess::byte(0).emit_load(function); - - function.instruction(&Instruction::LocalGet(RHS_PTR)); - function.instruction(&Instruction::LocalGet(INDEX)); - function.instruction(&Instruction::I32Add); - MemAccess::byte(0).emit_load(function); - - function.instruction(&Instruction::I32Ne); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(INDEX)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalSet(INDEX)); - function.instruction(&Instruction::Br(0)); - - function.instruction(&Instruction::End); - function.instruction(&Instruction::End); - function.instruction(&Instruction::I32Const(1)); -} - -fn emit_string_eq_helper(function: &mut WasmFunction, memory_eq_index: u32) { - const LHS_PTR: u32 = 0; - const RHS_PTR: u32 = 1; - const LEN: u32 = 2; - - function.instruction(&Instruction::LocalGet(LHS_PTR)); - function.instruction(&Instruction::LocalGet(RHS_PTR)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(LHS_PTR)); - MemAccess::i32(0, 2).emit_load(function); - function.instruction(&Instruction::LocalTee(LEN)); - - function.instruction(&Instruction::LocalGet(RHS_PTR)); - MemAccess::i32(0, 2).emit_load(function); - function.instruction(&Instruction::I32Ne); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(LHS_PTR)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalGet(RHS_PTR)); - function.instruction(&Instruction::I32Const(4)); - function.instruction(&Instruction::I32Add); - function.instruction(&Instruction::LocalGet(LEN)); - function.instruction(&Instruction::Call(memory_eq_index)); -} - -fn emit_arc_retain_helper(function: &mut WasmFunction) { - const PTR: u32 = 0; - const BASE: u32 = 1; - const COUNT: u32 = 2; - - function.instruction(&Instruction::LocalGet(PTR)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(PTR)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalTee(BASE)); - MemAccess::arc_ref_count().emit_load(function); - function.instruction(&Instruction::LocalTee(COUNT)); - function.instruction(&Instruction::I32Const(ARC_IMMORTAL_REFCNT)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(BASE)); - function.instruction(&Instruction::LocalGet(COUNT)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Add); - MemAccess::arc_ref_count().emit_store(function); -} - -fn emit_arc_release_helper(function: &mut WasmFunction) { - const PTR: u32 = 0; - const BASE: u32 = 1; - const COUNT: u32 = 2; - - function.instruction(&Instruction::LocalGet(PTR)); - function.instruction(&Instruction::I32Eqz); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(PTR)); - function.instruction(&Instruction::I32Const(ARC_HEADER_SIZE as i32)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalTee(BASE)); - MemAccess::arc_ref_count().emit_load(function); - function.instruction(&Instruction::LocalTee(COUNT)); - function.instruction(&Instruction::I32Const(ARC_IMMORTAL_REFCNT)); - function.instruction(&Instruction::I32Eq); - function.instruction(&Instruction::If(BlockType::Empty)); - function.instruction(&Instruction::I32Const(0)); - function.instruction(&Instruction::Return); - function.instruction(&Instruction::End); - - function.instruction(&Instruction::LocalGet(COUNT)); - function.instruction(&Instruction::I32Const(1)); - function.instruction(&Instruction::I32Sub); - function.instruction(&Instruction::LocalSet(COUNT)); - function.instruction(&Instruction::LocalGet(BASE)); - function.instruction(&Instruction::LocalGet(COUNT)); - MemAccess::arc_ref_count().emit_store(function); - function.instruction(&Instruction::LocalGet(COUNT)); - function.instruction(&Instruction::I32Eqz); -} - -fn closure_destroy_layout(word_type: ValType) -> FunctionLayout { - let mut local_plan = LocalPlanBuilder::new(0); - local_plan.add_param(LocalPurpose::EnvPtrParam, None, Some(word_type)); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32Aux, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ObjectI32, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ScratchF64, ValType::F64); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI64, ValType::I64); - FunctionLayout::new( - local_plan.finish(), - FramePlan::default(), - FunctionLayoutLookups { - slots: FxHashMap::default(), - param_names: Vec::new(), - raw_params: Vec::new(), - temps: FxHashMap::default(), - pattern_scalar_locals: FxHashMap::default(), - nominal_locals: FxHashMap::default(), - array_repeat_locals: FxHashMap::default(), - }, - ) -} - -fn scratch_only_layout(param_count: u32, word_type: ValType) -> FunctionLayout { - let mut local_plan = LocalPlanBuilder::new(param_count); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI32Aux, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ObjectI32, word_type); - local_plan.alloc_scratch(ScratchLocalKind::ScratchF64, ValType::F64); - local_plan.alloc_scratch(ScratchLocalKind::ScratchI64, ValType::I64); - FunctionLayout::new( - local_plan.finish(), - FramePlan::default(), - FunctionLayoutLookups { - slots: FxHashMap::default(), - param_names: Vec::new(), - raw_params: Vec::new(), - temps: FxHashMap::default(), - pattern_scalar_locals: FxHashMap::default(), - nominal_locals: FxHashMap::default(), - array_repeat_locals: FxHashMap::default(), - }, - ) -} - -fn memory_min_pages(static_data_len: usize) -> u64 { - let bytes = static_data_len.max(1); - let bytes = u64::try_from(bytes).expect("static data length should fit into u64"); - bytes.div_ceil(65_536) -} - -fn abi_mem_access(abi: &AbiTy, offset: u32) -> Option { - match abi { - AbiTy::Scalar(ty) => MemAccess::scalar(offset, *ty), - AbiTy::Aggregate(_) => None, - } -} - -fn pointee_mem_access( - db: &dyn salsa::Database, - pointee: Ty<'_>, - fallback_abi: &AbiTy, - offset: u32, -) -> Option { - match pointee.kind(db) { - TyKind::ExactInt(mitki_hir::ty::ExactInt::U8) => Some(MemAccess::byte(offset)), - _ => abi_mem_access(fallback_abi, offset), - } -} - -fn pointee_stride(db: &dyn salsa::Database, pointee: Ty<'_>) -> Option { - if let TyKind::ExactInt(int_ty) = pointee.kind(db) { - Some(u32::from(int_ty.bits().div_ceil(8)).max(1)) - } else { - let abi = crate::capability::supported_value_abi(db, pointee)?; - crate::layout::abi_layout(&abi).map(|layout| layout.size.max(1)) - } -} - -pub(super) fn emit_scalar_load(function: &mut WasmFunction, ty: BackendTy, offset: u32) { - if let Some(access) = MemAccess::scalar(offset, ty) { - access.emit_load(function); - } -} - -pub(super) fn emit_scalar_store(function: &mut WasmFunction, ty: BackendTy, offset: u32) { - if let Some(access) = MemAccess::scalar(offset, ty) { - access.emit_store(function); - } -} - -fn emit_raw_import_forwarder( - range: mitki_errors::TextRange, - import_index: u32, - signature: &FunctionSignature, -) -> Result { - if signature.result.is_aggregate() { - return Err(Diagnostic::error("raw Wasm imports cannot return aggregate values", range)); - } - if signature.params.iter().any(AbiTy::is_aggregate) { - return Err(Diagnostic::error("raw Wasm imports cannot take aggregate parameters", range)); - } - - let mut function = WasmFunction::new(Vec::new()); - let mut next_param = 0u32; - for param in &signature.params { - let AbiTy::Scalar(backend_ty) = param else { - unreachable!(); - }; - if backend_ty_value_type(*backend_ty).is_some() { - function.instruction(&Instruction::LocalGet(next_param)); - next_param += 1; - } - } - function.instruction(&Instruction::Call(import_index)); - function.instruction(&Instruction::End); - Ok(function) -} - -#[cfg(test)] -mod tests { - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "storage_plan_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!( - diagnostics.is_empty(), - "unexpected diagnostics: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - backend - } - - fn function_layout_for_fixture(fixture: &str, expected_name: &str) -> FunctionLayout { - let backend = compiler_for_fixture(fixture); - let instance = backend - .build_reachability_graph() - .functions - .iter() - .find_map(|instance| { - instance - .location - .source(backend.db) - .name() - .is_some_and(|name| name.as_str() == expected_name) - .then_some(instance.clone()) - }) - .expect("expected reachable function"); - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let signature = backend - .function_signature(&instance, function, inference) - .unwrap_or_else(|diagnostic| panic!("function signature: {}", diagnostic.message())); - backend - .build_layout(&ReachableInstance::Function(instance), function, inference, &signature) - .unwrap_or_else(|diagnostic| panic!("storage layout: {}", diagnostic.message())) - } - - fn storage_fixture() -> &'static str { - r#" -extern struct ByteSlice { - ptr: *const u8, - len: u32, -} - -unsafe fun local_layout(flag: bool): u32 { - val count: u32 = 1 - val xs: [int] = [1, 2, 3] - val bytes: ByteSlice = str_bytes("ok") - val out: *mut u32 = stack_alloc(1) - ptr_write(out, if flag { bytes.len } else { count }) - ptr_read(out) -} - -export fun main(): int { - unsafe { - if local_layout(true) == 2 { - 1 - } else { - 0 - } - } -} -"# - } - - #[test] - fn storage_layout_exposes_explicit_local_categories() { - let layout = function_layout_for_fixture(storage_fixture(), "local_layout"); - - assert_eq!(layout.local_plan.params.len(), 1, "expected one runtime parameter"); - assert!( - layout.local_plan.user_locals.len() >= 2, - "expected explicit user locals for scalar/ref bindings" - ); - assert!( - !layout.local_plan.spills.is_empty(), - "expected explicit spill locals for temporary object storage" - ); - assert_eq!( - layout.local_plan.joins.len(), - 0, - "current function emitter should keep joins explicit but unused in Step 6" - ); - assert!( - layout.local_plan.scratch.len() >= 4, - "expected scratch locals to be planned explicitly" - ); - } - - #[test] - fn storage_layout_assigns_frame_slots_for_bindings_temps_and_stack_alloc() { - let layout = function_layout_for_fixture(storage_fixture(), "local_layout"); - - assert!( - layout - .frame_plan - .slots - .iter() - .any(|slot| matches!(slot.purpose, FrameSlotPurpose::Binding(_))), - "expected aggregate bindings to live in explicit frame slots" - ); - assert!( - layout - .frame_plan - .slots - .iter() - .any(|slot| matches!(slot.purpose, FrameSlotPurpose::Temp(_))), - "expected aggregate expression temps to live in explicit frame slots" - ); - assert!( - layout - .frame_plan - .slots - .iter() - .any(|slot| matches!(slot.purpose, FrameSlotPurpose::StackAlloc(_))), - "expected stack_alloc to reserve an explicit frame slot" - ); - } - - #[test] - fn storage_layout_dump_is_deterministic_across_repeated_planning() { - let first = function_layout_for_fixture(storage_fixture(), "local_layout").dump_storage(); - let second = function_layout_for_fixture(storage_fixture(), "local_layout").dump_storage(); - assert_eq!(first, second); - } -} diff --git a/crates/mitki-backend-wasm/src/layout.rs b/crates/mitki-backend-wasm/src/layout.rs deleted file mode 100644 index abaf844..0000000 --- a/crates/mitki-backend-wasm/src/layout.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[allow(unused_imports)] -pub use mitki_codegen_core::layout::{ - ARC_ALIGN, ARC_HEADER_SIZE, ARC_IMMORTAL_REFCNT, ARRAY_CAPACITY_OFFSET, ARRAY_HEADER_SIZE, - ARRAY_LEN_OFFSET, AggregateKind, AggregateLayout, ArrayRuntimeLayout, EnumLayout, FieldLayout, - FieldsLayout, MemoryArg, VariantLayout, abi_layout, align_to, layout_fields, symbol_bits, -}; -use wasm_encoder::MemArg; - -pub fn memarg(offset: u32, align: u32) -> MemArg { - let arg = mitki_codegen_core::layout::memarg(offset, align); - MemArg { offset: arg.offset, align: arg.align, memory_index: arg.memory_index } -} diff --git a/crates/mitki-backend-wasm/src/lib.rs b/crates/mitki-backend-wasm/src/lib.rs deleted file mode 100644 index b656898..0000000 --- a/crates/mitki-backend-wasm/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub mod abi; -pub mod capability; -pub mod layout; - -mod api; -mod backend; - -pub use api::{ - CompileConfig, CompileOptions, ComptimeEvaluator, NoopComptimeEvaluator, compile_file, - compile_file_with_options, compile_function, compile_function_with_options, -}; -pub use backend::{ - Backend, BoundaryLegalityValidator, CapabilityValidator, boundary, planning, registry, - validation, -}; -pub use mitki_abi::{AbiScalar, AbiValue, CanonicalBlobView, CanonicalGraph, TransportClass}; -use mitki_errors::Diagnostic; - -pub use self::boundary::BoundaryPlan; -pub use self::planning::{EmissionObligations, ModulePlan, ReachabilityGraph}; - -pub fn collect_reachability_and_obligations<'db>( - backend: &mut Backend<'db>, - collect_stage_diagnostics: bool, -) -> Result<(), Vec> { - backend.collect_reachable_program(); - if collect_stage_diagnostics { - backend.collect_stage_diagnostics(); - } - if backend.diagnostics().is_empty() { Ok(()) } else { Err(backend.diagnostics().to_vec()) } -} - -pub fn check_boundary_legality<'db>(backend: &Backend<'db>) -> Result<(), Vec> { - let diagnostics = BoundaryLegalityValidator::check(backend); - if diagnostics.is_empty() { Ok(()) } else { Err(diagnostics) } -} - -pub fn check_capability<'db>(backend: &Backend<'db>) -> Result<(), Diagnostic> { - CapabilityValidator::check(backend) -} - -pub fn build_module_plan<'db>(backend: &Backend<'db>) -> Result, Diagnostic> { - backend.build_module_plan() -} diff --git a/crates/mitki-backend-wasm/src/lowering/function_codegen_ir.rs b/crates/mitki-backend-wasm/src/lowering/function_codegen_ir.rs deleted file mode 100644 index fe411c3..0000000 --- a/crates/mitki-backend-wasm/src/lowering/function_codegen_ir.rs +++ /dev/null @@ -1,2503 +0,0 @@ -#[cfg(test)] -use std::sync::Arc; - -use rustc_hash::{FxHashMap, FxHashSet}; -use wasm_encoder::ValType; - -use super::function_kernel::{ - FunctionKernelBindingInit, FunctionKernelBindingSource, FunctionKernelBundle, - FunctionKernelFunction, FunctionKernelId, FunctionKernelKind, FunctionKernelMatchArm, - FunctionKernelStmt, FunctionKernelTerminator, FunctionKernelValue, FunctionKernelValueKind, -}; -use super::plan::ModulePlan; -use super::*; -use crate::abi::backend_ty_value_type; - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmBundle<'db> { - pub(in crate::backend) direct_functions: Vec>, - pub(in crate::backend) closures: Vec>, - pub(in crate::backend) direct_indices: FxHashMap, usize>, - pub(in crate::backend) closure_indices: FxHashMap, usize>, -} - -impl<'db> StructuredWasmBundle<'db> { - #[allow(dead_code)] - pub(in crate::backend) fn direct( - &self, - instance: &InstanceKey<'db>, - ) -> Option<&StructuredWasmFunction<'db>> { - self.direct_indices.get(instance).and_then(|&index| self.direct_functions.get(index)) - } - - #[allow(dead_code)] - pub(in crate::backend) fn closure( - &self, - closure: &ClosureInstanceKey<'db>, - ) -> Option<&StructuredWasmFunction<'db>> { - self.closure_indices.get(closure).and_then(|&index| self.closures.get(index)) - } -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmFunction<'db> { - pub(in crate::backend) id: FunctionKernelId, - pub(in crate::backend) kind: FunctionKernelKind<'db>, - #[cfg_attr(not(test), allow(dead_code))] - pub(in crate::backend) debug_name: String, - pub(in crate::backend) signature: FunctionSignature, - pub(in crate::backend) layout: FunctionLayout, - pub(in crate::backend) legalization: Option>, - pub(in crate::backend) extra_locals: Vec, - pub(in crate::backend) param_inits: Vec>, - pub(in crate::backend) body: Option>, - pub(in crate::backend) body_ownership_ops: Vec, - #[cfg_attr(not(test), allow(dead_code))] - pub(in crate::backend) decisions: Vec, -} - -impl<'db> StructuredWasmFunction<'db> { - pub(in crate::backend) fn wasm_locals(&self) -> Vec<(u32, ValType)> { - let mut locals = self.layout.wasm_locals().to_vec(); - for local in &self.extra_locals { - match locals.last_mut() { - Some((count, value_type)) if *value_type == local.ty => *count += 1, - _ => locals.push((1, local.ty)), - } - } - locals - } -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmLocal { - pub(in crate::backend) index: u32, - pub(in crate::backend) ty: ValType, - #[cfg_attr(not(test), allow(dead_code))] - pub(in crate::backend) reason: StructuredWasmLocalReason, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum StructuredWasmLocalReason { - Join { - #[cfg_attr(not(test), allow(dead_code))] - source: ExprId, - #[cfg_attr(not(test), allow(dead_code))] - label: &'static str, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum StructuredWasmResultArity { - Unit, - Scalar(ValType), - Aggregate, -} - -impl StructuredWasmResultArity { - fn for_abi(abi: &AbiTy, _mode: ResultLoweringMode) -> Result { - match abi { - AbiTy::Scalar(BackendTy::Unit) => Ok(Self::Unit), - AbiTy::Scalar(ty) => backend_ty_value_type(*ty).map(Self::Scalar).ok_or_else(|| { - Diagnostic::error( - "internal error: missing scalar value type", - mitki_errors::TextRange::default(), - ) - }), - AbiTy::Aggregate(_) => Ok(Self::Aggregate), - } - } -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmExpr<'db> { - pub(in crate::backend) source: ExprId, - pub(in crate::backend) abi: AbiTy, - pub(in crate::backend) ownership: ValueOwnership, - pub(in crate::backend) kind: StructuredWasmExprKind<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum StructuredWasmExprKind<'db> { - Leaf(FunctionKernelValue<'db>), - WasmLocal(u32), - Eqz { - value: Box>, - }, - TeeLocal { - local: u32, - value: Box>, - }, - Select { - cond: Box>, - then_value: Box>, - else_value: Box>, - }, - Block { - region: StructuredWasmRegion<'db>, - result_arity: StructuredWasmResultArity, - }, - If { - cond: Box>, - then_region: StructuredWasmRegion<'db>, - else_region: StructuredWasmRegion<'db>, - result_arity: StructuredWasmResultArity, - }, - Match { - scrutinee: Box>, - arms: Vec>, - result_arity: StructuredWasmResultArity, - fallback_unreachable: bool, - }, - Loop { - body: Option>>, - result_arity: StructuredWasmResultArity, - }, - Break, - Continue, - #[allow(dead_code)] - Unreachable, -} - -#[derive(Clone, Debug, Default)] -pub(in crate::backend) struct StructuredWasmRegion<'db> { - pub(in crate::backend) stmts: Vec>, - pub(in crate::backend) tail: Option>>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum StructuredWasmStmt<'db> { - Local { - name: NameId, - abi: AbiTy, - initializer: Option>, - ownership_ops: Vec, - }, - Assign { - name: NameId, - abi: AbiTy, - value: StructuredWasmExpr<'db>, - }, - If { - source: ExprId, - abi: AbiTy, - ownership: ValueOwnership, - cond: StructuredWasmExpr<'db>, - then_region: StructuredWasmRegion<'db>, - else_region: StructuredWasmRegion<'db>, - ownership_ops: Vec, - }, - Match { - source: ExprId, - abi: AbiTy, - ownership: ValueOwnership, - scrutinee: StructuredWasmExpr<'db>, - arms: Vec>, - fallback_unreachable: bool, - ownership_ops: Vec, - }, - Pattern(StructuredWasmBindingInit<'db>), - Return { - source: ExprId, - value: Option>, - ownership_ops: Vec, - }, - Expr { - expr: StructuredWasmExpr<'db>, - ownership_ops: Vec, - }, - SetLocal { - #[allow(dead_code)] - source: ExprId, - local: u32, - ty: ValType, - value: StructuredWasmExpr<'db>, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum StructuredWasmOwnershipOp { - Retain, - Release, - Destroy, - Copy, - Move, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmBindingInit<'db> { - pub(in crate::backend) pattern: BackendPattern, - pub(in crate::backend) source: StructuredWasmBindingSource<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum StructuredWasmBindingSource<'db> { - Expr(StructuredWasmExpr<'db>), - Param { index: usize, abi: AbiTy, source: ExprId }, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmMatchArm<'db> { - pub(in crate::backend) pattern: BackendPattern, - pub(in crate::backend) body: StructuredWasmRegion<'db>, -} - -pub(in crate::backend) struct FunctionStackifier; -pub(in crate::backend) struct StructuredWasmPeephole; -pub(in crate::backend) struct StructuredWasmValidator; - -impl FunctionStackifier { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result, Diagnostic> { - let mut output = StructuredWasmBundle { - direct_functions: Vec::new(), - closures: Vec::new(), - direct_indices: FxHashMap::default(), - closure_indices: FxHashMap::default(), - }; - - for function in &bundle.direct_functions { - let built = StackifyFunction::new(function, backend.control_flow_strategy()).build()?; - output.direct_indices.insert( - match &built.kind { - FunctionKernelKind::Direct(instance) => instance.clone(), - FunctionKernelKind::Closure(_) => unreachable!(), - }, - output.direct_functions.len(), - ); - output.direct_functions.push(built); - } - - for function in &bundle.closures { - let built = StackifyFunction::new(function, backend.control_flow_strategy()).build()?; - output.closure_indices.insert( - match &built.kind { - FunctionKernelKind::Closure(closure) => closure.clone(), - FunctionKernelKind::Direct(_) => unreachable!(), - }, - output.closures.len(), - ); - output.closures.push(built); - } - - StructuredWasmValidator::validate(backend, plan, &output)?; - Ok(output) - } -} - -struct StackifyFunction<'a, 'db> { - function: &'a FunctionKernelFunction<'db>, - control_flow: ControlFlowStrategy, - next_local: u32, - extra_locals: Vec, - decisions: Vec, -} - -impl<'a, 'db> StackifyFunction<'a, 'db> { - fn new(function: &'a FunctionKernelFunction<'db>, control_flow: ControlFlowStrategy) -> Self { - Self { - function, - control_flow, - next_local: function.layout.next_local_index(), - extra_locals: Vec::new(), - decisions: Vec::new(), - } - } - - fn build(mut self) -> Result, Diagnostic> { - self.decisions.push(format!( - "result_lowering={}", - self.control_flow.default_result_lowering().dump_name() - )); - self.decisions - .push(format!("failure_lowering={}", self.control_flow.failure_lowering().dump_name())); - let param_inits = self - .function - .param_inits - .iter() - .map(|init| self.stackify_binding_init(init)) - .collect::, _>>()?; - let body = self.stackify_body_from_block()?; - Ok(StructuredWasmFunction { - id: self.function.id, - kind: self.function.kind.clone(), - debug_name: self.function.debug_name.clone(), - signature: self.function.signature.clone(), - layout: self.function.layout.clone(), - legalization: None, - extra_locals: self.extra_locals, - param_inits, - body, - body_ownership_ops: Vec::new(), - decisions: self.decisions, - }) - } - - fn stackify_body_from_block(&mut self) -> Result>, Diagnostic> { - let block = self.function.entry_block(); - let mut stmts = Vec::new(); - for stmt in &block.stmts { - match stmt { - FunctionKernelStmt::Retain { .. } - | FunctionKernelStmt::Release { .. } - | FunctionKernelStmt::Destroy { .. } - | FunctionKernelStmt::Copy { .. } - | FunctionKernelStmt::Move { .. } => {} - _ => stmts.push(self.stackify_stmt(stmt)?), - } - } - let tail = match &block.terminator { - FunctionKernelTerminator::Return { value, .. } => { - value.as_ref().map(|value| self.stackify_expr(value)).transpose()?.map(Box::new) - } - FunctionKernelTerminator::Unreachable { source } => { - Some(Box::new(StructuredWasmExpr { - source: *source, - abi: block.result_abi.clone(), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Unreachable, - })) - } - }; - - if stmts.is_empty() && tail.is_none() { - return Ok(None); - } - - Ok(Some(StructuredWasmExpr { - source: block.source, - abi: block.result_abi.clone(), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Block { - region: StructuredWasmRegion { stmts, tail }, - result_arity: self.result_arity(&block.result_abi)?, - }, - })) - } - - fn stackify_binding_init( - &mut self, - init: &FunctionKernelBindingInit<'db>, - ) -> Result, Diagnostic> { - Ok(StructuredWasmBindingInit { - pattern: init.pattern.clone(), - source: match &init.source { - FunctionKernelBindingSource::Value(expr) => { - StructuredWasmBindingSource::Expr(self.stackify_expr(expr)?) - } - FunctionKernelBindingSource::Param { index, abi, source } => { - StructuredWasmBindingSource::Param { - index: *index, - abi: abi.clone(), - source: *source, - } - } - }, - }) - } - - fn stackify_stmt( - &mut self, - stmt: &FunctionKernelStmt<'db>, - ) -> Result, Diagnostic> { - Ok(match stmt { - FunctionKernelStmt::Local { name, abi, initializer } => StructuredWasmStmt::Local { - name: *name, - abi: abi.clone(), - initializer: initializer - .as_ref() - .map(|expr| self.stackify_expr(expr)) - .transpose()?, - ownership_ops: Vec::new(), - }, - FunctionKernelStmt::Assign { name, abi, value } => StructuredWasmStmt::Assign { - name: *name, - abi: abi.clone(), - value: self.stackify_expr(value)?, - }, - FunctionKernelStmt::Pattern(init) => { - StructuredWasmStmt::Pattern(self.stackify_binding_init(init)?) - } - FunctionKernelStmt::Return { source, value } => StructuredWasmStmt::Return { - source: *source, - value: value.as_ref().map(|expr| self.stackify_expr(expr)).transpose()?, - ownership_ops: Vec::new(), - }, - FunctionKernelStmt::Expr(expr) => self.stackify_stmt_expr(expr)?, - FunctionKernelStmt::Retain { .. } - | FunctionKernelStmt::Release { .. } - | FunctionKernelStmt::Destroy { .. } - | FunctionKernelStmt::Copy { .. } - | FunctionKernelStmt::Move { .. } => { - return Err(Diagnostic::error( - "internal error: ownership op reached structured Wasm stackification", - mitki_errors::TextRange::default(), - )); - } - }) - } - - fn stackify_stmt_expr( - &mut self, - expr: &FunctionKernelValue<'db>, - ) -> Result, Diagnostic> { - Ok(match &expr.kind { - FunctionKernelValueKind::If { cond, then_branch, else_branch } => { - StructuredWasmStmt::If { - source: expr.source, - abi: expr.abi.clone(), - ownership: expr.ownership, - cond: self.stackify_expr(cond)?, - then_region: then_branch - .as_deref() - .map(|branch| self.stackify_region_from_expr(branch)) - .transpose()? - .unwrap_or_default(), - else_region: else_branch - .as_deref() - .map(|branch| self.stackify_region_from_expr(branch)) - .transpose()? - .unwrap_or_default(), - ownership_ops: Vec::new(), - } - } - FunctionKernelValueKind::Match { scrutinee, arms } => StructuredWasmStmt::Match { - source: expr.source, - abi: expr.abi.clone(), - ownership: expr.ownership, - scrutinee: self.stackify_expr(scrutinee)?, - arms: arms - .iter() - .map(|arm| { - Ok(StructuredWasmMatchArm { - pattern: arm.pattern.clone(), - body: self.stackify_region_from_expr(&arm.body)?, - }) - }) - .collect::, Diagnostic>>()?, - fallback_unreachable: true, - ownership_ops: Vec::new(), - }, - _ => StructuredWasmStmt::Expr { - expr: self.stackify_expr(expr)?, - ownership_ops: Vec::new(), - }, - }) - } - - fn stackify_region_from_expr( - &mut self, - expr: &FunctionKernelValue<'db>, - ) -> Result, Diagnostic> { - match &expr.kind { - FunctionKernelValueKind::Block { stmts, tail } => Ok(StructuredWasmRegion { - stmts: stmts - .iter() - .map(|stmt| self.stackify_stmt(stmt)) - .collect::, _>>()?, - tail: tail.as_ref().map(|tail| self.stackify_expr(tail)).transpose()?.map(Box::new), - }), - _ => Ok(StructuredWasmRegion { - stmts: Vec::new(), - tail: Some(Box::new(self.stackify_expr(expr)?)), - }), - } - } - - fn stackify_expr( - &mut self, - expr: &FunctionKernelValue<'db>, - ) -> Result, Diagnostic> { - let result_arity = self.result_arity(&expr.abi)?; - let ownership = match &expr.kind { - FunctionKernelValueKind::If { .. } | FunctionKernelValueKind::Match { .. } - if result_arity != StructuredWasmResultArity::Aggregate - && result_arity != StructuredWasmResultArity::Unit - && matches!( - expr.abi, - AbiTy::Scalar(ty) if ty.is_heap_ref() && expr.ownership.is_borrowed() - ) => - { - self.decisions.push(format!( - "join {:?} promoted borrowed heap ref to owned temp", - expr.source - )); - ValueOwnership::Owned - } - _ => expr.ownership, - }; - let kind = match &expr.kind { - FunctionKernelValueKind::Block { stmts, tail } => StructuredWasmExprKind::Block { - region: StructuredWasmRegion { - stmts: stmts - .iter() - .map(|stmt| self.stackify_stmt(stmt)) - .collect::, _>>()?, - tail: tail - .as_ref() - .map(|tail| self.stackify_expr(tail)) - .transpose()? - .map(Box::new), - }, - result_arity, - }, - FunctionKernelValueKind::If { cond, then_branch, else_branch } => { - self.stackify_if_expr(expr, cond, then_branch.as_deref(), else_branch.as_deref())? - } - FunctionKernelValueKind::Match { scrutinee, arms } => { - self.stackify_match_expr(expr, scrutinee, arms)? - } - FunctionKernelValueKind::Loop { body } => StructuredWasmExprKind::Loop { - body: body.as_ref().map(|body| self.stackify_expr(body)).transpose()?.map(Box::new), - result_arity, - }, - FunctionKernelValueKind::Break => StructuredWasmExprKind::Break, - FunctionKernelValueKind::Continue => StructuredWasmExprKind::Continue, - _ => StructuredWasmExprKind::Leaf(expr.clone()), - }; - Ok(StructuredWasmExpr { source: expr.source, abi: expr.abi.clone(), ownership, kind }) - } - - fn stackify_if_expr( - &mut self, - expr: &FunctionKernelValue<'db>, - cond: &FunctionKernelValue<'db>, - then_branch: Option<&FunctionKernelValue<'db>>, - else_branch: Option<&FunctionKernelValue<'db>>, - ) -> Result, Diagnostic> { - let result_arity = self.result_arity(&expr.abi)?; - match (result_arity, self.control_flow.result_lowering_mode(&expr.abi)) { - (StructuredWasmResultArity::Scalar(value_type), ResultLoweringMode::SpillToLocals) => { - let join = self.alloc_join_local(expr.source, value_type, "if"); - let then_region = self.stackify_join_region(then_branch, join, value_type)?; - let else_region = self.stackify_join_region(else_branch, join, value_type)?; - Ok(StructuredWasmExprKind::Block { - region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::If { - source: expr.source, - abi: AbiTy::Scalar(BackendTy::Unit), - ownership: ValueOwnership::None, - cond: self.stackify_expr(cond)?, - then_region, - else_region, - ownership_ops: Vec::new(), - }], - tail: Some(Box::new(StructuredWasmExpr { - source: expr.source, - abi: expr.abi.clone(), - ownership: expr.ownership, - kind: StructuredWasmExprKind::WasmLocal(join), - })), - }, - result_arity, - }) - } - _ => Ok(StructuredWasmExprKind::If { - cond: Box::new(self.stackify_expr(cond)?), - then_region: then_branch - .map(|branch| self.stackify_region_from_expr(branch)) - .transpose()? - .unwrap_or_default(), - else_region: else_branch - .map(|branch| self.stackify_region_from_expr(branch)) - .transpose()? - .unwrap_or_default(), - result_arity, - }), - } - } - - fn stackify_match_expr( - &mut self, - expr: &FunctionKernelValue<'db>, - scrutinee: &FunctionKernelValue<'db>, - arms: &[FunctionKernelMatchArm<'db>], - ) -> Result, Diagnostic> { - let result_arity = self.result_arity(&expr.abi)?; - match (result_arity, self.control_flow.result_lowering_mode(&expr.abi)) { - (StructuredWasmResultArity::Scalar(value_type), ResultLoweringMode::SpillToLocals) => { - let join = self.alloc_join_local(expr.source, value_type, "match"); - let arms = arms - .iter() - .map(|arm| { - Ok(StructuredWasmMatchArm { - pattern: arm.pattern.clone(), - body: self.stackify_join_region(Some(&arm.body), join, value_type)?, - }) - }) - .collect::, Diagnostic>>()?; - Ok(StructuredWasmExprKind::Block { - region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::Match { - source: expr.source, - abi: AbiTy::Scalar(BackendTy::Unit), - ownership: ValueOwnership::None, - scrutinee: self.stackify_expr(scrutinee)?, - arms, - fallback_unreachable: true, - ownership_ops: Vec::new(), - }], - tail: Some(Box::new(StructuredWasmExpr { - source: expr.source, - abi: expr.abi.clone(), - ownership: expr.ownership, - kind: StructuredWasmExprKind::WasmLocal(join), - })), - }, - result_arity, - }) - } - _ => Ok(StructuredWasmExprKind::Match { - scrutinee: Box::new(self.stackify_expr(scrutinee)?), - arms: arms - .iter() - .map(|arm| { - Ok(StructuredWasmMatchArm { - pattern: arm.pattern.clone(), - body: self.stackify_region_from_expr(&arm.body)?, - }) - }) - .collect::, Diagnostic>>()?, - result_arity, - fallback_unreachable: true, - }), - } - } - - fn result_arity(&self, abi: &AbiTy) -> Result { - StructuredWasmResultArity::for_abi(abi, self.control_flow.result_lowering_mode(abi)) - } - - fn stackify_join_region( - &mut self, - branch: Option<&FunctionKernelValue<'db>>, - local: u32, - ty: ValType, - ) -> Result, Diagnostic> { - let Some(branch) = branch else { - return Ok(StructuredWasmRegion::default()); - }; - Ok(StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::SetLocal { - source: branch.source, - local, - ty, - value: self.stackify_expr(branch)?, - }], - tail: None, - }) - } - - fn alloc_join_local(&mut self, source: ExprId, ty: ValType, label: &'static str) -> u32 { - let local = self.next_local; - self.next_local += 1; - self.extra_locals.push(StructuredWasmLocal { - index: local, - ty, - reason: StructuredWasmLocalReason::Join { source, label }, - }); - self.decisions.push(format!("join {:?} -> l{} ({label})", source, local)); - local - } -} - -impl StructuredWasmPeephole { - pub(in crate::backend) fn run<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - mut bundle: StructuredWasmBundle<'db>, - ) -> Result, Diagnostic> { - StructuredWasmValidator::validate(backend, plan, &bundle)?; - for function in &mut bundle.direct_functions { - peephole_function(function); - } - for function in &mut bundle.closures { - peephole_function(function); - } - StructuredWasmValidator::validate(backend, plan, &bundle)?; - Ok(bundle) - } -} - -impl StructuredWasmValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &StructuredWasmBundle<'db>, - ) -> Result<(), Diagnostic> { - let expected_direct = plan - .reachability - .functions - .iter() - .filter(|instance| { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - !matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - }) - .cloned() - .collect::>(); - - if bundle.direct_functions.len() != expected_direct.len() { - return Err(Diagnostic::error( - "internal error: structured Wasm direct body set drifted from ordinary \ - reachability", - backend.file_range(), - )); - } - if bundle.closures.len() != plan.reachability.closures.len() { - return Err(Diagnostic::error( - "internal error: structured Wasm closure body set drifted from reachability", - backend.file_range(), - )); - } - - for (index, function) in bundle.direct_functions.iter().enumerate() { - let expected_id = - FunctionKernelId(u32::try_from(index).expect("structured Wasm ids should fit u32")); - if function.id != expected_id { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm id drifted from direct ordering", - )); - } - match &function.kind { - FunctionKernelKind::Direct(instance) - if expected_direct.get(index) == Some(instance) => {} - _ => { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm direct ordering drifted from reachability", - )); - } - } - validate_structured_wasm_function(backend, function)?; - } - - let direct_count = u32::try_from(bundle.direct_functions.len()) - .expect("structured Wasm ids should fit u32"); - for (index, function) in bundle.closures.iter().enumerate() { - let expected_id = - FunctionKernelId(direct_count + u32::try_from(index).expect("fit u32")); - if function.id != expected_id { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm id drifted from closure ordering", - )); - } - match &function.kind { - FunctionKernelKind::Closure(closure) - if plan.reachability.closures.get(index) == Some(closure) => {} - _ => { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm closure ordering drifted from \ - reachability", - )); - } - } - validate_structured_wasm_function(backend, function)?; - } - - Ok(()) - } -} - -#[derive(Clone, Default)] -struct ValidationState { - initialized_names: FxHashSet, - initialized_locals: FxHashSet, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum EmitTermination { - Open, - Closed, -} - -fn validate_structured_wasm_function<'db>( - backend: &Backend<'db>, - function: &StructuredWasmFunction<'db>, -) -> Result<(), Diagnostic> { - for (expected_local, local) in - (function.layout.next_local_index()..).zip(function.extra_locals.iter()) - { - if local.index != expected_local { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm locals drifted from deterministic ordering", - )); - } - } - - let mut state = ValidationState::default(); - state.initialized_names.extend(function.layout.param_names.iter().copied()); - for init in &function.param_inits { - validate_binding_init(backend, function, &mut state, init)?; - } - - match &function.body { - Some(body) => { - validate_emit_expr(backend, function, &state, 0, body)?; - if body.abi != function.signature.result { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm body result drifted from signature", - )); - } - } - None if function.signature.result != AbiTy::Scalar(BackendTy::Unit) => { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm body is missing for non-unit function", - )); - } - None => {} - } - - Ok(()) -} - -fn validate_binding_init<'db>( - backend: &Backend<'db>, - function: &StructuredWasmFunction<'db>, - state: &mut ValidationState, - init: &StructuredWasmBindingInit<'db>, -) -> Result<(), Diagnostic> { - match &init.source { - StructuredWasmBindingSource::Expr(expr) => { - validate_emit_expr(backend, function, state, 0, expr)?; - } - StructuredWasmBindingSource::Param { index, abi, .. } => { - let expected = function.signature.params.get(*index).ok_or_else(|| { - structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm parameter binding referenced missing param", - ) - })?; - if expected != abi { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm parameter ABI drifted", - )); - } - } - } - bind_pattern_names_emit(backend, function, state, &init.pattern) -} - -fn validate_emit_region<'db>( - backend: &Backend<'db>, - function: &StructuredWasmFunction<'db>, - state: &ValidationState, - loop_depth: usize, - region: &StructuredWasmRegion<'db>, - expected_abi: Option<&AbiTy>, -) -> Result { - let mut state = state.clone(); - let mut terminated = false; - for stmt in ®ion.stmts { - if terminated { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm region contains statements after a terminator", - )); - } - terminated = validate_emit_stmt(backend, function, &mut state, loop_depth, stmt)? - == EmitTermination::Closed; - } - if terminated && region.tail.is_some() { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm region tail appears after a terminator", - )); - } - match (®ion.tail, expected_abi) { - (Some(tail), Some(expected)) => { - validate_emit_expr(backend, function, &state, loop_depth, tail)?; - if &tail.abi != expected { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm region tail ABI drifted", - )); - } - Ok(EmitTermination::Open) - } - (Some(tail), None) => validate_emit_expr(backend, function, &state, loop_depth, tail), - (None, Some(expected)) if *expected != AbiTy::Scalar(BackendTy::Unit) => { - Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm region is missing a non-unit tail", - )) - } - (None, _) => Ok(if terminated { EmitTermination::Closed } else { EmitTermination::Open }), - } -} - -fn validate_emit_stmt<'db>( - backend: &Backend<'db>, - function: &StructuredWasmFunction<'db>, - state: &mut ValidationState, - loop_depth: usize, - stmt: &StructuredWasmStmt<'db>, -) -> Result { - match stmt { - StructuredWasmStmt::Local { name, initializer, .. } => { - ensure_emit_binding_slot(function, *name) - .map_err(|message| structured_wasm_error(backend, &function.kind, message))?; - if let Some(initializer) = initializer { - validate_emit_expr(backend, function, state, loop_depth, initializer)?; - state.initialized_names.insert(*name); - } - Ok(EmitTermination::Open) - } - StructuredWasmStmt::Assign { name, value, .. } => { - ensure_emit_binding_slot(function, *name) - .map_err(|message| structured_wasm_error(backend, &function.kind, message))?; - validate_emit_expr(backend, function, state, loop_depth, value)?; - state.initialized_names.insert(*name); - Ok(EmitTermination::Open) - } - StructuredWasmStmt::Pattern(init) => { - validate_binding_init(backend, function, state, init)?; - Ok(EmitTermination::Open) - } - StructuredWasmStmt::If { abi, cond, then_region, else_region, .. } => { - validate_emit_expr(backend, function, state, loop_depth, cond)?; - let then_term = - validate_emit_region(backend, function, state, loop_depth, then_region, Some(abi))?; - let else_term = - validate_emit_region(backend, function, state, loop_depth, else_region, Some(abi))?; - let termination = - if then_term == EmitTermination::Closed && else_term == EmitTermination::Closed { - EmitTermination::Closed - } else { - EmitTermination::Open - }; - if termination == EmitTermination::Open { - state.initialized_locals.extend(definite_local_writes_stmt(stmt)); - } - Ok(termination) - } - StructuredWasmStmt::Match { scrutinee, arms, abi, fallback_unreachable, .. } => { - if !fallback_unreachable { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm statement match lost its explicit \ - unreachable fallback", - )); - } - validate_emit_expr(backend, function, state, loop_depth, scrutinee)?; - let mut open_arm_count = 0usize; - for arm in arms { - let mut arm_state = state.clone(); - bind_pattern_names_emit(backend, function, &mut arm_state, &arm.pattern)?; - let term = validate_emit_region( - backend, - function, - &arm_state, - loop_depth, - &arm.body, - Some(abi), - )?; - if term == EmitTermination::Open { - open_arm_count += 1; - } - } - if open_arm_count > 0 { - state.initialized_locals.extend(definite_local_writes_stmt(stmt)); - Ok(EmitTermination::Open) - } else { - Ok(EmitTermination::Closed) - } - } - StructuredWasmStmt::Return { value, .. } => { - match (value, &function.signature.result) { - (Some(expr), _) => { - validate_emit_expr(backend, function, state, loop_depth, expr)?; - if expr.abi != function.signature.result { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm return result drifted from signature", - )); - } - } - (None, AbiTy::Scalar(BackendTy::Unit)) => {} - (None, _) => { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm return is missing a value", - )); - } - } - Ok(EmitTermination::Closed) - } - StructuredWasmStmt::Expr { expr, .. } => { - let termination = validate_emit_expr(backend, function, state, loop_depth, expr)?; - if termination == EmitTermination::Open { - state.initialized_locals.extend(definite_local_writes_expr(expr)); - } - Ok(termination) - } - StructuredWasmStmt::SetLocal { local, ty, value, .. } => { - if !function - .extra_locals - .iter() - .any(|candidate| candidate.index == *local && candidate.ty == *ty) - { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm store referenced an unknown extra local", - )); - } - validate_emit_expr(backend, function, state, loop_depth, value)?; - state.initialized_locals.insert(*local); - Ok(EmitTermination::Open) - } - } -} - -fn validate_emit_expr<'db>( - backend: &Backend<'db>, - function: &StructuredWasmFunction<'db>, - state: &ValidationState, - loop_depth: usize, - expr: &StructuredWasmExpr<'db>, -) -> Result { - match &expr.kind { - StructuredWasmExprKind::Leaf(leaf) => { - validate_leaf_expr(function, state, leaf) - .map_err(|message| structured_wasm_error(backend, &function.kind, message))?; - Ok(EmitTermination::Open) - } - StructuredWasmExprKind::WasmLocal(local) => { - if !state.initialized_locals.contains(local) { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm used an extra local before definition", - )); - } - Ok(EmitTermination::Open) - } - StructuredWasmExprKind::Eqz { value } => { - validate_emit_expr(backend, function, state, loop_depth, value) - } - StructuredWasmExprKind::TeeLocal { local, value } => { - if !function.extra_locals.iter().any(|candidate| candidate.index == *local) { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm tee referenced an unknown extra local", - )); - } - validate_emit_expr(backend, function, state, loop_depth, value) - } - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - validate_emit_expr(backend, function, state, loop_depth, cond)?; - validate_emit_expr(backend, function, state, loop_depth, then_value)?; - validate_emit_expr(backend, function, state, loop_depth, else_value)?; - Ok(EmitTermination::Open) - } - StructuredWasmExprKind::Block { region, result_arity } => { - if *result_arity - != StructuredWasmResultArity::for_abi( - &expr.abi, - backend.control_flow_strategy().result_lowering_mode(&expr.abi), - )? - { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm block result arity drifted from its ABI", - )); - } - validate_emit_region(backend, function, state, loop_depth, region, Some(&expr.abi)) - } - StructuredWasmExprKind::If { cond, then_region, else_region, result_arity } => { - if *result_arity - != StructuredWasmResultArity::for_abi( - &expr.abi, - backend.control_flow_strategy().result_lowering_mode(&expr.abi), - )? - { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm if result arity drifted from its ABI", - )); - } - validate_emit_expr(backend, function, state, loop_depth, cond)?; - let then_term = validate_emit_region( - backend, - function, - state, - loop_depth, - then_region, - Some(&expr.abi), - )?; - let else_term = validate_emit_region( - backend, - function, - state, - loop_depth, - else_region, - Some(&expr.abi), - )?; - Ok(if then_term == EmitTermination::Closed && else_term == EmitTermination::Closed { - EmitTermination::Closed - } else { - EmitTermination::Open - }) - } - StructuredWasmExprKind::Match { scrutinee, arms, result_arity, fallback_unreachable } => { - if *result_arity - != StructuredWasmResultArity::for_abi( - &expr.abi, - backend.control_flow_strategy().result_lowering_mode(&expr.abi), - )? - { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm match result arity drifted from its ABI", - )); - } - if !fallback_unreachable { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm match lost its explicit unreachable fallback", - )); - } - validate_emit_expr(backend, function, state, loop_depth, scrutinee)?; - for arm in arms { - let mut arm_state = state.clone(); - bind_pattern_names_emit(backend, function, &mut arm_state, &arm.pattern)?; - validate_emit_region( - backend, - function, - &arm_state, - loop_depth, - &arm.body, - Some(&expr.abi), - )?; - } - Ok(EmitTermination::Open) - } - StructuredWasmExprKind::Loop { body, result_arity } => { - if *result_arity != StructuredWasmResultArity::Unit { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm loop result arity is unsupported", - )); - } - if let Some(body) = body { - validate_emit_expr(backend, function, state, loop_depth + 1, body)?; - } - Ok(EmitTermination::Open) - } - StructuredWasmExprKind::Break | StructuredWasmExprKind::Continue => { - if loop_depth == 0 { - return Err(structured_wasm_error( - backend, - &function.kind, - "internal error: structured Wasm loop control escaped loop nesting", - )); - } - Ok(EmitTermination::Closed) - } - StructuredWasmExprKind::Unreachable => Ok(EmitTermination::Closed), - } -} - -fn validate_leaf_expr( - function: &StructuredWasmFunction<'_>, - state: &ValidationState, - expr: &FunctionKernelValue<'_>, -) -> Result<(), String> { - match &expr.kind { - FunctionKernelValueKind::Local(name) => { - ensure_emit_binding_slot(function, *name)?; - if !state.initialized_names.contains(name) { - return Err("internal error: structured Wasm used a binding before initialization" - .to_owned()); - } - } - FunctionKernelValueKind::Clone { value } - | FunctionKernelValueKind::Prefix { expr: value, .. } - | FunctionKernelValueKind::AddrOffset { base: value, .. } - | FunctionKernelValueKind::MemoryRead { addr: value, .. } - | FunctionKernelValueKind::Field { base: value, .. } => { - validate_leaf_expr(function, state, value)? - } - FunctionKernelValueKind::MemoryWrite { addr, value } => { - validate_leaf_expr(function, state, addr)?; - validate_leaf_expr(function, state, value)?; - } - FunctionKernelValueKind::PointerAdd { ptr, count, .. } => { - validate_leaf_expr(function, state, ptr)?; - validate_leaf_expr(function, state, count)?; - } - FunctionKernelValueKind::StringFromBytes { ptr, len } => { - validate_leaf_expr(function, state, ptr)?; - validate_leaf_expr(function, state, len)?; - } - FunctionKernelValueKind::Array { items, .. } => { - for item in items { - validate_leaf_expr(function, state, item)?; - } - } - FunctionKernelValueKind::ArrayRepeat { value, len, .. } => { - validate_leaf_expr(function, state, value)?; - validate_leaf_expr(function, state, len)?; - } - FunctionKernelValueKind::Call { args, .. } - | FunctionKernelValueKind::VariantCall { args, .. } => { - for arg in args { - validate_leaf_expr(function, state, arg)?; - } - } - FunctionKernelValueKind::IndirectCall { callee, args, .. } => { - validate_leaf_expr(function, state, callee)?; - for arg in args { - validate_leaf_expr(function, state, arg)?; - } - } - FunctionKernelValueKind::Binary { lhs, rhs, .. } => { - validate_leaf_expr(function, state, lhs)?; - validate_leaf_expr(function, state, rhs)?; - } - FunctionKernelValueKind::Tuple { fields } | FunctionKernelValueKind::Struct { fields } => { - for field in fields { - validate_leaf_expr(function, state, &field.value)?; - } - } - FunctionKernelValueKind::Union { value, .. } => { - if let Some(value) = value { - validate_leaf_expr(function, state, value)?; - } - } - FunctionKernelValueKind::ClosureValue { env, .. } => { - for field in &env.fields { - validate_leaf_expr(function, state, &field.value)?; - } - } - FunctionKernelValueKind::Block { .. } - | FunctionKernelValueKind::If { .. } - | FunctionKernelValueKind::Match { .. } - | FunctionKernelValueKind::Loop { .. } - | FunctionKernelValueKind::Break - | FunctionKernelValueKind::Continue => { - return Err( - "internal error: structured Wasm leaf retained kernel control flow".to_owned() - ); - } - FunctionKernelValueKind::Capture(_) - | FunctionKernelValueKind::FunctionValue { .. } - | FunctionKernelValueKind::Bool(_) - | FunctionKernelValueKind::Int(_) - | FunctionKernelValueKind::Float(_) - | FunctionKernelValueKind::String(_) - | FunctionKernelValueKind::Char(_) - | FunctionKernelValueKind::Unit - | FunctionKernelValueKind::StackAddr { .. } - | FunctionKernelValueKind::VariantValue { .. } => {} - } - Ok(()) -} - -fn ensure_emit_binding_slot( - function: &StructuredWasmFunction<'_>, - name: NameId, -) -> Result<(), String> { - if function.layout.lookups.slots.contains_key(&name) - || function.layout.lookups.param_names.contains(&name) - { - Ok(()) - } else { - Err(format!( - "internal error: structured Wasm referenced binding `{}` without a planned storage \ - slot", - format_args!("{name:?}") - )) - } -} - -fn bind_pattern_names_emit( - backend: &Backend<'_>, - function: &StructuredWasmFunction<'_>, - state: &mut ValidationState, - pattern: &BackendPattern, -) -> Result<(), Diagnostic> { - let mut names = Vec::new(); - pattern.binding_names(&mut names); - for name in names { - ensure_emit_binding_slot(function, name) - .map_err(|message| structured_wasm_error(backend, &function.kind, message))?; - state.initialized_names.insert(name); - } - Ok(()) -} - -fn structured_wasm_error<'db>( - backend: &Backend<'db>, - kind: &FunctionKernelKind<'db>, - message: impl Into, -) -> Diagnostic { - let location = match kind { - FunctionKernelKind::Direct(instance) => instance.location, - FunctionKernelKind::Closure(closure) => closure.owner, - }; - backend.diagnostic_at_function(location, message.into(), backend.function_range(location)) -} - -fn peephole_function(function: &mut StructuredWasmFunction<'_>) { - for init in &mut function.param_inits { - if let StructuredWasmBindingSource::Expr(expr) = &mut init.source { - peephole_expr(expr); - } - } - if let Some(body) = &mut function.body { - peephole_expr(body); - } -} - -fn peephole_expr(expr: &mut StructuredWasmExpr<'_>) { - let expr_source = expr.source; - let expr_abi = expr.abi.clone(); - let expr_ownership = expr.ownership; - match &mut expr.kind { - StructuredWasmExprKind::Leaf(backend) => { - peephole_leaf_expr(backend); - if let Some(rewritten) = - rewrite_leaf_compare_to_zero(expr_source, expr_abi.clone(), expr_ownership, backend) - { - *expr = rewritten; - } - } - StructuredWasmExprKind::Eqz { value } => peephole_expr(value), - StructuredWasmExprKind::TeeLocal { value, .. } => peephole_expr(value), - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - peephole_expr(cond); - peephole_expr(then_value); - peephole_expr(else_value); - } - StructuredWasmExprKind::Block { region, .. } => { - peephole_region(region); - apply_block_peepholes(expr_source, expr_abi, expr_ownership, region); - } - StructuredWasmExprKind::If { cond, then_region, else_region, .. } => { - peephole_expr(cond); - peephole_region(then_region); - peephole_region(else_region); - if region_is_empty(then_region) && !region_is_empty(else_region) { - let inverted = StructuredWasmExpr { - source: cond.source, - abi: cond.abi.clone(), - ownership: cond.ownership, - kind: StructuredWasmExprKind::Eqz { value: cond.clone() }, - }; - **cond = inverted; - std::mem::swap(then_region, else_region); - } - } - StructuredWasmExprKind::Match { scrutinee, arms, .. } => { - peephole_expr(scrutinee); - for arm in arms { - peephole_region(&mut arm.body); - } - } - StructuredWasmExprKind::Loop { body, .. } => { - if let Some(body) = body { - peephole_expr(body); - } - } - StructuredWasmExprKind::WasmLocal(_) - | StructuredWasmExprKind::Break - | StructuredWasmExprKind::Continue - | StructuredWasmExprKind::Unreachable => {} - } -} - -fn peephole_region(region: &mut StructuredWasmRegion<'_>) { - for stmt in &mut region.stmts { - match stmt { - StructuredWasmStmt::Local { initializer, .. } => { - if let Some(initializer) = initializer { - peephole_expr(initializer); - } - } - StructuredWasmStmt::Assign { value, .. } => peephole_expr(value), - StructuredWasmStmt::If { cond, then_region, else_region, .. } => { - peephole_expr(cond); - peephole_region(then_region); - peephole_region(else_region); - if region_is_empty(then_region) && !region_is_empty(else_region) { - let inverted = StructuredWasmExpr { - source: cond.source, - abi: cond.abi.clone(), - ownership: cond.ownership, - kind: StructuredWasmExprKind::Eqz { value: Box::new(cond.clone()) }, - }; - *cond = inverted; - std::mem::swap(then_region, else_region); - } - } - StructuredWasmStmt::Match { scrutinee, arms, .. } => { - peephole_expr(scrutinee); - for arm in arms { - peephole_region(&mut arm.body); - } - } - StructuredWasmStmt::Pattern(init) => { - if let StructuredWasmBindingSource::Expr(expr) = &mut init.source { - peephole_expr(expr); - } - } - StructuredWasmStmt::Return { value, .. } => { - if let Some(value) = value { - peephole_expr(value); - } - } - StructuredWasmStmt::Expr { expr, .. } => peephole_expr(expr), - StructuredWasmStmt::SetLocal { value, .. } => peephole_expr(value), - } - } - if let Some(tail) = &mut region.tail { - peephole_expr(tail); - } -} - -fn apply_block_peepholes( - source: ExprId, - abi: AbiTy, - ownership: ValueOwnership, - region: &mut StructuredWasmRegion<'_>, -) { - if let Some(StructuredWasmExpr { kind: StructuredWasmExprKind::WasmLocal(local), .. }) = - region.tail.as_deref() - { - if let Some(StructuredWasmStmt::SetLocal { local: set_local, value, .. }) = - region.stmts.last() - && *set_local == *local - && matches!(value.kind, StructuredWasmExprKind::WasmLocal(_)) - { - region.tail = Some(Box::new(value.clone())); - region.stmts.pop(); - return; - } - if let Some(StructuredWasmStmt::SetLocal { local: set_local, value, .. }) = - region.stmts.last() - && *set_local == *local - { - region.tail = Some(Box::new(StructuredWasmExpr { - source: value.source, - abi: value.abi.clone(), - ownership: value.ownership, - kind: StructuredWasmExprKind::TeeLocal { - local: *local, - value: Box::new(value.clone()), - }, - })); - region.stmts.pop(); - return; - } - } - - let Some(StructuredWasmExpr { kind: StructuredWasmExprKind::WasmLocal(local), .. }) = - region.tail.as_deref() - else { - return; - }; - let Some(if_expr) = region.stmts.last().and_then(|stmt| match stmt { - StructuredWasmStmt::If { - source, abi, ownership, cond, then_region, else_region, .. - } => Some(StructuredWasmExpr { - source: *source, - abi: abi.clone(), - ownership: *ownership, - kind: StructuredWasmExprKind::If { - cond: Box::new(cond.clone()), - then_region: then_region.clone(), - else_region: else_region.clone(), - result_arity: StructuredWasmResultArity::Unit, - }, - }), - StructuredWasmStmt::Expr { expr, .. } => Some(expr.clone()), - _ => None, - }) else { - return; - }; - let StructuredWasmExprKind::If { cond, then_region, else_region, result_arity } = &if_expr.kind - else { - return; - }; - if *result_arity != StructuredWasmResultArity::Unit { - return; - } - let Some(then_value) = single_region_set_local(then_region, *local) else { - return; - }; - let Some(else_value) = single_region_set_local(else_region, *local) else { - return; - }; - if !is_pure_scalar_expr(cond) - || !is_pure_scalar_expr(&then_value) - || !is_pure_scalar_expr(&else_value) - { - return; - } - region.stmts.pop(); - region.tail = Some(Box::new(StructuredWasmExpr { - source, - abi, - ownership, - kind: StructuredWasmExprKind::Select { - cond: cond.clone(), - then_value: Box::new(then_value), - else_value: Box::new(else_value), - }, - })); -} - -fn single_region_set_local<'db>( - region: &StructuredWasmRegion<'db>, - local: u32, -) -> Option> { - if region.tail.is_some() || region.stmts.len() != 1 { - return None; - } - match region.stmts.first()? { - StructuredWasmStmt::SetLocal { local: set_local, value, .. } if *set_local == local => { - Some(value.clone()) - } - _ => None, - } -} - -fn region_is_empty(region: &StructuredWasmRegion<'_>) -> bool { - region.stmts.is_empty() && region.tail.is_none() -} - -fn is_pure_scalar_expr(expr: &StructuredWasmExpr<'_>) -> bool { - match &expr.kind { - StructuredWasmExprKind::WasmLocal(_) | StructuredWasmExprKind::Eqz { .. } => true, - StructuredWasmExprKind::Leaf(backend) => is_pure_kernel_scalar_expr(backend), - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - is_pure_scalar_expr(cond) - && is_pure_scalar_expr(then_value) - && is_pure_scalar_expr(else_value) - } - _ => false, - } -} - -fn is_pure_kernel_scalar_expr(expr: &FunctionKernelValue<'_>) -> bool { - match &expr.kind { - FunctionKernelValueKind::Local(_) - | FunctionKernelValueKind::Bool(_) - | FunctionKernelValueKind::Int(_) - | FunctionKernelValueKind::Float(_) - | FunctionKernelValueKind::String(_) - | FunctionKernelValueKind::Char(_) - | FunctionKernelValueKind::Unit => true, - FunctionKernelValueKind::Binary { lhs, rhs, .. } => { - is_pure_kernel_scalar_expr(lhs) && is_pure_kernel_scalar_expr(rhs) - } - FunctionKernelValueKind::Prefix { expr, .. } => is_pure_kernel_scalar_expr(expr), - _ => false, - } -} - -fn rewrite_leaf_compare_to_zero<'db>( - source: ExprId, - abi: AbiTy, - ownership: ValueOwnership, - backend: &FunctionKernelValue<'db>, -) -> Option> { - let FunctionKernelValueKind::Binary { op: BackendBinaryOp::Eq, lhs, rhs } = &backend.kind - else { - return None; - }; - let value = if is_zero_literal(lhs) { - rhs.as_ref() - } else if is_zero_literal(rhs) { - lhs.as_ref() - } else { - return None; - }; - match value.abi { - AbiTy::Scalar(BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_)) - | AbiTy::Scalar(BackendTy::I64) => Some(StructuredWasmExpr { - source, - abi, - ownership, - kind: StructuredWasmExprKind::Eqz { - value: Box::new(StructuredWasmExpr { - source: value.source, - abi: value.abi.clone(), - ownership: value.ownership, - kind: StructuredWasmExprKind::Leaf(value.clone()), - }), - }, - }), - _ => None, - } -} - -fn is_zero_literal(expr: &FunctionKernelValue<'_>) -> bool { - matches!( - (&expr.abi, &expr.kind), - ( - AbiTy::Scalar(BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_)), - FunctionKernelValueKind::Int(0) - ) | (AbiTy::Scalar(BackendTy::I64), FunctionKernelValueKind::Int(0)) - ) -} - -fn definite_local_writes_expr(expr: &StructuredWasmExpr<'_>) -> FxHashSet { - let (falls_through, locals) = fallthrough_local_writes_expr(expr); - if falls_through { locals } else { FxHashSet::default() } -} - -fn definite_local_writes_stmt(stmt: &StructuredWasmStmt<'_>) -> FxHashSet { - let (falls_through, locals) = fallthrough_local_writes_stmt(stmt); - if falls_through { locals } else { FxHashSet::default() } -} - -fn fallthrough_local_writes_expr(expr: &StructuredWasmExpr<'_>) -> (bool, FxHashSet) { - match &expr.kind { - StructuredWasmExprKind::Leaf(_) - | StructuredWasmExprKind::WasmLocal(_) - | StructuredWasmExprKind::Eqz { .. } - | StructuredWasmExprKind::Select { .. } => (true, FxHashSet::default()), - StructuredWasmExprKind::TeeLocal { local, .. } => { - let mut locals = FxHashSet::default(); - locals.insert(*local); - (true, locals) - } - StructuredWasmExprKind::Block { region, .. } => fallthrough_local_writes_region(region), - StructuredWasmExprKind::If { then_region, else_region, .. } => { - let (then_open, then_writes) = fallthrough_local_writes_region(then_region); - let (else_open, else_writes) = fallthrough_local_writes_region(else_region); - match (then_open, else_open) { - (true, true) => (true, intersect_local_writes(&then_writes, &else_writes)), - (true, false) => (true, then_writes), - (false, true) => (true, else_writes), - (false, false) => (false, FxHashSet::default()), - } - } - StructuredWasmExprKind::Match { arms, fallback_unreachable, .. } => { - let mut open_writes = Vec::new(); - for arm in arms { - let (open, writes) = fallthrough_local_writes_region(&arm.body); - if open { - open_writes.push(writes); - } - } - if open_writes.is_empty() { - if *fallback_unreachable { - (false, FxHashSet::default()) - } else { - (true, FxHashSet::default()) - } - } else { - let mut iter = open_writes.into_iter(); - let first = iter.next().expect("non-empty writes"); - let merged = iter.fold(first, |acc, writes| intersect_local_writes(&acc, &writes)); - (true, merged) - } - } - StructuredWasmExprKind::Loop { .. } => (true, FxHashSet::default()), - StructuredWasmExprKind::Break - | StructuredWasmExprKind::Continue - | StructuredWasmExprKind::Unreachable => (false, FxHashSet::default()), - } -} - -fn fallthrough_local_writes_region(region: &StructuredWasmRegion<'_>) -> (bool, FxHashSet) { - let mut locals = FxHashSet::default(); - for stmt in ®ion.stmts { - let (open, writes) = fallthrough_local_writes_stmt(stmt); - if open { - locals.extend(writes); - } else { - return (false, FxHashSet::default()); - } - } - if let Some(tail) = region.tail.as_deref() { - let (open, writes) = fallthrough_local_writes_expr(tail); - if open { - locals.extend(writes); - (true, locals) - } else { - (false, FxHashSet::default()) - } - } else { - (true, locals) - } -} - -fn fallthrough_local_writes_stmt(stmt: &StructuredWasmStmt<'_>) -> (bool, FxHashSet) { - match stmt { - StructuredWasmStmt::Return { .. } => (false, FxHashSet::default()), - StructuredWasmStmt::SetLocal { local, .. } => { - let mut locals = FxHashSet::default(); - locals.insert(*local); - (true, locals) - } - StructuredWasmStmt::If { then_region, else_region, .. } => { - let (then_open, then_writes) = fallthrough_local_writes_region(then_region); - let (else_open, else_writes) = fallthrough_local_writes_region(else_region); - match (then_open, else_open) { - (true, true) => (true, intersect_local_writes(&then_writes, &else_writes)), - (true, false) => (true, then_writes), - (false, true) => (true, else_writes), - (false, false) => (false, FxHashSet::default()), - } - } - StructuredWasmStmt::Match { arms, fallback_unreachable, .. } => { - let mut open_writes = Vec::new(); - for arm in arms { - let (open, writes) = fallthrough_local_writes_region(&arm.body); - if open { - open_writes.push(writes); - } - } - if open_writes.is_empty() { - if *fallback_unreachable { - (false, FxHashSet::default()) - } else { - (true, FxHashSet::default()) - } - } else { - let mut iter = open_writes.into_iter(); - let first = iter.next().expect("non-empty writes"); - let merged = iter.fold(first, |acc, writes| intersect_local_writes(&acc, &writes)); - (true, merged) - } - } - StructuredWasmStmt::Expr { expr, .. } => fallthrough_local_writes_expr(expr), - StructuredWasmStmt::Local { .. } - | StructuredWasmStmt::Assign { .. } - | StructuredWasmStmt::Pattern(_) => (true, FxHashSet::default()), - } -} - -fn intersect_local_writes(lhs: &FxHashSet, rhs: &FxHashSet) -> FxHashSet { - lhs.intersection(rhs).copied().collect() -} - -fn peephole_leaf_expr(expr: &mut FunctionKernelValue<'_>) { - match &mut expr.kind { - FunctionKernelValueKind::Clone { value } - | FunctionKernelValueKind::Prefix { expr: value, .. } - | FunctionKernelValueKind::AddrOffset { base: value, .. } - | FunctionKernelValueKind::MemoryRead { addr: value, .. } - | FunctionKernelValueKind::Field { base: value, .. } => peephole_leaf_expr(value), - FunctionKernelValueKind::MemoryWrite { addr, value } => { - peephole_leaf_expr(addr); - peephole_leaf_expr(value); - } - FunctionKernelValueKind::PointerAdd { ptr, count, .. } => { - peephole_leaf_expr(ptr); - peephole_leaf_expr(count); - } - FunctionKernelValueKind::StringFromBytes { ptr, len } => { - peephole_leaf_expr(ptr); - peephole_leaf_expr(len); - } - FunctionKernelValueKind::Array { items, .. } => { - for item in items { - peephole_leaf_expr(item); - } - } - FunctionKernelValueKind::ArrayRepeat { value, len, .. } => { - peephole_leaf_expr(value); - peephole_leaf_expr(len); - } - FunctionKernelValueKind::Block { stmts, tail } => { - for stmt in stmts { - peephole_leaf_stmt(stmt); - } - if let Some(tail) = tail { - peephole_leaf_expr(tail); - } - } - FunctionKernelValueKind::Call { args, .. } - | FunctionKernelValueKind::VariantCall { args, .. } => { - for arg in args { - peephole_leaf_expr(arg); - } - } - FunctionKernelValueKind::IndirectCall { callee, args, .. } => { - peephole_leaf_expr(callee); - for arg in args { - peephole_leaf_expr(arg); - } - } - FunctionKernelValueKind::Binary { lhs, rhs, .. } => { - peephole_leaf_expr(lhs); - peephole_leaf_expr(rhs); - } - FunctionKernelValueKind::If { cond, then_branch, else_branch } => { - peephole_leaf_expr(cond); - if let Some(then_branch) = then_branch { - peephole_leaf_expr(then_branch); - } - if let Some(else_branch) = else_branch { - peephole_leaf_expr(else_branch); - } - } - FunctionKernelValueKind::Match { scrutinee, arms } => { - peephole_leaf_expr(scrutinee); - for arm in arms { - peephole_leaf_expr(&mut arm.body); - } - } - FunctionKernelValueKind::Loop { body } => { - if let Some(body) = body { - peephole_leaf_expr(body); - } - } - FunctionKernelValueKind::Tuple { fields } | FunctionKernelValueKind::Struct { fields } => { - for field in fields { - peephole_leaf_expr(&mut field.value); - } - } - FunctionKernelValueKind::Union { value, .. } => { - if let Some(value) = value { - peephole_leaf_expr(value); - } - } - FunctionKernelValueKind::ClosureValue { env, .. } => { - for field in &mut env.fields { - peephole_leaf_expr(&mut field.value); - } - } - FunctionKernelValueKind::Local(_) - | FunctionKernelValueKind::Capture(_) - | FunctionKernelValueKind::FunctionValue { .. } - | FunctionKernelValueKind::Bool(_) - | FunctionKernelValueKind::Int(_) - | FunctionKernelValueKind::Float(_) - | FunctionKernelValueKind::String(_) - | FunctionKernelValueKind::Char(_) - | FunctionKernelValueKind::Unit - | FunctionKernelValueKind::StackAddr { .. } - | FunctionKernelValueKind::Break - | FunctionKernelValueKind::Continue - | FunctionKernelValueKind::VariantValue { .. } => {} - } - - fold_leaf_mem_offset(expr); -} - -fn peephole_leaf_stmt(stmt: &mut FunctionKernelStmt<'_>) { - match stmt { - FunctionKernelStmt::Local { initializer, .. } => { - if let Some(initializer) = initializer { - peephole_leaf_expr(initializer); - } - } - FunctionKernelStmt::Assign { value, .. } => peephole_leaf_expr(value), - FunctionKernelStmt::Pattern(init) => { - if let FunctionKernelBindingSource::Value(expr) = &mut init.source { - peephole_leaf_expr(expr); - } - } - FunctionKernelStmt::Return { value, .. } => { - if let Some(value) = value { - peephole_leaf_expr(value); - } - } - FunctionKernelStmt::Expr(expr) => peephole_leaf_expr(expr), - FunctionKernelStmt::Retain { value, .. } - | FunctionKernelStmt::Release { value, .. } - | FunctionKernelStmt::Destroy { value, .. } - | FunctionKernelStmt::Copy { value, .. } - | FunctionKernelStmt::Move { value, .. } => peephole_leaf_expr(value), - } -} - -fn fold_leaf_mem_offset(expr: &mut FunctionKernelValue<'_>) { - let FunctionKernelValueKind::MemoryRead { addr, access } = &mut expr.kind else { - return; - }; - let Some(existing) = access.take() else { - return; - }; - let FunctionKernelValueKind::AddrOffset { base, offset } = &mut addr.kind else { - *access = Some(existing); - return; - }; - let Some(total) = existing.offset.checked_add(*offset) else { - *access = Some(existing); - return; - }; - let folded = MemAccess { offset: total, ..existing }; - let replacement = (**base).clone(); - **addr = replacement; - *access = Some(folded); -} - -#[cfg(test)] -impl<'db> StructuredWasmBundle<'db> { - pub(crate) fn dump(&self, _db: &dyn salsa::Database) -> String { - use std::fmt::Write as _; - - let mut output = String::new(); - writeln!(&mut output, "structured_wasm.direct:").expect("write string"); - for function in &self.direct_functions { - dump_structured_wasm_function(&mut output, function); - } - writeln!(&mut output, "structured_wasm.closures:").expect("write string"); - for function in &self.closures { - dump_structured_wasm_function(&mut output, function); - } - output - } -} - -#[cfg(test)] -fn dump_structured_wasm_function(output: &mut String, function: &StructuredWasmFunction<'_>) { - use std::fmt::Write as _; - - writeln!( - output, - " - [m{}] {} :: ({}) -> {}", - function.id.0, - function.debug_name, - function.signature.params.iter().map(format_abi_ty).collect::>().join(", "), - format_abi_ty(&function.signature.result) - ) - .expect("write string"); - writeln!(output, " storage: {}", function.layout.dump_storage().replace('\n', " | ")) - .expect("write string"); - if function.extra_locals.is_empty() { - writeln!(output, " extra_locals: _").expect("write string"); - } else { - let locals = function - .extra_locals - .iter() - .map(|local| { - let reason = match &local.reason { - StructuredWasmLocalReason::Join { source, label } => { - format!("join({source:?},{label})") - } - }; - format!("l{}:{:?}:{reason}", local.index, local.ty) - }) - .collect::>() - .join(", "); - writeln!(output, " extra_locals: {locals}").expect("write string"); - } - if function.decisions.is_empty() { - writeln!(output, " decisions: _").expect("write string"); - } else { - writeln!(output, " decisions: {}", function.decisions.join(" | ")) - .expect("write string"); - } - writeln!(output, " body: {}", dump_emit_expr_summary(&function.body)).expect("write string"); -} - -#[cfg(test)] -fn dump_emit_expr_summary(expr: &Option>) -> String { - fn dump_region(region: &StructuredWasmRegion<'_>) -> String { - let stmts = region.stmts.iter().map(dump_stmt).collect::>().join("; "); - let tail = region - .tail - .as_ref() - .map_or_else(String::new, |tail| format!("; tail {}", dump_expr(tail))); - format!("{{{stmts}{tail}}}") - } - - fn dump_stmt(stmt: &StructuredWasmStmt<'_>) -> String { - match stmt { - StructuredWasmStmt::Local { name, initializer, .. } => { - initializer.as_ref().map_or_else( - || format!("local {name:?}"), - |expr| format!("local {name:?} = {}", dump_expr(expr)), - ) - } - StructuredWasmStmt::Assign { name, value, .. } => { - format!("assign {name:?} = {}", dump_expr(value)) - } - StructuredWasmStmt::If { cond, then_region, else_region, .. } => format!( - "if({}, {}, {})", - dump_expr(cond), - dump_region(then_region), - dump_region(else_region) - ), - StructuredWasmStmt::Match { scrutinee, arms, .. } => format!( - "match({}, [{}])", - dump_expr(scrutinee), - arms.iter().map(|arm| dump_region(&arm.body)).collect::>().join(", ") - ), - StructuredWasmStmt::Pattern(init) => match &init.source { - StructuredWasmBindingSource::Expr(expr) => { - format!("pattern = {}", dump_expr(expr)) - } - StructuredWasmBindingSource::Param { index, .. } => { - format!("pattern = param[{index}]") - } - }, - StructuredWasmStmt::Return { value, .. } => value - .as_ref() - .map_or_else(|| "return".to_owned(), |expr| format!("return {}", dump_expr(expr))), - StructuredWasmStmt::Expr { expr, .. } => dump_expr(expr), - StructuredWasmStmt::SetLocal { local, value, .. } => { - format!("set l{local} = {}", dump_expr(value)) - } - } - } - - fn dump_expr(expr: &StructuredWasmExpr<'_>) -> String { - match &expr.kind { - StructuredWasmExprKind::Leaf(backend) => format!("leaf({backend:?})"), - StructuredWasmExprKind::WasmLocal(local) => format!("local.get({local})"), - StructuredWasmExprKind::Eqz { value } => format!("eqz({})", dump_expr(value)), - StructuredWasmExprKind::TeeLocal { local, value } => { - format!("tee(l{local}, {})", dump_expr(value)) - } - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - format!( - "select({}, {}, {})", - dump_expr(cond), - dump_expr(then_value), - dump_expr(else_value) - ) - } - StructuredWasmExprKind::Block { region, .. } => format!("block{}", dump_region(region)), - StructuredWasmExprKind::If { cond, then_region, else_region, .. } => format!( - "if({}, {}, {})", - dump_expr(cond), - dump_region(then_region), - dump_region(else_region) - ), - StructuredWasmExprKind::Match { scrutinee, arms, .. } => format!( - "match({}, [{}])", - dump_expr(scrutinee), - arms.iter().map(|arm| dump_region(&arm.body)).collect::>().join(", ") - ), - StructuredWasmExprKind::Loop { body, .. } => { - format!( - "loop({})", - body.as_ref().map_or_else(|| "_".to_owned(), |expr| dump_expr(expr)) - ) - } - StructuredWasmExprKind::Break => "break".to_owned(), - StructuredWasmExprKind::Continue => "continue".to_owned(), - StructuredWasmExprKind::Unreachable => "unreachable".to_owned(), - } - } - - expr.as_ref().map_or_else(|| "_".to_owned(), dump_expr) -} - -#[cfg(test)] -fn format_abi_ty(abi: &AbiTy) -> String { - match abi { - AbiTy::Scalar(BackendTy::Int) => "i32".to_owned(), - AbiTy::Scalar(BackendTy::I64) => "i64".to_owned(), - AbiTy::Scalar(BackendTy::Bool) => "bool".to_owned(), - AbiTy::Scalar(BackendTy::Float) => "f64".to_owned(), - AbiTy::Scalar(BackendTy::Char) => "char".to_owned(), - AbiTy::Scalar(BackendTy::Ref(kind)) => format!("ref({kind:?})"), - AbiTy::Scalar(BackendTy::Unit) => "unit".to_owned(), - AbiTy::Aggregate(layout) => { - format!("aggregate(size={}, align={})", layout.size, layout.align) - } - } -} - -#[cfg(test)] -mod tests { - use expect_test::expect; - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "structured_wasm_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!( - diagnostics.is_empty(), - "unexpected diagnostics: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - backend - } - - #[test] - fn structured_wasm_dump_tracks_stackify_and_peephole_output() { - let backend = compiler_for_fixture( - r#" -enum Score { - Good(int), - Bad(int), -} - -fun choose(flag: bool, value: Score): int { - if flag { - match value { - .Good(v) => v, - .Bad(v) => if v == 41 { 42 } else { v }, - } - } else { - 0 - } -} - -export fun main(): int { - choose(true, Score.Bad(41)) -} -"#, - ); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - let pre = plan - .function_kernel - .as_ref() - .expect("expected function kernel bundle") - .dump(backend.db); - let stackified = FunctionStackifier::build( - &backend, - &plan, - plan.function_kernel.as_ref().expect("expected function kernel bundle"), - ) - .unwrap_or_else(|diagnostic| panic!("stackify should succeed: {}", diagnostic.message())) - .dump(backend.db); - let post = plan - .function_wasm_ir - .as_ref() - .expect("expected structured wasm bundle") - .lowered_bundle() - .dump(backend.db); - - expect!["function_kernel.direct:"] - .assert_eq(&pre.lines().take(1).collect::>().join("\n")); - assert!(stackified.contains("structured_wasm.direct:")); - assert!(stackified.contains("extra_locals:")); - assert!(post.contains("decisions:")); - assert!(post.contains("body: block")); - } - - #[test] - fn structured_wasm_dump_is_deterministic_across_repeated_planning() { - let backend = compiler_for_fixture( - r#" -fun choose(flag: bool): int { - if flag { 42 } else { 0 } -} - -export fun main(): int { - choose(true) -} -"#, - ); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .function_wasm_ir - .as_ref() - .expect("expected structured wasm bundle") - .lowered_bundle() - .dump(backend.db); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .function_wasm_ir - .as_ref() - .expect("expected structured wasm bundle") - .lowered_bundle() - .dump(backend.db); - assert_eq!(first, second); - } - - #[test] - fn peephole_rewrites_eqz_local_tee_select_and_mem_offsets() { - let mut eqz = StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Binary { - op: BackendBinaryOp::Eq, - lhs: Box::new(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Local(NameId::ZERO), - }), - rhs: Box::new(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Int(0), - }), - }, - }), - }; - peephole_expr(&mut eqz); - assert!(matches!(eqz.kind, StructuredWasmExprKind::Eqz { .. })); - - let mut tee_block = StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Block { - region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::SetLocal { - source: ExprId::ZERO, - local: 5, - ty: ValType::I32, - value: StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Int(42), - }), - }, - }], - tail: Some(Box::new(StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::WasmLocal(5), - })), - }, - result_arity: StructuredWasmResultArity::Scalar(ValType::I32), - }, - }; - peephole_expr(&mut tee_block); - assert!(dump_emit_expr_summary(&Some(tee_block)).contains("tee(")); - - let mut select_block = StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Block { - region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::Expr { - expr: StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Unit), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::If { - cond: Box::new(StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Bool), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Local(NameId::ZERO), - }), - }), - then_region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::SetLocal { - source: ExprId::ZERO, - local: 6, - ty: ValType::I32, - value: StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf( - FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Int(1), - }, - ), - }, - }], - tail: None, - }, - else_region: StructuredWasmRegion { - stmts: vec![StructuredWasmStmt::SetLocal { - source: ExprId::ZERO, - local: 6, - ty: ValType::I32, - value: StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf( - FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Int(0), - }, - ), - }, - }], - tail: None, - }, - result_arity: StructuredWasmResultArity::Unit, - }, - }, - ownership_ops: Vec::new(), - }], - tail: Some(Box::new(StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::WasmLocal(6), - })), - }, - result_arity: StructuredWasmResultArity::Scalar(ValType::I32), - }, - }; - peephole_expr(&mut select_block); - assert!(dump_emit_expr_summary(&Some(select_block)).contains("select(")); - - let mut mem = StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::MemoryRead { - addr: Box::new(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::AddrOffset { - base: Box::new(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Local(NameId::ZERO), - }), - offset: 4, - }, - }), - access: Some(MemAccess::i32(8, 2)), - }, - }), - }; - peephole_expr(&mut mem); - let StructuredWasmExprKind::Leaf(FunctionKernelValue { - kind: FunctionKernelValueKind::MemoryRead { addr, access: Some(access) }, - .. - }) = mem.kind - else { - panic!("expected folded memory read"); - }; - assert_eq!(access.offset, 12); - assert!(matches!(addr.kind, FunctionKernelValueKind::Local(_))); - } - - #[test] - fn structured_wasm_validator_rejects_control_flow_leaf() { - let backend = compiler_for_fixture( - r#" -fun choose(flag: bool): int { - if flag { 42 } else { 0 } -} - -export fun main(): int { - choose(true) -} -"#, - ); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - let mut bundle = plan - .function_wasm_ir - .as_ref() - .expect("expected structured wasm bundle") - .lowered_bundle(); - let function = bundle - .direct_functions - .first_mut() - .expect("expected one direct structured wasm function"); - function.body = Some(StructuredWasmExpr { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: StructuredWasmExprKind::Leaf(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Block { - stmts: Vec::new(), - tail: Some(Box::new(FunctionKernelValue { - source: ExprId::ZERO, - abi: AbiTy::Scalar(BackendTy::Int), - ownership: ValueOwnership::None, - kind: FunctionKernelValueKind::Int(42), - })), - }, - }), - }); - - let diagnostic = StructuredWasmValidator::validate(&backend, &plan, &bundle) - .expect_err("structured Wasm validator should reject control-flow leaves"); - assert!(diagnostic.message().contains("structured Wasm leaf retained kernel control flow")); - } -} diff --git a/crates/mitki-backend-wasm/src/lowering/function_kernel.rs b/crates/mitki-backend-wasm/src/lowering/function_kernel.rs deleted file mode 100644 index aef2fcf..0000000 --- a/crates/mitki-backend-wasm/src/lowering/function_kernel.rs +++ /dev/null @@ -1,669 +0,0 @@ -#![allow(dead_code)] - -#[cfg(test)] -use std::sync::Arc; - -use mitki_errors::Diagnostic; -use rustc_hash::FxHashMap; - -use super::plan::ModulePlan; -use super::*; -use crate::layout::VariantLayout; - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelBundle<'db> { - pub(in crate::backend) direct_functions: Vec>, - pub(in crate::backend) closures: Vec>, - pub(in crate::backend) direct_indices: FxHashMap, usize>, - pub(in crate::backend) closure_indices: FxHashMap, usize>, -} - -impl<'db> FunctionKernelBundle<'db> { - #[cfg(test)] - pub(in crate::backend) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut out = String::new(); - out.push_str("function_kernel.direct:\n"); - for function in &self.direct_functions { - function.dump_into(db, &mut out); - } - out.push_str("function_kernel.closures:\n"); - for function in &self.closures { - function.dump_into(db, &mut out); - } - out - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct FunctionKernelId(pub(in crate::backend) u32); - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelFunction<'db> { - pub(in crate::backend) id: FunctionKernelId, - pub(in crate::backend) kind: FunctionKernelKind<'db>, - pub(in crate::backend) debug_name: String, - pub(in crate::backend) signature: FunctionSignature, - pub(in crate::backend) layout: FunctionLayout, - pub(in crate::backend) legalization: Option>, - pub(in crate::backend) param_inits: Vec>, - pub(in crate::backend) entry: FunctionKernelBlockId, - pub(in crate::backend) blocks: Vec>, - pub(in crate::backend) decisions: Vec, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelLowered<'db> { - pub(in crate::backend) param_inits: Vec>, - pub(in crate::backend) body: Option>, -} - -impl<'db> FunctionKernelFunction<'db> { - pub(in crate::backend) fn entry_block(&self) -> &FunctionKernelBlock<'db> { - &self.blocks[self.entry.0 as usize] - } - - #[cfg(test)] - fn dump_into(&self, db: &dyn salsa::Database, out: &mut String) { - use std::fmt::Write as _; - - writeln!(out, " {}:", self.debug_name).expect("write string"); - writeln!(out, " id: k{}", self.id.0).expect("write string"); - writeln!(out, " source: {:?}", self.kind).expect("write string"); - writeln!(out, " decisions: {:?}", self.decisions).expect("write string"); - for block in &self.blocks { - writeln!(out, " block b{}: {}", block.id.0, describe_abi(db, &block.result_abi)) - .expect("write string"); - for stmt in &block.stmts { - writeln!(out, " stmt: {}", dump_stmt(db, stmt)).expect("write string"); - } - writeln!(out, " term: {}", dump_terminator(db, &block.terminator)) - .expect("write string"); - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum FunctionKernelKind<'db> { - Direct(InstanceKey<'db>), - Closure(ClosureInstanceKey<'db>), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct FunctionKernelBlockId(pub(in crate::backend) u32); - -#[derive(Clone, Debug)] -#[cfg_attr(not(test), allow(dead_code))] -pub(in crate::backend) struct FunctionKernelBlock<'db> { - pub(in crate::backend) id: FunctionKernelBlockId, - pub(in crate::backend) source: ExprId, - pub(in crate::backend) result_abi: AbiTy, - pub(in crate::backend) stmts: Vec>, - pub(in crate::backend) terminator: FunctionKernelTerminator<'db>, -} - -#[derive(Clone, Debug)] -#[cfg_attr(not(test), allow(dead_code))] -pub(in crate::backend) enum FunctionKernelStmt<'db> { - Local { name: NameId, abi: AbiTy, initializer: Option> }, - Assign { name: NameId, abi: AbiTy, value: FunctionKernelValue<'db> }, - Pattern(FunctionKernelBindingInit<'db>), - Expr(FunctionKernelValue<'db>), - Return { source: ExprId, value: Option> }, - Retain { source: ExprId, abi: AbiTy, value: FunctionKernelValue<'db> }, - Release { source: ExprId, abi: AbiTy, value: FunctionKernelValue<'db> }, - Destroy { source: ExprId, abi: AbiTy, value: FunctionKernelValue<'db> }, - Copy { source: ExprId, abi: AbiTy, value: FunctionKernelValue<'db> }, - Move { source: ExprId, abi: AbiTy, value: FunctionKernelValue<'db> }, -} - -#[derive(Clone, Debug)] -#[cfg_attr(not(test), allow(dead_code))] -pub(in crate::backend) enum FunctionKernelTerminator<'db> { - Return { source: ExprId, value: Option> }, - Unreachable { source: ExprId }, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelBindingInit<'db> { - pub(in crate::backend) pattern: BackendPattern, - pub(in crate::backend) source: FunctionKernelBindingSource<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum FunctionKernelBindingSource<'db> { - Value(FunctionKernelValue<'db>), - Param { index: usize, abi: AbiTy, source: ExprId }, -} - -impl<'db> FunctionKernelBindingSource<'db> { - #[cfg_attr(not(test), allow(dead_code))] - pub(in crate::backend) fn source_expr(&self) -> ExprId { - match self { - Self::Value(value) => value.source, - Self::Param { source, .. } => *source, - } - } -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelMatchArm<'db> { - pub(in crate::backend) pattern: BackendPattern, - pub(in crate::backend) body: FunctionKernelValue<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelFieldValue<'db> { - pub(in crate::backend) field: FieldLayout, - pub(in crate::backend) value: FunctionKernelValue<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelClosureEnvInit<'db> { - pub(in crate::backend) layout: AggregateLayout, - pub(in crate::backend) fields: Vec>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionKernelValue<'db> { - pub(in crate::backend) source: ExprId, - pub(in crate::backend) abi: AbiTy, - pub(in crate::backend) ownership: ValueOwnership, - pub(in crate::backend) kind: FunctionKernelValueKind<'db>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum FunctionKernelValueKind<'db> { - Local(NameId), - Capture(FieldLayout), - FunctionValue { - target: FunctionValueTarget<'db>, - }, - Clone { - value: Box>, - }, - ClosureValue { - target: ClosureInstanceKey<'db>, - env: FunctionKernelClosureEnvInit<'db>, - }, - Bool(bool), - Int(i64), - Float(f64), - String(u32), - StringFromBytes { - ptr: Box>, - len: Box>, - }, - Char(char), - Unit, - StackAddr { - frame_slot: FrameSlotId, - }, - AddrOffset { - base: Box>, - offset: u32, - }, - MemoryRead { - addr: Box>, - access: Option, - }, - MemoryWrite { - addr: Box>, - value: Box>, - }, - PointerAdd { - ptr: Box>, - count: Box>, - stride: u32, - }, - Array { - layout: ArrayRuntimeLayout, - items: Vec>, - }, - ArrayRepeat { - layout: ArrayRuntimeLayout, - value: Box>, - len: Box>, - }, - Block { - stmts: Vec>, - tail: Option>>, - }, - Call { - target: BackendCallTarget<'db>, - args: Vec>, - }, - IndirectCall { - callee: Box>, - signature: FunctionSignature, - args: Vec>, - }, - Binary { - op: BackendBinaryOp, - lhs: Box>, - rhs: Box>, - }, - Prefix { - op: BackendPrefixOp, - expr: Box>, - }, - If { - cond: Box>, - then_branch: Option>>, - else_branch: Option>>, - }, - Match { - scrutinee: Box>, - arms: Vec>, - }, - Loop { - body: Option>>, - }, - Break, - Continue, - Field { - base: Box>, - field: FieldLayout, - }, - Tuple { - fields: Vec>, - }, - Struct { - fields: Vec>, - }, - Union { - variant: VariantLayout, - value: Option>>, - }, - VariantValue { - variant: VariantLayout, - }, - VariantCall { - variant: VariantLayout, - args: Vec>, - }, -} - -pub(in crate::backend) struct FunctionKernelBuilder; -pub(in crate::backend) struct FunctionKernelValidator; - -impl FunctionKernelBuilder { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - ) -> Result, Diagnostic> { - let static_data = backend.build_static_data_for(&plan.reachability)?; - let mut output = FunctionKernelBundle { - direct_functions: Vec::new(), - closures: Vec::new(), - direct_indices: FxHashMap::default(), - closure_indices: FxHashMap::default(), - }; - let mut next_id = 0u32; - - for instance in &plan.reachability.functions { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - if matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) { - continue; - } - - let inference = instance.location.infer(backend.db); - let signature = backend.function_signature(instance, function, inference)?; - let layout = backend.build_layout( - &ReachableInstance::Function(instance.clone()), - function, - inference, - &signature, - )?; - let lowered = backend.build_lowered_kernel_function( - &ReachableInstance::Function(instance.clone()), - function, - hir_function.source_map(backend.db), - inference, - &layout, - &static_data, - )?; - output.direct_indices.insert(instance.clone(), output.direct_functions.len()); - output.direct_functions.push(build_function( - FunctionKernelId(next_id), - FunctionKernelKind::Direct(instance.clone()), - format_instance_key(backend.db, instance), - signature, - layout, - lowered, - )); - next_id += 1; - } - - for closure in &plan.reachability.closures { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - let info = backend.closure_info(closure, function, inference)?; - let layout = backend.build_layout( - &ReachableInstance::Closure(closure.clone()), - function, - inference, - &info.signature, - )?; - let lowered = backend.build_lowered_kernel_function( - &ReachableInstance::Closure(closure.clone()), - function, - hir_function.source_map(backend.db), - inference, - &layout, - &static_data, - )?; - output.closure_indices.insert(closure.clone(), output.closures.len()); - output.closures.push(build_function( - FunctionKernelId(next_id), - FunctionKernelKind::Closure(closure.clone()), - format_closure_key(backend.db, closure), - info.signature, - layout, - lowered, - )); - next_id += 1; - } - - FunctionKernelValidator::validate(backend, plan, &output)?; - Ok(output) - } -} - -impl FunctionKernelValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result<(), Diagnostic> { - let expected_direct = plan - .reachability - .functions - .iter() - .filter(|instance| { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - !matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - }) - .cloned() - .collect::>(); - if bundle.direct_functions.len() != expected_direct.len() { - return Err(Diagnostic::error( - "internal error: function kernel direct body set drifted from ordinary \ - reachability", - backend.file_range(), - )); - } - if bundle.closures.len() != plan.reachability.closures.len() { - return Err(Diagnostic::error( - "internal error: function kernel closure body set drifted from reachability", - backend.file_range(), - )); - } - for (index, function) in bundle.direct_functions.iter().enumerate() { - let expected = - FunctionKernelId(u32::try_from(index).expect("kernel ids should fit u32")); - if function.id != expected { - return Err(Diagnostic::error( - format!( - "internal error: function kernel direct id drifted (expected k{}, found \ - k{})", - expected.0, function.id.0 - ), - backend.file_range(), - )); - } - if function.blocks.len() != 1 || function.entry != FunctionKernelBlockId(0) { - return Err(Diagnostic::error( - format!( - "internal error: function kernel `{}` should currently lower to one entry \ - block", - function.debug_name - ), - backend.file_range(), - )); - } - match &function.kind { - FunctionKernelKind::Direct(instance) - if expected_direct.get(index) == Some(instance) => {} - _ => { - return Err(Diagnostic::error( - format!( - "internal error: function kernel direct ordering drifted for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - } - } - - for (index, function) in bundle.closures.iter().enumerate() { - match &function.kind { - FunctionKernelKind::Closure(closure) - if plan.reachability.closures.get(index) == Some(closure) => {} - _ => { - return Err(Diagnostic::error( - format!( - "internal error: function kernel closure ordering drifted for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - } - } - Ok(()) - } -} - -fn format_instance_key(db: &dyn salsa::Database, instance: &InstanceKey<'_>) -> String { - let name = instance.location.source(db).name().map_or("", |name| name.as_str()); - if instance.type_args.is_empty() { - name.to_owned() - } else { - let type_args = instance - .type_args - .iter() - .map(|ty| ty.display(db).to_string()) - .collect::>() - .join(", "); - format!("{name}[{type_args}]") - } -} - -fn format_closure_key(db: &dyn salsa::Database, closure: &ClosureInstanceKey<'_>) -> String { - let owner = format_instance_key(db, &closure.owner_instance()); - format!("{owner}::closure#{:?}", closure.closure) -} - -fn build_function<'db>( - id: FunctionKernelId, - kind: FunctionKernelKind<'db>, - debug_name: String, - signature: FunctionSignature, - layout: FunctionLayout, - lowered: FunctionKernelLowered<'db>, -) -> FunctionKernelFunction<'db> { - let (stmts, terminator, source, result_abi) = match lowered.body { - Some(body) => lower_body(body), - None => ( - Vec::new(), - FunctionKernelTerminator::Return { source: ExprId::ZERO, value: None }, - ExprId::ZERO, - signature.result.clone(), - ), - }; - FunctionKernelFunction { - id, - kind, - debug_name, - signature, - layout, - legalization: None, - param_inits: lowered.param_inits, - entry: FunctionKernelBlockId(0), - blocks: vec![FunctionKernelBlock { - id: FunctionKernelBlockId(0), - source, - result_abi, - stmts, - terminator, - }], - decisions: vec!["kernel=single-entry".to_owned()], - } -} - -fn lower_body<'db>( - body: FunctionKernelValue<'db>, -) -> (Vec>, FunctionKernelTerminator<'db>, ExprId, AbiTy) { - let source = body.source; - let result_abi = body.abi.clone(); - match body.kind { - FunctionKernelValueKind::Block { stmts, tail } => { - let terminator = - FunctionKernelTerminator::Return { source, value: tail.map(|tail| *tail) }; - (stmts, terminator, source, result_abi) - } - _ => ( - Vec::new(), - FunctionKernelTerminator::Return { source, value: Some(body) }, - source, - result_abi, - ), - } -} - -#[cfg(test)] -fn describe_abi(_db: &dyn salsa::Database, abi: &AbiTy) -> String { - match abi { - AbiTy::Scalar(ty) => format!("{ty:?}"), - AbiTy::Aggregate(layout) => format!("aggregate(size={})", layout.size), - } -} - -#[cfg(test)] -fn dump_stmt(db: &dyn salsa::Database, stmt: &FunctionKernelStmt<'_>) -> String { - match stmt { - FunctionKernelStmt::Local { name, initializer, .. } => { - format!("local {:?} = {}", name, dump_value(db, initializer.as_ref())) - } - FunctionKernelStmt::Assign { name, value, .. } => { - format!("assign {:?} = {}", name, dump_value(db, Some(value))) - } - FunctionKernelStmt::Pattern(init) => format!("pattern {:?}", init.pattern), - FunctionKernelStmt::Expr(expr) => format!("expr {}", dump_value(db, Some(expr))), - FunctionKernelStmt::Return { value, .. } => { - format!("return {}", dump_value(db, value.as_ref())) - } - FunctionKernelStmt::Retain { value, .. } => { - format!("retain {}", dump_value(db, Some(value))) - } - FunctionKernelStmt::Release { value, .. } => { - format!("release {}", dump_value(db, Some(value))) - } - FunctionKernelStmt::Destroy { value, .. } => { - format!("destroy {}", dump_value(db, Some(value))) - } - FunctionKernelStmt::Copy { value, .. } => format!("copy {}", dump_value(db, Some(value))), - FunctionKernelStmt::Move { value, .. } => format!("move {}", dump_value(db, Some(value))), - } -} - -#[cfg(test)] -fn dump_terminator(db: &dyn salsa::Database, term: &FunctionKernelTerminator<'_>) -> String { - match term { - FunctionKernelTerminator::Return { value, .. } => { - format!("return {}", dump_value(db, value.as_ref())) - } - FunctionKernelTerminator::Unreachable { .. } => "unreachable".to_owned(), - } -} - -#[cfg(test)] -fn dump_value(db: &dyn salsa::Database, value: Option<&FunctionKernelValue<'_>>) -> String { - let Some(value) = value else { - return "unit".to_owned(); - }; - match &value.kind { - FunctionKernelValueKind::Local(name) => format!("local({name:?})"), - FunctionKernelValueKind::Bool(value) => format!("bool({value})"), - FunctionKernelValueKind::Int(value) => format!("int({value})"), - FunctionKernelValueKind::String(_) => "string".to_owned(), - FunctionKernelValueKind::StringFromBytes { .. } => "string_from_bytes".to_owned(), - FunctionKernelValueKind::Call { .. } => "call".to_owned(), - FunctionKernelValueKind::If { .. } => "if".to_owned(), - FunctionKernelValueKind::Match { .. } => "match".to_owned(), - FunctionKernelValueKind::Loop { .. } => "loop".to_owned(), - FunctionKernelValueKind::Block { .. } => "block".to_owned(), - _ => describe_abi(db, &value.abi), - } -} - -#[cfg(test)] -mod tests { - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "function_kernel_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {:?}", diagnostics); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected backend diagnostics: {:?}", - backend.diagnostics - ); - backend - } - - #[test] - fn function_kernel_dump_is_stable() { - let backend = compiler_for_fixture( - r#" -fun choose(flag: bool): int { - if flag { 42 } else { 0 } -} - -export fun main(): int { - choose(true) -} -"#, - ); - let plan = backend.build_module_plan().expect("module plan"); - let first = plan.function_kernel.as_ref().expect("kernel").dump(backend.db); - let second = backend - .build_module_plan() - .expect("module plan") - .function_kernel - .as_ref() - .expect("kernel") - .dump(backend.db); - assert_eq!(first, second); - assert!(first.contains("function_kernel.direct:")); - assert!(first.contains("block b0")); - } -} diff --git a/crates/mitki-backend-wasm/src/lowering/function_legalize.rs b/crates/mitki-backend-wasm/src/lowering/function_legalize.rs deleted file mode 100644 index a1d39ad..0000000 --- a/crates/mitki-backend-wasm/src/lowering/function_legalize.rs +++ /dev/null @@ -1,490 +0,0 @@ -use mitki_errors::Diagnostic; -use rustc_hash::FxHashMap; - -use super::function_kernel::{ - FunctionKernelBlock, FunctionKernelBundle, FunctionKernelFunction, FunctionKernelStmt, - FunctionKernelTerminator, FunctionKernelValue, FunctionKernelValueKind, -}; -use super::plan::ModulePlan; -use super::*; - -pub(in crate::backend) struct FunctionLegalizer; -pub(in crate::backend) struct CallConvValidator; - -#[derive(Clone, Debug)] -pub(in crate::backend) struct FunctionLegalization<'db> { - pub(in crate::backend) signature: LegalizedFunctionSignature, - pub(in crate::backend) callable_representation: LegalizedCallableRepresentation, - pub(in crate::backend) call_sites: FxHashMap>, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct LegalizedFunctionSignature { - pub(in crate::backend) params: Vec, - pub(in crate::backend) hidden_env_local: Option, - pub(in crate::backend) hidden_result_local: Option, - pub(in crate::backend) result: LegalizedResultPassing, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct LegalizedParam { - pub(in crate::backend) ordinal: usize, - pub(in crate::backend) abi: AbiTy, - pub(in crate::backend) passing: LegalizedArgPassing, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum LegalizedArgPassing { - Direct, - ByAddress, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum LegalizedResultPassing { - Unit, - DirectScalar(BackendTy), - IndirectOutPtr, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum LegalizedCallableRepresentation { - HandleAndTable, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct LegalizedCallSite<'db> { - pub(in crate::backend) kind: LegalizedCallKind<'db>, - pub(in crate::backend) arg_passings: Vec, - pub(in crate::backend) result: LegalizedResultPassing, - pub(in crate::backend) callable_representation: LegalizedCallableRepresentation, -} - -#[derive(Clone, Debug)] -pub(in crate::backend) enum LegalizedCallKind<'db> { - Direct(BackendCallTarget<'db>), - Indirect(FunctionSignature), -} - -impl FunctionLegalizer { - pub(in crate::backend) fn run<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result, Diagnostic> { - let legalized = FunctionKernelBundle { - direct_functions: bundle - .direct_functions - .iter() - .map(|function| legalize_function(backend, function)) - .collect(), - closures: bundle - .closures - .iter() - .map(|function| legalize_function(backend, function)) - .collect(), - direct_indices: bundle.direct_indices.clone(), - closure_indices: bundle.closure_indices.clone(), - }; - CallConvValidator::validate(backend, plan, &legalized)?; - Ok(legalized) - } -} - -impl CallConvValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - _plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result<(), Diagnostic> { - let signature_strategy = backend.signature_strategy(); - for function in bundle.direct_functions.iter().chain(bundle.closures.iter()) { - let lowered = signature_strategy.direct_signature(&function.signature); - let legalization = function.legalization.as_ref().ok_or_else(|| { - Diagnostic::error( - format!( - "internal error: function `{}` is missing call-convention legalization", - function.debug_name - ), - backend.file_range(), - ) - })?; - let has_result_ptr = function.layout.result_ptr_local().is_some(); - if legalization.signature.hidden_result_local.is_some() != has_result_ptr { - return Err(Diagnostic::error( - format!( - "internal error: legalization drifted result-pointer storage for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - if lowered.params.is_empty() - && function.signature.params.is_empty() - && legalization.signature.hidden_result_local.is_some() - { - return Err(Diagnostic::error( - format!( - "internal error: legalized signature for `{}` lost the hidden result \ - pointer lane", - function.debug_name - ), - backend.file_range(), - )); - } - if legalization.signature.hidden_env_local != function.layout.env_ptr_local() { - return Err(Diagnostic::error( - format!( - "internal error: legalization drifted env parameter storage for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - if legalization.signature.params.len() != function.signature.params.len() { - return Err(Diagnostic::error( - format!( - "internal error: legalization drifted parameter arity for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - for (ordinal, (expected_abi, param)) in - function.signature.params.iter().zip(&legalization.signature.params).enumerate() - { - if param.ordinal != ordinal - || ¶m.abi != expected_abi - || param.passing != arg_passing_for(expected_abi) - { - return Err(Diagnostic::error( - format!( - "internal error: legalization drifted parameter passing for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - } - } - Ok(()) - } -} - -fn legalize_function<'db>( - backend: &Backend<'db>, - function: &FunctionKernelFunction<'db>, -) -> FunctionKernelFunction<'db> { - let mut legalized = function.clone(); - let signature_strategy = backend.signature_strategy(); - let lowered = signature_strategy.direct_signature(&legalized.signature); - let callable_representation = LegalizedCallableRepresentation::HandleAndTable; - legalized.legalization = Some(FunctionLegalization { - signature: LegalizedFunctionSignature { - params: legalized - .signature - .params - .iter() - .cloned() - .enumerate() - .map(|(ordinal, abi)| LegalizedParam { - ordinal, - passing: arg_passing_for(&abi), - abi, - }) - .collect(), - hidden_env_local: legalized.layout.env_ptr_local(), - hidden_result_local: legalized.layout.result_ptr_local(), - result: result_passing_for(&legalized.signature.result), - }, - callable_representation, - call_sites: collect_call_sites(&legalized.blocks, callable_representation), - }); - legalized.decisions.push(format!("callconv={}", backend.target_profile().canonical_name())); - legalized.decisions.push(format!("physical_params={}", lowered.params.len())); - legalized.decisions.push(format!("physical_results={}", lowered.results.len())); - if legalized.legalization.as_ref().and_then(|it| it.signature.hidden_env_local).is_some() { - legalized.decisions.push("env_param=hidden".to_owned()); - } - if legalized.legalization.as_ref().and_then(|it| it.signature.hidden_result_local).is_some() { - legalized.decisions.push("result_ptr=hidden".to_owned()); - } - legalized.decisions.push("aggregate_args=by-address".to_owned()); - legalized.decisions.push("callables=handle-and-table".to_owned()); - legalized -} - -fn arg_passing_for(abi: &AbiTy) -> LegalizedArgPassing { - match abi { - AbiTy::Scalar(_) => LegalizedArgPassing::Direct, - AbiTy::Aggregate(_) => LegalizedArgPassing::ByAddress, - } -} - -fn result_passing_for(abi: &AbiTy) -> LegalizedResultPassing { - match abi { - AbiTy::Scalar(BackendTy::Unit) => LegalizedResultPassing::Unit, - AbiTy::Scalar(ty) => LegalizedResultPassing::DirectScalar(*ty), - AbiTy::Aggregate(_) => LegalizedResultPassing::IndirectOutPtr, - } -} - -fn collect_call_sites<'db>( - blocks: &[FunctionKernelBlock<'db>], - callable_representation: LegalizedCallableRepresentation, -) -> FxHashMap> { - let mut sites = FxHashMap::default(); - for block in blocks { - for stmt in &block.stmts { - collect_stmt_call_sites(stmt, callable_representation, &mut sites); - } - collect_terminator_call_sites(&block.terminator, callable_representation, &mut sites); - } - sites -} - -fn collect_stmt_call_sites<'db>( - stmt: &FunctionKernelStmt<'db>, - callable_representation: LegalizedCallableRepresentation, - sites: &mut FxHashMap>, -) { - match stmt { - FunctionKernelStmt::Local { initializer, .. } => { - if let Some(initializer) = initializer { - collect_value_call_sites(initializer, callable_representation, sites); - } - } - FunctionKernelStmt::Assign { value, .. } => { - collect_value_call_sites(value, callable_representation, sites); - } - FunctionKernelStmt::Pattern(init) => { - if let function_kernel::FunctionKernelBindingSource::Value(value) = &init.source { - collect_value_call_sites(value, callable_representation, sites); - } - } - FunctionKernelStmt::Expr(value) - | FunctionKernelStmt::Retain { value, .. } - | FunctionKernelStmt::Release { value, .. } - | FunctionKernelStmt::Destroy { value, .. } - | FunctionKernelStmt::Copy { value, .. } - | FunctionKernelStmt::Move { value, .. } => { - collect_value_call_sites(value, callable_representation, sites); - } - FunctionKernelStmt::Return { value, .. } => { - if let Some(value) = value { - collect_value_call_sites(value, callable_representation, sites); - } - } - } -} - -fn collect_terminator_call_sites<'db>( - terminator: &FunctionKernelTerminator<'db>, - callable_representation: LegalizedCallableRepresentation, - sites: &mut FxHashMap>, -) { - if let FunctionKernelTerminator::Return { value, .. } = terminator - && let Some(value) = value - { - collect_value_call_sites(value, callable_representation, sites); - } -} - -fn collect_value_call_sites<'db>( - value: &FunctionKernelValue<'db>, - callable_representation: LegalizedCallableRepresentation, - sites: &mut FxHashMap>, -) { - match &value.kind { - FunctionKernelValueKind::Call { target, args } => { - sites.insert( - value.source, - LegalizedCallSite { - kind: LegalizedCallKind::Direct(target.clone()), - arg_passings: args.iter().map(|arg| arg_passing_for(&arg.abi)).collect(), - result: result_passing_for(&target.signature.result), - callable_representation, - }, - ); - for arg in args { - collect_value_call_sites(arg, callable_representation, sites); - } - } - FunctionKernelValueKind::IndirectCall { callee, signature, args } => { - sites.insert( - value.source, - LegalizedCallSite { - kind: LegalizedCallKind::Indirect(signature.clone()), - arg_passings: args.iter().map(|arg| arg_passing_for(&arg.abi)).collect(), - result: result_passing_for(&signature.result), - callable_representation, - }, - ); - collect_value_call_sites(callee, callable_representation, sites); - for arg in args { - collect_value_call_sites(arg, callable_representation, sites); - } - } - FunctionKernelValueKind::Clone { value } - | FunctionKernelValueKind::AddrOffset { base: value, .. } - | FunctionKernelValueKind::MemoryRead { addr: value, .. } - | FunctionKernelValueKind::Prefix { expr: value, .. } - | FunctionKernelValueKind::Loop { body: Some(value) } - | FunctionKernelValueKind::Field { base: value, .. } => { - collect_value_call_sites(value, callable_representation, sites); - } - FunctionKernelValueKind::MemoryWrite { addr, value } - | FunctionKernelValueKind::Binary { lhs: addr, rhs: value, .. } => { - collect_value_call_sites(addr, callable_representation, sites); - collect_value_call_sites(value, callable_representation, sites); - } - FunctionKernelValueKind::PointerAdd { ptr, count, .. } => { - collect_value_call_sites(ptr, callable_representation, sites); - collect_value_call_sites(count, callable_representation, sites); - } - FunctionKernelValueKind::StringFromBytes { ptr, len } => { - collect_value_call_sites(ptr, callable_representation, sites); - collect_value_call_sites(len, callable_representation, sites); - } - FunctionKernelValueKind::Array { items, .. } - | FunctionKernelValueKind::VariantCall { args: items, .. } => { - for item in items { - collect_value_call_sites(item, callable_representation, sites); - } - } - FunctionKernelValueKind::ArrayRepeat { value, len, .. } => { - collect_value_call_sites(value, callable_representation, sites); - collect_value_call_sites(len, callable_representation, sites); - } - FunctionKernelValueKind::Block { stmts, tail } => { - for stmt in stmts { - collect_stmt_call_sites(stmt, callable_representation, sites); - } - if let Some(tail) = tail { - collect_value_call_sites(tail, callable_representation, sites); - } - } - FunctionKernelValueKind::If { cond, then_branch, else_branch } => { - collect_value_call_sites(cond, callable_representation, sites); - if let Some(value) = then_branch { - collect_value_call_sites(value, callable_representation, sites); - } - if let Some(value) = else_branch { - collect_value_call_sites(value, callable_representation, sites); - } - } - FunctionKernelValueKind::Match { scrutinee, arms } => { - collect_value_call_sites(scrutinee, callable_representation, sites); - for arm in arms { - collect_value_call_sites(&arm.body, callable_representation, sites); - } - } - FunctionKernelValueKind::Tuple { fields } | FunctionKernelValueKind::Struct { fields } => { - for field in fields { - collect_value_call_sites(&field.value, callable_representation, sites); - } - } - FunctionKernelValueKind::ClosureValue { env, .. } => { - for field in &env.fields { - collect_value_call_sites(&field.value, callable_representation, sites); - } - } - FunctionKernelValueKind::Union { value, .. } => { - if let Some(value) = value { - collect_value_call_sites(value, callable_representation, sites); - } - } - FunctionKernelValueKind::Local(_) - | FunctionKernelValueKind::Capture(_) - | FunctionKernelValueKind::FunctionValue { .. } - | FunctionKernelValueKind::Bool(_) - | FunctionKernelValueKind::Int(_) - | FunctionKernelValueKind::Float(_) - | FunctionKernelValueKind::String(_) - | FunctionKernelValueKind::Char(_) - | FunctionKernelValueKind::Unit - | FunctionKernelValueKind::StackAddr { .. } - | FunctionKernelValueKind::Loop { body: None } - | FunctionKernelValueKind::Break - | FunctionKernelValueKind::Continue - | FunctionKernelValueKind::VariantValue { .. } => {} - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "function_legalize_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {:?}", diagnostics); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected backend diagnostics: {:?}", - backend.diagnostics - ); - backend - } - - #[test] - fn legalization_tracks_hidden_result_pointers_and_closure_env_params() { - let backend = compiler_for_fixture( - r#" -fun apply(f: fun(int) -> int, x: int): int { - f(x) -} - -fun pair(): (int, int) { - (1, 2) -} - -export fun main(): int { - val offset = 1 - val add: fun(int) -> int = { value in value + offset } - val _ = pair() - apply(add, 41) -} -"#, - ); - let plan = backend.build_module_plan().expect("module plan"); - - let direct = plan - .function_kernel - .as_ref() - .expect("kernel") - .direct_functions - .iter() - .find(|function| function.signature.result.is_aggregate()) - .expect("aggregate-returning function"); - assert!(direct.decisions.iter().any(|decision| decision == "result_ptr=hidden")); - - let closure = - plan.function_kernel.as_ref().expect("kernel").closures.first().expect("closure"); - assert!(closure.decisions.iter().any(|decision| decision == "env_param=hidden")); - assert!( - closure - .decisions - .iter() - .any(|decision| decision.starts_with("callconv=wasm-core-v2/m32")) - ); - } -} diff --git a/crates/mitki-backend-wasm/src/lowering/function_ownership.rs b/crates/mitki-backend-wasm/src/lowering/function_ownership.rs deleted file mode 100644 index 5aa73d1..0000000 --- a/crates/mitki-backend-wasm/src/lowering/function_ownership.rs +++ /dev/null @@ -1,241 +0,0 @@ -#![allow(unused_imports)] - -#[cfg(test)] -use std::sync::Arc; - -use mitki_errors::Diagnostic; - -use super::function_kernel::{ - FunctionKernelBindingSource, FunctionKernelBlock, FunctionKernelBundle, FunctionKernelFunction, - FunctionKernelStmt, FunctionKernelTerminator, FunctionKernelValueKind, -}; -use super::plan::ModulePlan; -use super::*; - -pub(in crate::backend) struct OwnershipLowering; -pub(in crate::backend) struct OwnershipValidator; - -impl OwnershipLowering { - pub(in crate::backend) fn run<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result, Diagnostic> { - let lowered = FunctionKernelBundle { - direct_functions: bundle.direct_functions.iter().map(lower_function).collect(), - closures: bundle.closures.iter().map(lower_function).collect(), - direct_indices: bundle.direct_indices.clone(), - closure_indices: bundle.closure_indices.clone(), - }; - OwnershipValidator::validate(backend, plan, &lowered)?; - Ok(lowered) - } -} - -impl OwnershipValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - _plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result<(), Diagnostic> { - for function in bundle.direct_functions.iter().chain(bundle.closures.iter()) { - let has_ownership_stmt = - function.blocks.iter().flat_map(|block| block.stmts.iter()).any(|stmt| { - matches!( - stmt, - FunctionKernelStmt::Retain { .. } - | FunctionKernelStmt::Release { .. } - | FunctionKernelStmt::Destroy { .. } - | FunctionKernelStmt::Copy { .. } - | FunctionKernelStmt::Move { .. } - ) - }); - if !has_ownership_stmt - && function.blocks.iter().any(|block| block.result_abi.contains_heap_refs()) - { - return Err(Diagnostic::error( - format!( - "internal error: ownership lowering did not materialize any ownership ops \ - for `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - } - Ok(()) - } -} - -fn lower_function<'db>(function: &FunctionKernelFunction<'db>) -> FunctionKernelFunction<'db> { - let mut lowered = function.clone(); - lowered.decisions.push("ownership=explicit".to_owned()); - lowered.blocks = lowered.blocks.iter().map(lower_block).collect(); - lowered -} - -fn lower_block<'db>(block: &FunctionKernelBlock<'db>) -> FunctionKernelBlock<'db> { - let mut stmts = Vec::new(); - for stmt in &block.stmts { - lower_stmt(stmt, &mut stmts); - } - let mut terminator = block.terminator.clone(); - if let FunctionKernelTerminator::Return { source, value: Some(value) } = &block.terminator { - if value.ownership.is_borrowed() - && matches!(value.abi, AbiTy::Scalar(ty) if ty.is_heap_ref()) - { - stmts.push(FunctionKernelStmt::Retain { - source: *source, - abi: value.abi.clone(), - value: value.clone(), - }); - } else if value.ownership.is_owned() && value.abi.contains_heap_refs() { - stmts.push(FunctionKernelStmt::Move { - source: *source, - abi: value.abi.clone(), - value: value.clone(), - }); - } - terminator = - FunctionKernelTerminator::Return { source: *source, value: Some(value.clone()) }; - } - FunctionKernelBlock { stmts, terminator, ..block.clone() } -} - -fn lower_stmt<'db>(stmt: &FunctionKernelStmt<'db>, out: &mut Vec>) { - match stmt { - FunctionKernelStmt::Local { name, abi, initializer } => { - out.push(stmt.clone()); - if let Some(initializer) = initializer { - if initializer.ownership.is_borrowed() - && matches!(abi, AbiTy::Scalar(ty) if ty.is_heap_ref()) - { - out.push(FunctionKernelStmt::Retain { - source: initializer.source, - abi: abi.clone(), - value: initializer.clone(), - }); - } else if initializer.ownership.is_owned() && abi.contains_heap_refs() { - out.push(FunctionKernelStmt::Move { - source: initializer.source, - abi: abi.clone(), - value: initializer.clone(), - }); - } - if matches!(initializer.kind, FunctionKernelValueKind::Clone { .. }) { - out.push(FunctionKernelStmt::Copy { - source: initializer.source, - abi: initializer.abi.clone(), - value: initializer.clone(), - }); - } - } - let _ = name; - } - FunctionKernelStmt::Assign { .. } => { - out.push(stmt.clone()); - } - FunctionKernelStmt::Pattern(init) => { - out.push(stmt.clone()); - if let FunctionKernelBindingSource::Value(value) = &init.source - && value.ownership.is_owned() - && value.abi.contains_heap_refs() - { - out.push(FunctionKernelStmt::Move { - source: value.source, - abi: value.abi.clone(), - value: value.clone(), - }); - } - } - FunctionKernelStmt::Expr(expr) => { - out.push(stmt.clone()); - if expr.ownership.is_owned() && expr.abi.contains_heap_refs() { - out.push(FunctionKernelStmt::Release { - source: expr.source, - abi: expr.abi.clone(), - value: expr.clone(), - }); - } - } - FunctionKernelStmt::Return { source, value } => { - out.push(stmt.clone()); - if let Some(value) = value - && value.ownership.is_borrowed() - && matches!(value.abi, AbiTy::Scalar(ty) if ty.is_heap_ref()) - { - out.push(FunctionKernelStmt::Retain { - source: *source, - abi: value.abi.clone(), - value: value.clone(), - }); - } - } - FunctionKernelStmt::Retain { .. } - | FunctionKernelStmt::Release { .. } - | FunctionKernelStmt::Destroy { .. } - | FunctionKernelStmt::Copy { .. } - | FunctionKernelStmt::Move { .. } => out.push(stmt.clone()), - } -} - -#[cfg(test)] -mod tests { - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "function_ownership_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {:?}", diagnostics); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected backend diagnostics: {:?}", - backend.diagnostics - ); - backend - } - - #[test] - fn ownership_lowering_materializes_explicit_moves_for_heap_values() { - let backend = compiler_for_fixture( - r#" -export fun main(): [str] { - val xs = ["a", "b"]; - xs -} -"#, - ); - let plan = backend.build_module_plan().expect("module plan"); - let kernel = plan - .function_kernel - .as_ref() - .expect("kernel") - .direct_functions - .first() - .expect("direct function"); - let has_move = kernel - .entry_block() - .stmts - .iter() - .any(|stmt| matches!(stmt, FunctionKernelStmt::Move { .. })); - assert!(has_move, "expected ownership lowering to materialize a move op"); - assert!(kernel.decisions.iter().any(|decision| decision == "ownership=explicit")); - } -} diff --git a/crates/mitki-backend-wasm/src/lowering/function_wasm_ir.rs b/crates/mitki-backend-wasm/src/lowering/function_wasm_ir.rs deleted file mode 100644 index 816eb40..0000000 --- a/crates/mitki-backend-wasm/src/lowering/function_wasm_ir.rs +++ /dev/null @@ -1,898 +0,0 @@ -#![allow(unused_imports, unused_qualifications)] - -use std::ops::Deref; - -use mitki_errors::Diagnostic; -use rustc_hash::FxHashMap; - -pub(in crate::backend) use super::function_codegen_ir::{ - FunctionStackifier, StructuredWasmBindingInit, StructuredWasmBindingSource, StructuredWasmExpr, - StructuredWasmExprKind, StructuredWasmLocal, StructuredWasmLocalReason, StructuredWasmMatchArm, - StructuredWasmOwnershipOp, StructuredWasmPeephole, StructuredWasmRegion, - StructuredWasmResultArity, StructuredWasmStmt, -}; -use super::function_kernel::{ - FunctionKernelBundle, FunctionKernelFunction, FunctionKernelKind, FunctionKernelValue, - FunctionKernelValueKind, -}; -use super::plan::ModulePlan; -use super::*; - -pub(in crate::backend) struct StructuredWasmLowering; -pub(in crate::backend) struct StructuredWasmValidator; -struct StructuredWasmIndexResolver; - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmBundle<'db> { - pub(in crate::backend) direct_functions: Vec>, - pub(in crate::backend) closures: Vec>, - pub(in crate::backend) direct_indices: FxHashMap, usize>, - pub(in crate::backend) closure_indices: FxHashMap, usize>, -} - -impl<'db> StructuredWasmBundle<'db> { - pub(in crate::backend) fn direct( - &self, - instance: &InstanceKey<'db>, - ) -> Option<&StructuredWasmFunction<'db>> { - self.direct_indices.get(instance).and_then(|&index| self.direct_functions.get(index)) - } - - pub(in crate::backend) fn closure( - &self, - closure: &ClosureInstanceKey<'db>, - ) -> Option<&StructuredWasmFunction<'db>> { - self.closure_indices.get(closure).and_then(|&index| self.closures.get(index)) - } - - pub(in crate::backend) fn lowered_bundle( - &self, - ) -> super::function_codegen_ir::StructuredWasmBundle<'db> { - super::function_codegen_ir::StructuredWasmBundle { - direct_functions: self - .direct_functions - .iter() - .map(|function| function.lowered.clone()) - .collect(), - closures: self.closures.iter().map(|function| function.lowered.clone()).collect(), - direct_indices: self.direct_indices.clone(), - closure_indices: self.closure_indices.clone(), - } - } -} - -#[derive(Clone, Debug)] -pub(in crate::backend) struct StructuredWasmFunction<'db> { - lowered: super::function_codegen_ir::StructuredWasmFunction<'db>, - pub(in crate::backend) resolved: StructuredWasmResolvedRefs<'db>, -} - -impl<'db> StructuredWasmFunction<'db> { - pub(in crate::backend) fn resolved(&self) -> &StructuredWasmResolvedRefs<'db> { - &self.resolved - } -} - -impl<'db> Deref for StructuredWasmFunction<'db> { - type Target = super::function_codegen_ir::StructuredWasmFunction<'db>; - - fn deref(&self) -> &Self::Target { - &self.lowered - } -} - -#[derive(Clone, Debug, Default)] -pub(in crate::backend) struct StructuredWasmResolvedRefs<'db> { - pub(in crate::backend) direct_calls: FxHashMap>, - pub(in crate::backend) indirect_calls: FxHashMap, - pub(in crate::backend) callable_values: - FxHashMap>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct StructuredWasmResolvedDirectCall<'db> { - pub(in crate::backend) function_index: u32, - pub(in crate::backend) _marker: std::marker::PhantomData<&'db ()>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct StructuredWasmResolvedIndirectCall { - pub(in crate::backend) signature: FunctionSignature, - pub(in crate::backend) type_index: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct StructuredWasmResolvedCallableValue<'db> { - pub(in crate::backend) target: FunctionValueTarget<'db>, - pub(in crate::backend) table_slot: u32, -} - -impl StructuredWasmLowering { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &FunctionKernelBundle<'db>, - ) -> Result, Diagnostic> { - let stackified = StructuredWasmPeephole::run( - backend, - plan, - FunctionStackifier::build(backend, plan, bundle)?, - )?; - let stackified = Self::inject_ownership_ops(stackified, bundle); - StructuredWasmIndexResolver::resolve(backend, plan, &stackified) - } -} - -impl StructuredWasmValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &StructuredWasmBundle<'db>, - ) -> Result<(), Diagnostic> { - super::function_codegen_ir::StructuredWasmValidator::validate( - backend, - plan, - &bundle.lowered_bundle(), - ) - } -} - -impl StructuredWasmLowering { - fn inject_ownership_ops<'db>( - mut bundle: super::function_codegen_ir::StructuredWasmBundle<'db>, - kernel: &FunctionKernelBundle<'db>, - ) -> super::function_codegen_ir::StructuredWasmBundle<'db> { - for (function, kernel_function) in - bundle.direct_functions.iter_mut().zip(&kernel.direct_functions) - { - Self::inject_function_ownership(function, kernel_function); - } - for (function, kernel_function) in bundle.closures.iter_mut().zip(&kernel.closures) { - Self::inject_function_ownership(function, kernel_function); - } - bundle - } - - fn inject_function_ownership<'db>( - function: &mut super::function_codegen_ir::StructuredWasmFunction<'db>, - kernel: &FunctionKernelFunction<'db>, - ) { - function.legalization = kernel.legalization.clone(); - let Some(body) = function.body.as_mut() else { - return; - }; - let StructuredWasmExprKind::Block { region, .. } = &mut body.kind else { - return; - }; - let block = kernel.entry_block(); - let original_stmts = std::mem::take(&mut region.stmts); - let mut original_iter = original_stmts.into_iter(); - let mut rewritten = Vec::new(); - let mut index = 0usize; - let mut return_ops = Vec::new(); - while index < block.stmts.len() { - if Self::kernel_stmt_is_ownership(&block.stmts[index]) { - index += 1; - continue; - } - let Some(stmt) = original_iter.next() else { - break; - }; - let mut ops = Vec::new(); - index += 1; - while index < block.stmts.len() && Self::kernel_stmt_is_ownership(&block.stmts[index]) { - ops.push(Self::ownership_op_from_kernel_stmt(&block.stmts[index])); - index += 1; - } - if index == block.stmts.len() - && matches!( - block.terminator, - super::function_kernel::FunctionKernelTerminator::Return { .. } - ) - { - return_ops = ops; - rewritten.push(stmt); - break; - } - rewritten.push(Self::attach_ownership_ops(stmt, ops)); - } - - if let super::function_kernel::FunctionKernelTerminator::Return { .. } = &block.terminator { - function.body_ownership_ops = return_ops; - } - rewritten.extend(original_iter); - region.stmts = rewritten; - } - - fn kernel_stmt_is_ownership<'db>( - stmt: &super::function_kernel::FunctionKernelStmt<'db>, - ) -> bool { - matches!( - stmt, - super::function_kernel::FunctionKernelStmt::Retain { .. } - | super::function_kernel::FunctionKernelStmt::Release { .. } - | super::function_kernel::FunctionKernelStmt::Destroy { .. } - | super::function_kernel::FunctionKernelStmt::Copy { .. } - | super::function_kernel::FunctionKernelStmt::Move { .. } - ) - } - - fn ownership_op_from_kernel_stmt<'db>( - stmt: &super::function_kernel::FunctionKernelStmt<'db>, - ) -> StructuredWasmOwnershipOp { - match stmt { - super::function_kernel::FunctionKernelStmt::Retain { .. } => { - StructuredWasmOwnershipOp::Retain - } - super::function_kernel::FunctionKernelStmt::Release { .. } => { - StructuredWasmOwnershipOp::Release - } - super::function_kernel::FunctionKernelStmt::Destroy { .. } => { - StructuredWasmOwnershipOp::Destroy - } - super::function_kernel::FunctionKernelStmt::Copy { .. } => { - StructuredWasmOwnershipOp::Copy - } - super::function_kernel::FunctionKernelStmt::Move { .. } => { - StructuredWasmOwnershipOp::Move - } - _ => unreachable!(), - } - } - - fn attach_ownership_ops<'db>( - stmt: StructuredWasmStmt<'db>, - ownership_ops: Vec, - ) -> StructuredWasmStmt<'db> { - match stmt { - StructuredWasmStmt::Local { name, abi, initializer, .. } => { - StructuredWasmStmt::Local { name, abi, initializer, ownership_ops } - } - StructuredWasmStmt::If { - source, - abi, - ownership, - cond, - then_region, - else_region, - .. - } => StructuredWasmStmt::If { - source, - abi, - ownership, - cond, - then_region, - else_region, - ownership_ops, - }, - StructuredWasmStmt::Match { - source, - abi, - ownership, - scrutinee, - arms, - fallback_unreachable, - .. - } => StructuredWasmStmt::Match { - source, - abi, - ownership, - scrutinee, - arms, - fallback_unreachable, - ownership_ops, - }, - StructuredWasmStmt::Return { source, value, .. } => { - StructuredWasmStmt::Return { source, value, ownership_ops } - } - StructuredWasmStmt::Expr { expr, .. } => { - StructuredWasmStmt::Expr { expr, ownership_ops } - } - other => other, - } - } -} - -impl StructuredWasmIndexResolver { - fn resolve<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &super::function_codegen_ir::StructuredWasmBundle<'db>, - ) -> Result, Diagnostic> { - Ok(StructuredWasmBundle { - direct_functions: bundle - .direct_functions - .iter() - .map(|function| Self::resolve_function(backend, plan, function)) - .collect::>()?, - closures: bundle - .closures - .iter() - .map(|function| Self::resolve_function(backend, plan, function)) - .collect::>()?, - direct_indices: bundle.direct_indices.clone(), - closure_indices: bundle.closure_indices.clone(), - }) - } - - fn resolve_function<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - ) -> Result, Diagnostic> { - let mut resolved = StructuredWasmResolvedRefs::default(); - for init in &function.param_inits { - Self::resolve_binding_init(backend, plan, function, &mut resolved, init)?; - } - if let Some(body) = &function.body { - Self::resolve_emit_expr(backend, plan, function, &mut resolved, body)?; - } - Ok(StructuredWasmFunction { lowered: function.clone(), resolved }) - } - - fn resolve_binding_init<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - resolved: &mut StructuredWasmResolvedRefs<'db>, - init: &StructuredWasmBindingInit<'db>, - ) -> Result<(), Diagnostic> { - if let StructuredWasmBindingSource::Expr(expr) = &init.source { - Self::resolve_emit_expr(backend, plan, function, resolved, expr)?; - } - Ok(()) - } - - fn resolve_region<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - resolved: &mut StructuredWasmResolvedRefs<'db>, - region: &StructuredWasmRegion<'db>, - ) -> Result<(), Diagnostic> { - for stmt in ®ion.stmts { - Self::resolve_stmt(backend, plan, function, resolved, stmt)?; - } - if let Some(tail) = ®ion.tail { - Self::resolve_emit_expr(backend, plan, function, resolved, tail)?; - } - Ok(()) - } - - fn resolve_stmt<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - resolved: &mut StructuredWasmResolvedRefs<'db>, - stmt: &StructuredWasmStmt<'db>, - ) -> Result<(), Diagnostic> { - match stmt { - StructuredWasmStmt::Local { initializer, .. } => { - if let Some(initializer) = initializer { - Self::resolve_emit_expr(backend, plan, function, resolved, initializer)?; - } - } - StructuredWasmStmt::Assign { value, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, value)?; - } - StructuredWasmStmt::If { cond, then_region, else_region, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, cond)?; - Self::resolve_region(backend, plan, function, resolved, then_region)?; - Self::resolve_region(backend, plan, function, resolved, else_region)?; - } - StructuredWasmStmt::Match { scrutinee, arms, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, scrutinee)?; - for arm in arms { - Self::resolve_region(backend, plan, function, resolved, &arm.body)?; - } - } - StructuredWasmStmt::Pattern(init) => { - Self::resolve_binding_init(backend, plan, function, resolved, init)?; - } - StructuredWasmStmt::Return { value, .. } => { - if let Some(value) = value { - Self::resolve_emit_expr(backend, plan, function, resolved, value)?; - } - } - StructuredWasmStmt::Expr { expr, .. } - | StructuredWasmStmt::SetLocal { value: expr, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, expr)?; - } - } - Ok(()) - } - - fn resolve_emit_expr<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - resolved: &mut StructuredWasmResolvedRefs<'db>, - expr: &StructuredWasmExpr<'db>, - ) -> Result<(), Diagnostic> { - match &expr.kind { - StructuredWasmExprKind::Leaf(expr) => { - Self::resolve_kernel_value(backend, plan, function, resolved, expr)?; - } - StructuredWasmExprKind::Eqz { value } - | StructuredWasmExprKind::TeeLocal { value, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, value)?; - } - StructuredWasmExprKind::Select { cond, then_value, else_value } => { - Self::resolve_emit_expr(backend, plan, function, resolved, cond)?; - Self::resolve_emit_expr(backend, plan, function, resolved, then_value)?; - Self::resolve_emit_expr(backend, plan, function, resolved, else_value)?; - } - StructuredWasmExprKind::Block { region, .. } => { - Self::resolve_region(backend, plan, function, resolved, region)?; - } - StructuredWasmExprKind::If { cond, then_region, else_region, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, cond)?; - Self::resolve_region(backend, plan, function, resolved, then_region)?; - Self::resolve_region(backend, plan, function, resolved, else_region)?; - } - StructuredWasmExprKind::Match { scrutinee, arms, .. } => { - Self::resolve_emit_expr(backend, plan, function, resolved, scrutinee)?; - for arm in arms { - Self::resolve_region(backend, plan, function, resolved, &arm.body)?; - } - } - StructuredWasmExprKind::Loop { body, .. } => { - if let Some(body) = body { - Self::resolve_emit_expr(backend, plan, function, resolved, body)?; - } - } - StructuredWasmExprKind::WasmLocal(_) - | StructuredWasmExprKind::Break - | StructuredWasmExprKind::Continue - | StructuredWasmExprKind::Unreachable => {} - } - Ok(()) - } - - fn resolve_kernel_value<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - resolved: &mut StructuredWasmResolvedRefs<'db>, - expr: &FunctionKernelValue<'db>, - ) -> Result<(), Diagnostic> { - match &expr.kind { - FunctionKernelValueKind::Call { target, args } => { - Self::insert_direct_call( - backend, - function, - &mut resolved.direct_calls, - expr.source, - StructuredWasmResolvedDirectCall { - function_index: Self::resolve_direct_call_index( - backend, - plan, - function, - target, - expr.source, - )?, - _marker: std::marker::PhantomData, - }, - )?; - for arg in args { - Self::resolve_kernel_value(backend, plan, function, resolved, arg)?; - } - } - FunctionKernelValueKind::IndirectCall { callee, signature, args } => { - Self::insert_indirect_call( - backend, - function, - &mut resolved.indirect_calls, - expr.source, - StructuredWasmResolvedIndirectCall { - signature: signature.clone(), - type_index: Self::resolve_indirect_type_index( - backend, - plan, - function, - signature, - expr.source, - )?, - }, - )?; - Self::resolve_kernel_value(backend, plan, function, resolved, callee)?; - for arg in args { - Self::resolve_kernel_value(backend, plan, function, resolved, arg)?; - } - } - FunctionKernelValueKind::FunctionValue { target } => { - Self::insert_callable_value( - backend, - function, - &mut resolved.callable_values, - expr.source, - StructuredWasmResolvedCallableValue { - target: target.clone(), - table_slot: Self::resolve_table_slot( - backend, - plan, - function, - target, - expr.source, - )?, - }, - )?; - } - FunctionKernelValueKind::ClosureValue { target, env } => { - let value_target = FunctionValueTarget::Closure(target.clone()); - Self::insert_callable_value( - backend, - function, - &mut resolved.callable_values, - expr.source, - StructuredWasmResolvedCallableValue { - target: value_target.clone(), - table_slot: Self::resolve_table_slot( - backend, - plan, - function, - &value_target, - expr.source, - )?, - }, - )?; - for field in &env.fields { - Self::resolve_kernel_value(backend, plan, function, resolved, &field.value)?; - } - } - FunctionKernelValueKind::Clone { value } - | FunctionKernelValueKind::AddrOffset { base: value, .. } - | FunctionKernelValueKind::MemoryRead { addr: value, .. } - | FunctionKernelValueKind::Prefix { expr: value, .. } - | FunctionKernelValueKind::Field { base: value, .. } => { - Self::resolve_kernel_value(backend, plan, function, resolved, value)?; - } - FunctionKernelValueKind::MemoryWrite { addr, value } - | FunctionKernelValueKind::Binary { lhs: addr, rhs: value, .. } => { - Self::resolve_kernel_value(backend, plan, function, resolved, addr)?; - Self::resolve_kernel_value(backend, plan, function, resolved, value)?; - } - FunctionKernelValueKind::PointerAdd { ptr, count, .. } => { - Self::resolve_kernel_value(backend, plan, function, resolved, ptr)?; - Self::resolve_kernel_value(backend, plan, function, resolved, count)?; - } - FunctionKernelValueKind::StringFromBytes { ptr, len } => { - Self::resolve_kernel_value(backend, plan, function, resolved, ptr)?; - Self::resolve_kernel_value(backend, plan, function, resolved, len)?; - } - FunctionKernelValueKind::Array { items, .. } - | FunctionKernelValueKind::VariantCall { args: items, .. } => { - for item in items { - Self::resolve_kernel_value(backend, plan, function, resolved, item)?; - } - } - FunctionKernelValueKind::ArrayRepeat { value, len, .. } => { - Self::resolve_kernel_value(backend, plan, function, resolved, value)?; - Self::resolve_kernel_value(backend, plan, function, resolved, len)?; - } - FunctionKernelValueKind::Tuple { fields } - | FunctionKernelValueKind::Struct { fields } => { - for field in fields { - Self::resolve_kernel_value(backend, plan, function, resolved, &field.value)?; - } - } - FunctionKernelValueKind::Union { value, .. } => { - if let Some(value) = value { - Self::resolve_kernel_value(backend, plan, function, resolved, value)?; - } - } - FunctionKernelValueKind::Local(_) - | FunctionKernelValueKind::Capture(_) - | FunctionKernelValueKind::Bool(_) - | FunctionKernelValueKind::Int(_) - | FunctionKernelValueKind::Float(_) - | FunctionKernelValueKind::String(_) - | FunctionKernelValueKind::Char(_) - | FunctionKernelValueKind::Unit - | FunctionKernelValueKind::StackAddr { .. } - | FunctionKernelValueKind::VariantValue { .. } => {} - FunctionKernelValueKind::Block { .. } - | FunctionKernelValueKind::If { .. } - | FunctionKernelValueKind::Match { .. } - | FunctionKernelValueKind::Loop { .. } - | FunctionKernelValueKind::Break - | FunctionKernelValueKind::Continue => { - return Err(Diagnostic::error( - "internal error: structured Wasm leaf retained kernel control flow during \ - index resolution", - match &function.kind { - FunctionKernelKind::Direct(instance) => { - backend.function_range(instance.location) - } - FunctionKernelKind::Closure(closure) => { - backend.function_range(closure.owner) - } - }, - )); - } - } - Ok(()) - } - - fn resolve_direct_call_index<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - target: &BackendCallTarget<'db>, - source: ExprId, - ) -> Result { - match &target.callable { - BackendCallable::Runtime(runtime) => { - plan.sections.runtime_function_indices.get(runtime).copied() - } - BackendCallable::StageIntrinsic(intrinsic) => { - plan.sections.stage_function_indices.get(intrinsic).copied() - } - BackendCallable::Function(instance) => { - plan.sections.direct_function_indices.get(instance).copied() - } - } - .ok_or_else(|| { - Self::error_for( - backend, - function, - format!( - "internal error: missing late-resolved direct call index for expr {:?}", - source - ), - ) - }) - } - - fn resolve_indirect_type_index<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - signature: &FunctionSignature, - source: ExprId, - ) -> Result { - plan.sections.callable_type_indices.get(signature).copied().ok_or_else(|| { - Self::error_for( - backend, - function, - format!( - "internal error: missing late-resolved indirect-call type index for expr {:?}", - source - ), - ) - }) - } - - fn resolve_table_slot<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - target: &FunctionValueTarget<'db>, - source: ExprId, - ) -> Result { - plan.sections.table_slots.get(target).copied().ok_or_else(|| { - Self::error_for( - backend, - function, - format!( - "internal error: missing late-resolved callable table slot for expr {:?}", - source - ), - ) - }) - } - - fn insert_direct_call<'db>( - backend: &Backend<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - map: &mut FxHashMap>, - source: ExprId, - value: StructuredWasmResolvedDirectCall<'db>, - ) -> Result<(), Diagnostic> { - if let Some(existing) = map.get(&source) - && existing != &value - { - return Err(Self::error_for( - backend, - function, - "internal error: direct call resolution produced inconsistent indices", - )); - } - map.insert(source, value); - Ok(()) - } - - fn insert_indirect_call<'db>( - backend: &Backend<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - map: &mut FxHashMap, - source: ExprId, - value: StructuredWasmResolvedIndirectCall, - ) -> Result<(), Diagnostic> { - if let Some(existing) = map.get(&source) - && existing != &value - { - return Err(Self::error_for( - backend, - function, - "internal error: indirect call resolution produced inconsistent indices", - )); - } - map.insert(source, value); - Ok(()) - } - - fn insert_callable_value<'db>( - backend: &Backend<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - map: &mut FxHashMap>, - source: ExprId, - value: StructuredWasmResolvedCallableValue<'db>, - ) -> Result<(), Diagnostic> { - if let Some(existing) = map.get(&source) - && existing != &value - { - return Err(Self::error_for( - backend, - function, - "internal error: callable-value resolution produced inconsistent table slots", - )); - } - map.insert(source, value); - Ok(()) - } - - fn error_for<'db>( - backend: &Backend<'db>, - function: &super::function_codegen_ir::StructuredWasmFunction<'db>, - message: impl Into, - ) -> Diagnostic { - let range = match &function.kind { - FunctionKernelKind::Direct(instance) => backend.function_range(instance.location), - FunctionKernelKind::Closure(closure) => backend.function_range(closure.owner), - }; - Diagnostic::error(message.into(), range) - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "function_wasm_ir_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {:?}", diagnostics); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected backend diagnostics: {:?}", - backend.diagnostics - ); - backend - } - - #[test] - fn structured_wasm_ir_carries_explicit_ownership_ops() { - let backend = compiler_for_fixture( - r#" -export fun main(): [str] { - ["x", "y"]; - val xs = ["a", "b"]; - xs -} -"#, - ); - let plan = backend.build_module_plan().expect("module plan"); - let function = plan - .function_wasm_ir - .as_ref() - .expect("structured wasm bundle") - .direct_functions - .iter() - .find(|function| function.debug_name.contains("main")) - .expect("main function"); - let body = function.body.as_ref().expect("body"); - let StructuredWasmExprKind::Block { region, .. } = &body.kind else { - panic!("expected structured block body"); - }; - assert!( - region.stmts.iter().any(|stmt| match stmt { - StructuredWasmStmt::Local { ownership_ops, .. } - | StructuredWasmStmt::If { ownership_ops, .. } - | StructuredWasmStmt::Match { ownership_ops, .. } - | StructuredWasmStmt::Return { ownership_ops, .. } - | StructuredWasmStmt::Expr { ownership_ops, .. } => { - ownership_ops.contains(&StructuredWasmOwnershipOp::Release) - || ownership_ops.contains(&StructuredWasmOwnershipOp::Move) - } - StructuredWasmStmt::Assign { .. } - | StructuredWasmStmt::Pattern(_) - | StructuredWasmStmt::SetLocal { .. } => false, - }), - "expected structured wasm stmts to carry explicit ownership ops" - ); - assert!( - function.body_ownership_ops.contains(&StructuredWasmOwnershipOp::Move), - "expected explicit body ownership ops for the return value" - ); - } - - #[test] - fn structured_wasm_ir_resolves_direct_indirect_and_callable_indices() { - let backend = compiler_for_fixture( - r#" -fun apply(f: fun(int) -> int, x: int): int { - f(x) -} - -export fun main(): int { - val offset = 1 - val add: fun(int) -> int = { value in value + offset } - apply(add, 41) -} -"#, - ); - let plan = backend.build_module_plan().expect("module plan"); - - let apply = plan - .function_wasm_ir - .as_ref() - .expect("structured wasm bundle") - .direct_functions - .iter() - .find(|function| function.debug_name.contains("apply")) - .expect("apply function"); - assert!( - !apply.resolved().indirect_calls.is_empty(), - "expected indirect call type indices to resolve in structured Wasm IR" - ); - - let main = plan - .function_wasm_ir - .as_ref() - .expect("structured wasm bundle") - .direct_functions - .iter() - .find(|function| function.debug_name.contains("main")) - .expect("main function"); - assert!( - !main.resolved().direct_calls.is_empty(), - "expected direct call indices to resolve in structured Wasm IR" - ); - assert!( - !main.resolved().callable_values.is_empty(), - "expected callable table slots to resolve in structured Wasm IR" - ); - - let closure = plan - .function_wasm_ir - .as_ref() - .expect("structured wasm bundle") - .closures - .first() - .expect("closure"); - assert!( - closure.resolved().indirect_calls.is_empty(), - "closure body should not invent unrelated indirect call sites" - ); - } -} diff --git a/crates/mitki-backend-wasm/src/lowering/mod.rs b/crates/mitki-backend-wasm/src/lowering/mod.rs deleted file mode 100644 index cb20a17..0000000 --- a/crates/mitki-backend-wasm/src/lowering/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub(super) mod function_codegen_ir; -pub(super) mod function_kernel; -pub(super) mod function_legalize; -pub(super) mod function_ownership; -pub(super) mod function_wasm_ir; -pub(super) mod wrapper_mir; - -use super::{boundary, planning as plan, *}; diff --git a/crates/mitki-backend-wasm/src/lowering/wrapper_mir.rs b/crates/mitki-backend-wasm/src/lowering/wrapper_mir.rs deleted file mode 100644 index dd062a4..0000000 --- a/crates/mitki-backend-wasm/src/lowering/wrapper_mir.rs +++ /dev/null @@ -1,2505 +0,0 @@ -#[cfg(test)] -use std::fmt::Write as _; -#[cfg(test)] -use std::sync::Arc; - -use mitki_abi::{FunctionSignature as AbiV2FunctionSignature, SigId, TransportClass}; -use mitki_errors::Diagnostic; -use mitki_hir::ty::{Ty, TyKind}; -use rustc_hash::FxHashMap; -use wasm_encoder::ValType; - -use super::super::boundary::{BoundarySig, BoundarySlot, InternalSig, transport_has_wasm_lane}; -use super::plan::ModulePlan; -use super::*; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct WrapperMirId(pub(in crate::backend) u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct PlaceId(pub(in crate::backend) u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct ValueId(pub(in crate::backend) u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct WrapperMirBundle<'db> { - pub(in crate::backend) imports: Vec>, - pub(in crate::backend) callable_adapters: Vec>, - pub(in crate::backend) trampolines: Vec>, - pub(in crate::backend) exports: Vec>, - pub(in crate::backend) import_indices: FxHashMap, usize>, - pub(in crate::backend) callable_adapter_indices: FxHashMap, usize>, - pub(in crate::backend) trampoline_indices: FxHashMap, - pub(in crate::backend) export_indices: FxHashMap, usize>, -} - -impl<'db> WrapperMirBundle<'db> { - pub(in crate::backend) fn import( - &self, - instance: &InstanceKey<'db>, - ) -> Option<&WrapperMirFunction<'db>> { - self.import_indices.get(instance).and_then(|&index| self.imports.get(index)) - } - - pub(in crate::backend) fn callable_adapter( - &self, - instance: &InstanceKey<'db>, - ) -> Option<&WrapperMirFunction<'db>> { - self.callable_adapter_indices - .get(instance) - .and_then(|&index| self.callable_adapters.get(index)) - } - - pub(in crate::backend) fn trampoline( - &self, - signature_id: SigId, - ) -> Option<&WrapperMirFunction<'db>> { - self.trampoline_indices.get(&signature_id).and_then(|&index| self.trampolines.get(index)) - } - - pub(in crate::backend) fn export( - &self, - instance: &InstanceKey<'db>, - ) -> Option<&WrapperMirFunction<'db>> { - self.export_indices.get(instance).and_then(|&index| self.exports.get(index)) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct WrapperMirFunction<'db> { - pub(in crate::backend) id: WrapperMirId, - pub(in crate::backend) kind: WrapperKind<'db>, - pub(in crate::backend) debug_name: String, - pub(in crate::backend) signature: WrapperMirSignature<'db>, - pub(in crate::backend) places: Vec, - pub(in crate::backend) body: Region<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum WrapperKind<'db> { - ImportThunk { - instance: InstanceKey<'db>, - metadata_index: usize, - }, - ExportWrapper { - instance: InstanceKey<'db>, - metadata_index: usize, - }, - HandleInvokeTrampoline { - instance: InstanceKey<'db>, - metadata_index: usize, - signature_id: SigId, - }, - FunctionValueWrapper { - instance: InstanceKey<'db>, - }, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct WrapperMirSignature<'db> { - pub(in crate::backend) wasm_params: Vec, - pub(in crate::backend) wasm_results: Vec, - pub(in crate::backend) internal: Option, - pub(in crate::backend) boundary: Option>, - pub(in crate::backend) callable: Option, - pub(in crate::backend) metadata_index: Option, - pub(in crate::backend) signature_id: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum WrapperPlaceTy { - I32, - I64, - F64, - Unit, -} - -impl WrapperPlaceTy { - pub(in crate::backend) fn value_type(self) -> Option { - match self { - Self::I32 => Some(ValType::I32), - Self::I64 => Some(ValType::I64), - Self::F64 => Some(ValType::F64), - Self::Unit => None, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum WrapperScratchKind { - NodeOffset, - Cursor, - Index, - Len, - Count, - Bytes, - BaseId, - TempPtr, - TempPtrAux, - ChildCount, - ChildBytes, - HandleCount, - HandleIndex, - HandleCursor, - F64Temp, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum PlaceKind { - Local, - Scratch(WrapperScratchKind), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct PlaceDecl { - pub(in crate::backend) id: PlaceId, - pub(in crate::backend) label: String, - pub(in crate::backend) ty: WrapperPlaceTy, - pub(in crate::backend) kind: PlaceKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct Region<'db> { - pub(in crate::backend) stmts: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum Stmt<'db> { - Let { - value: ValueId, - ty: WrapperPlaceTy, - rhs: RValue<'db>, - }, - Store { - place: PlaceId, - value: Operand, - }, - #[allow(dead_code)] - If { - cond: Operand, - then_region: Region<'db>, - else_region: Region<'db>, - result_places: Vec, - }, - Eval { - rhs: RValue<'db>, - }, - Return { - values: Vec, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(in crate::backend) enum WrapperHandleField { - Slot, - Env, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum Operand { - Value(ValueId), - Place(PlaceId), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum WrapperCallTarget<'db> { - DirectFunction(InstanceKey<'db>), - RawImport(InstanceKey<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum RValue<'db> { - ReadParam { - param: u32, - }, - NormalizeBool { - operand: Operand, - }, - #[allow(dead_code)] - LoadPlace { - place: PlaceId, - }, - CallDirect { - target: WrapperCallTarget<'db>, - wasm_results: Vec, - args: Vec, - result: Option, - }, - CallIndirect { - signature: FunctionSignature, - wasm_results: Vec, - env: Operand, - table_index: Operand, - args: Vec, - result: Option, - }, - EncodeImmediate { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - DecodeImmediate { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - EncodeCanonical { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - DecodeCanonical { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - WrapHandle { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - UnwrapHandle { - source: PlaceId, - dest: PlaceId, - slot: BoundarySlot<'db>, - }, - RetainNestedHandles { - place: PlaceId, - ty: Ty<'db>, - }, - ReleaseValue { - place: PlaceId, - abi: AbiTy, - }, - ReleaseCanonicalBlob { - place: PlaceId, - }, - ReleaseHandleObject { - place: PlaceId, - }, - AllocTempBuffer { - place: PlaceId, - layout: AggregateLayout, - }, - DeallocTempBuffer { - place: PlaceId, - layout: AggregateLayout, - }, - ZeroTempBuffer { - place: PlaceId, - size: u32, - }, - ReadHandleField { - handle: Operand, - field: WrapperHandleField, - }, -} - -pub(in crate::backend) struct WrapperMirBuilder; -pub(in crate::backend) struct WrapperMirValidator; - -pub(in crate::backend) fn abi_v2_wasm_signature( - graph: &mitki_abi::SemanticTypeGraph, - signature: &AbiV2FunctionSignature, -) -> Result<(Vec, Vec), Diagnostic> { - let shape = mitki_abi::typed_signature_wasm_shape(graph, signature).map_err(|error| { - Diagnostic::error(error.to_string(), mitki_errors::TextRange::default()) - })?; - Ok(( - shape.params.into_iter().map(contract_val_type_to_wasm).collect(), - shape.results.into_iter().map(contract_val_type_to_wasm).collect(), - )) -} - -pub(in crate::backend) fn abi_v2_transport_lane( - graph: &mitki_abi::SemanticTypeGraph, - transport: &mitki_abi::TransportRef, -) -> Result, Diagnostic> { - mitki_abi::transport_wasm_lane(graph, transport) - .map(|lane| lane.map(contract_val_type_to_wasm)) - .map_err(|error| Diagnostic::error(error.to_string(), mitki_errors::TextRange::default())) -} - -fn contract_val_type_to_wasm(value_type: mitki_abi::ContractValType) -> ValType { - match value_type { - mitki_abi::ContractValType::I32 => ValType::I32, - mitki_abi::ContractValType::I64 => ValType::I64, - mitki_abi::ContractValType::F32 => ValType::F32, - mitki_abi::ContractValType::F64 => ValType::F64, - mitki_abi::ContractValType::V128 => ValType::V128, - mitki_abi::ContractValType::Ref => ValType::I32, - } -} - -struct WrapperFunctionBuilder<'db> { - id: WrapperMirId, - kind: WrapperKind<'db>, - debug_name: String, - signature: WrapperMirSignature<'db>, - places: Vec, - place_by_label: FxHashMap, - stmts: Vec>, - next_place: u32, - next_value: u32, -} - -impl<'db> WrapperFunctionBuilder<'db> { - fn new( - id: WrapperMirId, - kind: WrapperKind<'db>, - debug_name: String, - signature: WrapperMirSignature<'db>, - ) -> Self { - Self { - id, - kind, - debug_name, - signature, - places: Vec::new(), - place_by_label: FxHashMap::default(), - stmts: Vec::new(), - next_place: 0, - next_value: 0, - } - } - - fn place(&mut self, label: impl Into, ty: WrapperPlaceTy, kind: PlaceKind) -> PlaceId { - let label = label.into(); - let id = PlaceId(self.next_place); - self.next_place += 1; - self.place_by_label.insert(label.clone(), id); - self.places.push(PlaceDecl { id, label, ty, kind }); - id - } - - fn find_place(&self, label: &str) -> PlaceId { - self.place_by_label[label] - } - - fn let_value(&mut self, ty: WrapperPlaceTy, rhs: RValue<'db>) -> ValueId { - let value = ValueId(self.next_value); - self.next_value += 1; - self.stmts.push(Stmt::Let { value, ty, rhs }); - value - } - - fn read_param_into(&mut self, place: PlaceId, param: u32, ty: WrapperPlaceTy) { - let value = self.let_value(ty, RValue::ReadParam { param }); - self.stmts.push(Stmt::Store { place, value: Operand::Value(value) }); - } - - fn normalize_place(&mut self, place: PlaceId) { - let value = self.let_value( - WrapperPlaceTy::I32, - RValue::NormalizeBool { operand: Operand::Place(place) }, - ); - self.stmts.push(Stmt::Store { place, value: Operand::Value(value) }); - } - - fn eval(&mut self, rhs: RValue<'db>) { - self.stmts.push(Stmt::Eval { rhs }); - } - - fn ret(&mut self, values: Vec) { - self.stmts.push(Stmt::Return { values }); - } - - fn finish(self) -> WrapperMirFunction<'db> { - WrapperMirFunction { - id: self.id, - kind: self.kind, - debug_name: self.debug_name, - signature: self.signature, - places: self.places, - body: Region { stmts: self.stmts }, - } - } -} - -fn place_ty_for_abi(abi: &AbiTy) -> WrapperPlaceTy { - match abi { - AbiTy::Scalar(BackendTy::I64) => WrapperPlaceTy::I64, - AbiTy::Scalar(BackendTy::Float) => WrapperPlaceTy::F64, - AbiTy::Scalar(BackendTy::Unit) => WrapperPlaceTy::Unit, - AbiTy::Scalar(BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_)) - | AbiTy::Aggregate(_) => WrapperPlaceTy::I32, - } -} - -fn place_ty_for_val_type(value_type: ValType) -> WrapperPlaceTy { - match value_type { - ValType::I32 => WrapperPlaceTy::I32, - ValType::I64 => WrapperPlaceTy::I64, - ValType::F64 => WrapperPlaceTy::F64, - _ => WrapperPlaceTy::I32, - } -} - -fn place_ty_for_transport( - graph: &mitki_abi::SemanticTypeGraph, - transport: &mitki_abi::TransportRef, -) -> Result { - Ok(match abi_v2_transport_lane(graph, transport)? { - None => WrapperPlaceTy::Unit, - Some(ValType::I32) => WrapperPlaceTy::I32, - Some(ValType::I64) => WrapperPlaceTy::I64, - Some(ValType::F64) => WrapperPlaceTy::F64, - Some(_) => WrapperPlaceTy::I32, - }) -} - -fn bind_wrapper_signature_params<'db>( - builder: &mut WrapperFunctionBuilder<'db>, - graph: &mitki_abi::SemanticTypeGraph, - signature: &AbiV2FunctionSignature, -) -> Result, Diagnostic> { - bind_wrapper_signature_params_with_offset(builder, graph, signature, 0) -} - -fn bind_wrapper_signature_params_with_offset<'db>( - builder: &mut WrapperFunctionBuilder<'db>, - graph: &mitki_abi::SemanticTypeGraph, - signature: &AbiV2FunctionSignature, - offset: u32, -) -> Result, Diagnostic> { - let mut params = Vec::with_capacity(signature.params.len()); - let mut wasm_index = offset; - for (ordinal, transport) in signature.params.iter().enumerate() { - let ty = place_ty_for_transport(graph, transport)?; - let place = builder.place(format!("param{ordinal}.lane"), ty, PlaceKind::Local); - if ty != WrapperPlaceTy::Unit { - builder.read_param_into(place, wasm_index, ty); - wasm_index += 1; - } - params.push(place); - } - Ok(params) -} - -fn add_canonical_scratch_places(builder: &mut WrapperFunctionBuilder<'_>) { - let scratches = [ - ("scratch.node_offset", WrapperScratchKind::NodeOffset, WrapperPlaceTy::I32), - ("scratch.cursor", WrapperScratchKind::Cursor, WrapperPlaceTy::I32), - ("scratch.index", WrapperScratchKind::Index, WrapperPlaceTy::I32), - ("scratch.len", WrapperScratchKind::Len, WrapperPlaceTy::I32), - ("scratch.count", WrapperScratchKind::Count, WrapperPlaceTy::I32), - ("scratch.bytes", WrapperScratchKind::Bytes, WrapperPlaceTy::I32), - ("scratch.base_id", WrapperScratchKind::BaseId, WrapperPlaceTy::I32), - ("scratch.temp_ptr", WrapperScratchKind::TempPtr, WrapperPlaceTy::I32), - ("scratch.temp_ptr_aux", WrapperScratchKind::TempPtrAux, WrapperPlaceTy::I32), - ("scratch.child_count", WrapperScratchKind::ChildCount, WrapperPlaceTy::I32), - ("scratch.child_bytes", WrapperScratchKind::ChildBytes, WrapperPlaceTy::I32), - ("scratch.handle_count", WrapperScratchKind::HandleCount, WrapperPlaceTy::I32), - ("scratch.handle_index", WrapperScratchKind::HandleIndex, WrapperPlaceTy::I32), - ("scratch.handle_cursor", WrapperScratchKind::HandleCursor, WrapperPlaceTy::I32), - ("scratch.f64_temp", WrapperScratchKind::F64Temp, WrapperPlaceTy::F64), - ]; - for (label, kind, ty) in scratches { - builder.place(label, ty, PlaceKind::Scratch(kind)); - } -} - -fn maybe_prepare_runtime_place( - builder: &mut WrapperFunctionBuilder<'_>, - place: PlaceId, - abi: &AbiTy, -) { - if let AbiTy::Aggregate(layout) = abi { - builder.eval(RValue::AllocTempBuffer { place, layout: (**layout).clone() }); - builder.eval(RValue::ZeroTempBuffer { place, size: layout.size }); - } -} - -fn release_runtime_places( - builder: &mut WrapperFunctionBuilder<'_>, - abis: &[AbiTy], - places: &[PlaceId], -) { - for (abi, place) in abis.iter().zip(places.iter().copied()) { - release_runtime_place(builder, abi, place); - } -} - -fn release_runtime_place(builder: &mut WrapperFunctionBuilder<'_>, abi: &AbiTy, place: PlaceId) { - builder.eval(RValue::ReleaseValue { place, abi: abi.clone() }); - if let AbiTy::Aggregate(layout) = abi { - builder.eval(RValue::DeallocTempBuffer { place, layout: (**layout).clone() }); - } -} - -fn call_args_for_internal_signature( - signature: &FunctionSignature, - result_ptr_place: Option, - arg_places: &[PlaceId], -) -> Vec { - let mut args = Vec::new(); - if signature.result.is_aggregate() - && let Some(result_ptr_place) = result_ptr_place - { - args.push(Operand::Place(result_ptr_place)); - } - args.extend(arg_places.iter().copied().map(Operand::Place)); - args -} - -impl WrapperMirBuilder { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - ) -> Result, Diagnostic> { - let mut bundle = WrapperMirBundle { - imports: Vec::new(), - callable_adapters: Vec::new(), - trampolines: Vec::new(), - exports: Vec::new(), - import_indices: FxHashMap::default(), - callable_adapter_indices: FxHashMap::default(), - trampoline_indices: FxHashMap::default(), - export_indices: FxHashMap::default(), - }; - let mut next_id = 0u32; - - for import in &plan.boundary.imports { - let function = build_import_wrapper_mir(backend, plan, import, WrapperMirId(next_id))?; - bundle.import_indices.insert(import.instance.clone(), bundle.imports.len()); - bundle.imports.push(function); - next_id += 1; - } - for instance in &plan.reachability.functions { - let function = - build_function_value_wrapper_mir(backend, plan, instance, WrapperMirId(next_id))?; - bundle - .callable_adapter_indices - .insert(instance.clone(), bundle.callable_adapters.len()); - bundle.callable_adapters.push(function); - next_id += 1; - } - for trampoline in &plan.callables.invoke_trampolines { - let function = - build_handle_trampoline_mir(backend, plan, trampoline, WrapperMirId(next_id))?; - bundle.trampoline_indices.insert(trampoline.signature_id, bundle.trampolines.len()); - bundle.trampolines.push(function); - next_id += 1; - } - for export in &plan.boundary.exports { - let function = build_export_wrapper_mir(backend, plan, export, WrapperMirId(next_id))?; - bundle.export_indices.insert(export.instance.clone(), bundle.exports.len()); - bundle.exports.push(function); - next_id += 1; - } - - Ok(bundle) - } -} - -impl WrapperMirValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - bundle: &WrapperMirBundle<'db>, - ) -> Result<(), Diagnostic> { - for function in bundle - .imports - .iter() - .chain(bundle.callable_adapters.iter()) - .chain(bundle.trampolines.iter()) - .chain(bundle.exports.iter()) - { - validate_wrapper_mir_function(backend, plan, function)?; - } - Ok(()) - } -} - -fn build_function_value_wrapper_mir<'db>( - backend: &Backend<'db>, - _plan: &ModulePlan<'db>, - instance: &InstanceKey<'db>, - id: WrapperMirId, -) -> Result, Diagnostic> { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let signature = backend.function_signature(instance, function, inference)?; - let debug_name = instance - .location - .source(backend.db) - .name() - .map_or("".to_owned(), |name| name.as_str().to_owned()); - let signature_strategy = backend.signature_strategy(); - let callable_lowering = backend.callable_lowering_strategy(); - let memory_model = backend.memory_model_strategy(); - let callable_signature = - callable_lowering.callable_signature(signature_strategy, memory_model, &signature); - let mut builder = WrapperFunctionBuilder::new( - id, - WrapperKind::FunctionValueWrapper { instance: instance.clone() }, - format!("callable {debug_name}"), - WrapperMirSignature { - wasm_params: callable_signature.params.clone(), - wasm_results: callable_signature.results.clone(), - internal: Some(InternalSig::from_function_signature(signature.clone())), - boundary: None, - callable: Some(signature.clone()), - metadata_index: None, - signature_id: None, - }, - ); - - let mut next_param = 0u32; - if let Some(env_lane) = callable_lowering.environment_lane(memory_model) { - let env_ty = place_ty_for_val_type(env_lane); - let env_place = builder.place("env", env_ty, PlaceKind::Local); - builder.read_param_into(env_place, next_param, env_ty); - next_param += 1; - } - let result_ptr_place = if signature.result.is_aggregate() { - let ptr_ty = place_ty_for_val_type(signature_strategy.result_pointer_lane()); - let place = builder.place("result_ptr", ptr_ty, PlaceKind::Local); - builder.read_param_into(place, next_param, ptr_ty); - next_param += 1; - Some(place) - } else { - None - }; - - let mut arg_places = Vec::with_capacity(signature.params.len()); - for (index, abi) in signature.params.iter().enumerate() { - let ty = place_ty_for_abi(abi); - let place = builder.place(format!("param{index}"), ty, PlaceKind::Local); - if ty != WrapperPlaceTy::Unit { - builder.read_param_into(place, next_param, ty); - next_param += 1; - if matches!(abi, AbiTy::Scalar(BackendTy::Bool)) { - builder.normalize_place(place); - } - } - arg_places.push(place); - } - - let call_result_place = if signature.result.is_aggregate() - || matches!(signature.result, AbiTy::Scalar(BackendTy::Unit)) - { - None - } else { - Some(builder.place("result", place_ty_for_abi(&signature.result), PlaceKind::Local)) - }; - builder.eval(RValue::CallDirect { - target: WrapperCallTarget::DirectFunction(instance.clone()), - wasm_results: signature_strategy - .direct_signature(&signature) - .results - .into_iter() - .map(place_ty_for_val_type) - .collect(), - args: call_args_for_internal_signature(&signature, result_ptr_place, &arg_places), - result: if signature.result.is_aggregate() { None } else { call_result_place }, - }); - - let mut return_values = Vec::new(); - if let Some(result_place) = call_result_place { - if matches!(signature.result, AbiTy::Scalar(BackendTy::Bool)) { - builder.normalize_place(result_place); - } - return_values.push(Operand::Place(result_place)); - } - builder.ret(return_values); - Ok(builder.finish()) -} - -fn build_import_wrapper_mir<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - import: &boundary::ImportBoundaryPlan<'db>, - id: WrapperMirId, -) -> Result, Diagnostic> { - let entry = &plan.boundary.instances[import.metadata_index]; - let built = &plan.abi_preview.functions[import.metadata_index]; - let abi_signature = &plan.abi_preview.graph.signatures[built.signature_id.0 as usize]; - let signature_strategy = backend.signature_strategy(); - let lowered_internal = - signature_strategy.direct_signature(&entry.signature.internal.as_function_signature()); - let mut builder = WrapperFunctionBuilder::new( - id, - WrapperKind::ImportThunk { - instance: import.instance.clone(), - metadata_index: import.metadata_index, - }, - format!("import {}", import.logical_name), - WrapperMirSignature { - wasm_params: lowered_internal.params.clone(), - wasm_results: lowered_internal.results.clone(), - internal: Some(entry.signature.internal.clone()), - boundary: Some(import.wrapper.signature.clone()), - callable: None, - metadata_index: Some(import.metadata_index), - signature_id: Some(built.signature_id), - }, - ); - - let internal_signature = entry.signature.internal.as_function_signature(); - let mut wasm_param = 0u32; - let result_ptr_place = if internal_signature.result.is_aggregate() { - let ptr_ty = place_ty_for_val_type(signature_strategy.result_pointer_lane()); - let place = builder.place("result_ptr", ptr_ty, PlaceKind::Local); - builder.read_param_into(place, wasm_param, ptr_ty); - wasm_param += 1; - Some(place) - } else { - None - }; - let mut internal_param_places = Vec::with_capacity(internal_signature.params.len()); - for (index, abi) in internal_signature.params.iter().enumerate() { - let ty = place_ty_for_abi(abi); - let place = builder.place(format!("param{index}"), ty, PlaceKind::Local); - if ty != WrapperPlaceTy::Unit { - builder.read_param_into(place, wasm_param, ty); - wasm_param += 1; - } - internal_param_places.push(place); - } - add_canonical_scratch_places(&mut builder); - - let mut call_args = Vec::new(); - for (index, slot) in import.wrapper.signature.params.iter().enumerate() { - let source_place = internal_param_places[index]; - match slot.transport.transport_class { - TransportClass::Immediate => { - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(source_place); - } - call_args.push(Operand::Place(source_place)); - } - TransportClass::CanonicalValue => { - let blob = builder.place( - format!("param{index}.blob"), - WrapperPlaceTy::I32, - PlaceKind::Local, - ); - builder.eval(RValue::EncodeCanonical { - source: source_place, - dest: blob, - slot: slot.clone(), - }); - builder.eval(RValue::RetainNestedHandles { place: blob, ty: slot.semantic_ty }); - call_args.push(Operand::Place(blob)); - } - TransportClass::CapabilityHandle => { - let handle = builder.place( - format!("param{index}.handle"), - WrapperPlaceTy::I32, - PlaceKind::Local, - ); - builder.eval(RValue::WrapHandle { - source: source_place, - dest: handle, - slot: slot.clone(), - }); - call_args.push(Operand::Place(handle)); - } - } - } - - let call_result_place = import.wrapper.signature.results.first().map(|slot| { - let ty = place_ty_for_transport(&plan.abi_preview.graph, &abi_signature.result) - .unwrap_or_else(|diagnostic| { - panic!("transport lane should lower: {}", diagnostic.message()) - }); - builder.place( - match slot.transport.transport_class { - TransportClass::Immediate => "result.lane", - TransportClass::CanonicalValue => "result.blob", - TransportClass::CapabilityHandle => "result.handle", - }, - ty, - PlaceKind::Local, - ) - }); - builder.eval(RValue::CallDirect { - target: WrapperCallTarget::RawImport(import.instance.clone()), - wasm_results: abi_v2_transport_lane(&plan.abi_preview.graph, &abi_signature.result)? - .into_iter() - .map(place_ty_for_val_type) - .collect(), - args: call_args, - result: call_result_place, - }); - - let mut return_values = Vec::new(); - if let Some(slot) = import.wrapper.signature.results.first() - && let Some(result_place) = call_result_place - { - match slot.transport.transport_class { - TransportClass::Immediate => { - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(result_place); - } - if result_ptr_place.is_none() { - return_values.push(Operand::Place(result_place)); - } - } - TransportClass::CanonicalValue => { - if let Some(result_ptr_place) = result_ptr_place { - builder.eval(RValue::DecodeCanonical { - source: result_place, - dest: result_ptr_place, - slot: slot.clone(), - }); - } else if let Some(result_abi) = entry.signature.internal.results.first() { - let runtime_result = builder.place( - "result.runtime", - place_ty_for_abi(result_abi), - PlaceKind::Local, - ); - builder.eval(RValue::DecodeCanonical { - source: result_place, - dest: runtime_result, - slot: slot.clone(), - }); - return_values.push(Operand::Place(runtime_result)); - } - } - TransportClass::CapabilityHandle => { - if let Some(result_ptr_place) = result_ptr_place { - builder.eval(RValue::UnwrapHandle { - source: result_place, - dest: result_ptr_place, - slot: slot.clone(), - }); - } else if let Some(result_abi) = entry.signature.internal.results.first() { - let runtime_result = builder.place( - "result.runtime", - place_ty_for_abi(result_abi), - PlaceKind::Local, - ); - builder.eval(RValue::UnwrapHandle { - source: result_place, - dest: runtime_result, - slot: slot.clone(), - }); - return_values.push(Operand::Place(runtime_result)); - } - } - } - } - for (index, abi) in internal_signature.params.iter().enumerate() { - release_runtime_place(&mut builder, abi, internal_param_places[index]); - match import.wrapper.signature.params[index].transport.transport_class { - TransportClass::CanonicalValue => { - builder.eval(RValue::ReleaseCanonicalBlob { - place: builder.find_place(&format!("param{index}.blob")), - }); - } - TransportClass::CapabilityHandle => { - builder.eval(RValue::ReleaseHandleObject { - place: builder.find_place(&format!("param{index}.handle")), - }); - } - TransportClass::Immediate => {} - } - } - builder.ret(return_values); - Ok(builder.finish()) -} - -fn build_export_wrapper_mir<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - export: &boundary::ExportBoundaryPlan<'db>, - id: WrapperMirId, -) -> Result, Diagnostic> { - let entry = &plan.boundary.instances[export.metadata_index]; - let built = &plan.abi_preview.functions[export.metadata_index]; - let abi_signature = &plan.abi_preview.graph.signatures[built.signature_id.0 as usize]; - let (wasm_params, wasm_results) = - abi_v2_wasm_signature(&plan.abi_preview.graph, abi_signature)?; - let mut builder = WrapperFunctionBuilder::new( - id, - WrapperKind::ExportWrapper { - instance: export.instance.clone(), - metadata_index: export.metadata_index, - }, - format!("export {}", export.logical_name), - WrapperMirSignature { - wasm_params, - wasm_results, - internal: Some(entry.signature.internal.clone()), - boundary: Some(export.wrapper.signature.clone()), - callable: None, - metadata_index: Some(export.metadata_index), - signature_id: Some(built.signature_id), - }, - ); - - let param_places = - bind_wrapper_signature_params(&mut builder, &plan.abi_preview.graph, abi_signature)?; - add_canonical_scratch_places(&mut builder); - - let mut runtime_param_places = Vec::with_capacity(entry.signature.params.len()); - for (index, slot) in entry.signature.params.iter().enumerate() { - let runtime_place = builder.place( - format!("runtime.param{index}"), - place_ty_for_abi(&entry.signature.internal.params[index]), - PlaceKind::Local, - ); - match slot.transport.transport_class { - TransportClass::Immediate => { - builder.eval(RValue::DecodeImmediate { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(runtime_place); - } - } - TransportClass::CanonicalValue => { - maybe_prepare_runtime_place( - &mut builder, - runtime_place, - &entry.signature.internal.params[index], - ); - builder.eval(RValue::DecodeCanonical { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - } - TransportClass::CapabilityHandle => { - maybe_prepare_runtime_place( - &mut builder, - runtime_place, - &entry.signature.internal.params[index], - ); - builder.eval(RValue::UnwrapHandle { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - } - } - runtime_param_places.push(runtime_place); - } - - let call_result_place = entry.signature.internal.results.first().map(|abi| { - let place = builder.place("runtime.result", place_ty_for_abi(abi), PlaceKind::Local); - maybe_prepare_runtime_place(&mut builder, place, abi); - place - }); - let internal_signature = entry.signature.internal.as_function_signature(); - let lowered_internal = backend.signature_strategy().direct_signature(&internal_signature); - builder.eval(RValue::CallDirect { - target: WrapperCallTarget::DirectFunction(export.instance.clone()), - wasm_results: lowered_internal.results.into_iter().map(place_ty_for_val_type).collect(), - args: call_args_for_internal_signature( - &internal_signature, - call_result_place, - &runtime_param_places, - ), - result: if internal_signature.result.is_aggregate() { None } else { call_result_place }, - }); - - let mut return_values = Vec::new(); - if let Some(slot) = entry.signature.results.first() - && let Some(result_place) = call_result_place - { - let outward_place = builder.place( - "result.out", - place_ty_for_transport(&plan.abi_preview.graph, &abi_signature.result)?, - PlaceKind::Local, - ); - match slot.transport.transport_class { - TransportClass::Immediate => { - builder.eval(RValue::EncodeImmediate { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(outward_place); - } - } - TransportClass::CanonicalValue => { - builder.eval(RValue::EncodeCanonical { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - builder.eval(RValue::RetainNestedHandles { - place: outward_place, - ty: slot.semantic_ty, - }); - } - TransportClass::CapabilityHandle => { - builder.eval(RValue::WrapHandle { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - } - } - return_values.push(Operand::Place(outward_place)); - } - - release_runtime_places(&mut builder, &internal_signature.params, &runtime_param_places); - if let Some(result_place) = call_result_place { - release_runtime_place(&mut builder, &internal_signature.result, result_place); - } - builder.ret(return_values); - Ok(builder.finish()) -} - -fn build_handle_trampoline_mir<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - trampoline: ®istry::CallableTrampolinePlan<'db>, - id: WrapperMirId, -) -> Result, Diagnostic> { - let entry = &plan.boundary.instances[trampoline.metadata_index]; - let built = &plan.abi_preview.functions[trampoline.metadata_index]; - let abi_signature = &plan.abi_preview.graph.signatures[built.signature_id.0 as usize]; - let (wasm_params, wasm_results) = - abi_v2_wasm_signature(&plan.abi_preview.graph, abi_signature)?; - let callable_lowering = backend.callable_lowering_strategy(); - let memory_model = backend.memory_model_strategy(); - if !callable_lowering.uses_table_slots() { - return Err(Diagnostic::error( - format!( - "the target profile `{}` does not support handle-and-table boundary trampolines \ - for callable exports yet", - backend.target_profile().canonical_name() - ), - backend.function_range(trampoline.instance.location), - )); - } - let lowered_boundary = callable_lowering.boundary_invoke_signature( - memory_model, - PhysicalWasmSignature::new(wasm_params, wasm_results), - ); - let mut builder = WrapperFunctionBuilder::new( - id, - WrapperKind::HandleInvokeTrampoline { - instance: trampoline.instance.clone(), - metadata_index: trampoline.metadata_index, - signature_id: trampoline.signature_id, - }, - format!("trampoline {}", entry.logical_name), - WrapperMirSignature { - wasm_params: lowered_boundary.params.clone(), - wasm_results: lowered_boundary.results.clone(), - internal: Some(entry.signature.internal.clone()), - boundary: Some(entry.signature.clone()), - callable: Some(entry.signature.internal.as_function_signature()), - metadata_index: Some(trampoline.metadata_index), - signature_id: Some(trampoline.signature_id), - }, - ); - - let handle_ty = place_ty_for_val_type(callable_lowering.handle_lane(memory_model)); - let handle_place = builder.place("handle", handle_ty, PlaceKind::Local); - builder.read_param_into(handle_place, 0, handle_ty); - let boundary_param_offset = u32::from(callable_lowering.uses_table_slots()); - let param_places = bind_wrapper_signature_params_with_offset( - &mut builder, - &plan.abi_preview.graph, - abi_signature, - boundary_param_offset, - )?; - add_canonical_scratch_places(&mut builder); - - let mut runtime_param_places = Vec::with_capacity(entry.signature.params.len()); - for (index, slot) in entry.signature.params.iter().enumerate() { - let runtime_place = builder.place( - format!("runtime.param{index}"), - place_ty_for_abi(&entry.signature.internal.params[index]), - PlaceKind::Local, - ); - match slot.transport.transport_class { - TransportClass::Immediate => { - builder.eval(RValue::DecodeImmediate { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(runtime_place); - } - } - TransportClass::CanonicalValue => { - maybe_prepare_runtime_place( - &mut builder, - runtime_place, - &entry.signature.internal.params[index], - ); - builder.eval(RValue::DecodeCanonical { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - } - TransportClass::CapabilityHandle => { - maybe_prepare_runtime_place( - &mut builder, - runtime_place, - &entry.signature.internal.params[index], - ); - builder.eval(RValue::UnwrapHandle { - source: param_places[index], - dest: runtime_place, - slot: slot.clone(), - }); - } - } - runtime_param_places.push(runtime_place); - } - - let env = builder.let_value( - place_ty_for_val_type(callable_lowering.handle_lane(memory_model)), - RValue::ReadHandleField { - handle: Operand::Place(handle_place), - field: WrapperHandleField::Env, - }, - ); - let table_slot = builder.let_value( - place_ty_for_val_type(callable_lowering.table_index_lane(memory_model)), - RValue::ReadHandleField { - handle: Operand::Place(handle_place), - field: WrapperHandleField::Slot, - }, - ); - let internal_signature = entry.signature.internal.as_function_signature(); - let lowered_internal = backend.signature_strategy().direct_signature(&internal_signature); - let call_result_place = entry.signature.internal.results.first().map(|abi| { - let place = builder.place("runtime.result", place_ty_for_abi(abi), PlaceKind::Local); - maybe_prepare_runtime_place(&mut builder, place, abi); - place - }); - builder.eval(RValue::CallIndirect { - signature: internal_signature.clone(), - wasm_results: lowered_internal.results.into_iter().map(place_ty_for_val_type).collect(), - env: Operand::Value(env), - table_index: Operand::Value(table_slot), - args: call_args_for_internal_signature( - &internal_signature, - call_result_place, - &runtime_param_places, - ), - result: if internal_signature.result.is_aggregate() { None } else { call_result_place }, - }); - - let mut return_values = Vec::new(); - if let Some(slot) = entry.signature.results.first() - && let Some(result_place) = call_result_place - { - let outward_place = builder.place( - "result.out", - place_ty_for_transport(&plan.abi_preview.graph, &abi_signature.result)?, - PlaceKind::Local, - ); - match slot.transport.transport_class { - TransportClass::Immediate => { - builder.eval(RValue::EncodeImmediate { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - if matches!(slot.semantic_ty.kind(backend.db), TyKind::Bool) { - builder.normalize_place(outward_place); - } - } - TransportClass::CanonicalValue => { - builder.eval(RValue::EncodeCanonical { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - builder.eval(RValue::RetainNestedHandles { - place: outward_place, - ty: slot.semantic_ty, - }); - } - TransportClass::CapabilityHandle => { - builder.eval(RValue::WrapHandle { - source: result_place, - dest: outward_place, - slot: slot.clone(), - }); - } - } - return_values.push(Operand::Place(outward_place)); - } - - release_runtime_places(&mut builder, &internal_signature.params, &runtime_param_places); - if let Some(result_place) = call_result_place { - release_runtime_place(&mut builder, &internal_signature.result, result_place); - } - builder.ret(return_values); - Ok(builder.finish()) -} - -fn validate_wrapper_mir_function<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - function: &WrapperMirFunction<'db>, -) -> Result<(), Diagnostic> { - match &function.kind { - WrapperKind::ImportThunk { instance, metadata_index } => { - let entry = plan.boundary.instances.get(*metadata_index).ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR import metadata index drifted", - backend.file_range(), - ) - })?; - let expected = entry.signature.internal.as_function_signature(); - let lowered = backend.signature_strategy().direct_signature(&expected); - if &entry.instance != instance - || entry.linkage != mitki_abi::LinkageKind::WasmImport - || function.signature.wasm_params != lowered.params - || function.signature.wasm_results != lowered.results - { - return Err(Diagnostic::error( - "internal error: import thunk MIR signature drifted from its source plan", - backend.function_range(instance.location), - )); - } - } - WrapperKind::ExportWrapper { instance, metadata_index } => { - let entry = plan.boundary.instances.get(*metadata_index).ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR export metadata index drifted", - backend.file_range(), - ) - })?; - let built = &plan.abi_preview.functions[*metadata_index]; - let abi_signature = &plan.abi_preview.graph.signatures[built.signature_id.0 as usize]; - let (params, results) = abi_v2_wasm_signature(&plan.abi_preview.graph, abi_signature)?; - let expected_param_lanes = abi_signature - .params - .iter() - .map(|transport| transport_has_wasm_lane(&plan.abi_preview.graph, transport)) - .collect::, _>>()? - .into_iter() - .filter(|has_lane| *has_lane) - .count(); - let expected_result_lanes = usize::from(transport_has_wasm_lane( - &plan.abi_preview.graph, - &abi_signature.result, - )?); - if &entry.instance != instance - || entry.linkage != mitki_abi::LinkageKind::WasmExport - || params.len() != expected_param_lanes - || results.len() != expected_result_lanes - || function.signature.wasm_params != params - || function.signature.wasm_results != results - { - return Err(Diagnostic::error( - format!( - "internal error: export wrapper MIR signature drifted from ABI metadata \ - in `{}` (actual params={:?}, actual results={:?}, expected params={:?}, \ - expected results={:?})", - function.debug_name, - function.signature.wasm_params, - function.signature.wasm_results, - params, - results, - ), - backend.function_range(instance.location), - )); - } - } - WrapperKind::HandleInvokeTrampoline { instance, metadata_index, signature_id } => { - let entry = plan.boundary.instances.get(*metadata_index).ok_or_else(|| { - Diagnostic::error( - "internal error: wrapper MIR trampoline metadata index drifted", - backend.file_range(), - ) - })?; - let built = &plan.abi_preview.functions[*metadata_index]; - let abi_signature = &plan.abi_preview.graph.signatures[built.signature_id.0 as usize]; - let (params, results) = abi_v2_wasm_signature(&plan.abi_preview.graph, abi_signature)?; - let lowered = backend.callable_lowering_strategy().boundary_invoke_signature( - backend.memory_model_strategy(), - PhysicalWasmSignature::new(params, results), - ); - let expected_param_lanes = abi_signature - .params - .iter() - .map(|transport| transport_has_wasm_lane(&plan.abi_preview.graph, transport)) - .collect::, _>>()? - .into_iter() - .filter(|has_lane| *has_lane) - .count(); - let expected_result_lanes = usize::from(transport_has_wasm_lane( - &plan.abi_preview.graph, - &abi_signature.result, - )?); - if &entry.instance != instance - || built.signature_id != *signature_id - || lowered.params.len() - != expected_param_lanes - + usize::from(backend.callable_lowering_strategy().uses_table_slots()) - || lowered.results.len() != expected_result_lanes - || function.signature.wasm_params != lowered.params - || function.signature.wasm_results != lowered.results - { - return Err(Diagnostic::error( - format!( - "internal error: handle trampoline MIR signature drifted from the \ - callable plan in `{}`", - function.debug_name - ), - backend.function_range(instance.location), - )); - } - } - WrapperKind::FunctionValueWrapper { instance } => { - let hir_function = instance.location.hir_function(backend.db); - let function_sig = backend.function_signature( - instance, - hir_function.function(backend.db), - instance.location.infer(backend.db), - )?; - let lowered = backend.callable_lowering_strategy().callable_signature( - backend.signature_strategy(), - backend.memory_model_strategy(), - &function_sig, - ); - if function.signature.wasm_params != lowered.params - || function.signature.wasm_results != lowered.results - { - return Err(Diagnostic::error( - "internal error: callable wrapper MIR signature drifted from the callable ABI", - backend.function_range(instance.location), - )); - } - } - } - - let mut seen_places = FxHashMap::default(); - for place in &function.places { - if seen_places.insert(place.id, place.label.as_str()).is_some() { - return Err(Diagnostic::error( - "internal error: wrapper MIR declared the same place id twice", - backend.file_range(), - )); - } - } - if function.body.stmts.is_empty() - || !matches!(function.body.stmts.last(), Some(Stmt::Return { .. })) - { - return Err(Diagnostic::error( - "internal error: wrapper MIR region must end with a return", - backend.file_range(), - )); - } - validate_region(backend, function, &function.body, &mut FxHashMap::default())?; - Ok(()) -} - -fn validate_region<'db>( - backend: &Backend<'db>, - function: &WrapperMirFunction<'db>, - region: &Region<'db>, - values: &mut FxHashMap, -) -> Result<(), Diagnostic> { - let mut initialized = Vec::new(); - for stmt in ®ion.stmts { - match stmt { - Stmt::Let { value, ty, rhs } => { - validate_rvalue_uses(backend, function, &initialized, values, rhs)?; - values.insert(*value, *ty); - } - Stmt::Store { place, value } => { - validate_operand(backend, function, &initialized, values, value)?; - if !initialized.contains(place) { - initialized.push(*place); - } - } - Stmt::If { cond, then_region, else_region, .. } => { - validate_operand(backend, function, &initialized, values, cond)?; - validate_region(backend, function, then_region, &mut values.clone())?; - validate_region(backend, function, else_region, &mut values.clone())?; - } - Stmt::Eval { rhs } => { - validate_rvalue_uses(backend, function, &initialized, values, rhs)?; - if let Some(place) = rvalue_writes_place(rhs) - && !initialized.contains(&place) - { - initialized.push(place); - } - } - Stmt::Return { values: return_values } => { - if return_values.len() != function.signature.wasm_results.len() { - return Err(Diagnostic::error( - format!( - "internal error: wrapper MIR return arity drifted from its signature \ - in `{}`", - function.debug_name - ), - backend.file_range(), - )); - } - for value in return_values { - validate_operand(backend, function, &initialized, values, value)?; - } - } - } - } - Ok(()) -} - -fn validate_rvalue_uses<'db>( - backend: &Backend<'db>, - function: &WrapperMirFunction<'db>, - initialized: &[PlaceId], - values: &FxHashMap, - rhs: &RValue<'db>, -) -> Result<(), Diagnostic> { - match rhs { - RValue::ReadParam { .. } => {} - RValue::NormalizeBool { operand } => { - validate_operand(backend, function, initialized, values, operand)? - } - RValue::LoadPlace { place } - | RValue::ReleaseValue { place, .. } - | RValue::ReleaseCanonicalBlob { place } - | RValue::ReleaseHandleObject { place } - | RValue::DeallocTempBuffer { place, .. } - | RValue::ZeroTempBuffer { place, .. } => { - validate_place_initialized(backend, function, initialized, *place)? - } - RValue::CallDirect { args, .. } => { - for arg in args { - validate_operand(backend, function, initialized, values, arg)?; - } - } - RValue::CallIndirect { env, table_index, args, .. } => { - validate_operand(backend, function, initialized, values, env)?; - validate_operand(backend, function, initialized, values, table_index)?; - for arg in args { - validate_operand(backend, function, initialized, values, arg)?; - } - } - RValue::EncodeImmediate { source, .. } - | RValue::DecodeImmediate { source, .. } - | RValue::EncodeCanonical { source, .. } - | RValue::DecodeCanonical { source, .. } - | RValue::WrapHandle { source, .. } - | RValue::UnwrapHandle { source, .. } - | RValue::RetainNestedHandles { place: source, .. } => { - validate_place_initialized(backend, function, initialized, *source)? - } - RValue::AllocTempBuffer { .. } => {} - RValue::ReadHandleField { handle, .. } => { - validate_operand(backend, function, initialized, values, handle)? - } - } - Ok(()) -} - -fn validate_operand<'db>( - backend: &Backend<'db>, - function: &WrapperMirFunction<'db>, - initialized: &[PlaceId], - values: &FxHashMap, - operand: &Operand, -) -> Result<(), Diagnostic> { - match operand { - Operand::Value(value) => { - if !values.contains_key(value) { - return Err(Diagnostic::error( - "internal error: wrapper MIR referenced an unknown value", - backend.file_range(), - )); - } - } - Operand::Place(place) => { - validate_place_initialized(backend, function, initialized, *place)? - } - } - Ok(()) -} - -fn validate_place_initialized<'db>( - backend: &Backend<'db>, - function: &WrapperMirFunction<'db>, - initialized: &[PlaceId], - place: PlaceId, -) -> Result<(), Diagnostic> { - if !function.places.iter().any(|decl| decl.id == place) { - return Err(Diagnostic::error( - "internal error: wrapper MIR referenced an unknown place", - backend.file_range(), - )); - } - if !initialized.contains(&place) { - return Err(Diagnostic::error( - "internal error: wrapper MIR used a place before initialization", - backend.file_range(), - )); - } - Ok(()) -} - -fn rvalue_writes_place(rhs: &RValue<'_>) -> Option { - match rhs { - RValue::EncodeImmediate { dest, .. } - | RValue::DecodeImmediate { dest, .. } - | RValue::EncodeCanonical { dest, .. } - | RValue::DecodeCanonical { dest, .. } - | RValue::WrapHandle { dest, .. } - | RValue::UnwrapHandle { dest, .. } - | RValue::AllocTempBuffer { place: dest, .. } => Some(*dest), - RValue::CallDirect { result, .. } | RValue::CallIndirect { result, .. } => *result, - _ => None, - } -} - -#[cfg(test)] -impl<'db> WrapperMirBundle<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - dump_group(&mut output, "wrapper_mir.imports", db, &self.imports); - dump_group(&mut output, "wrapper_mir.callable_adapters", db, &self.callable_adapters); - dump_group(&mut output, "wrapper_mir.trampolines", db, &self.trampolines); - dump_group(&mut output, "wrapper_mir.exports", db, &self.exports); - output - } -} - -#[cfg(test)] -fn dump_group( - output: &mut String, - label: &str, - db: &dyn salsa::Database, - group: &[WrapperMirFunction<'_>], -) { - writeln!(output, "{label}:").expect("write to string"); - for function in group { - writeln!(output, " - [w{}] {}", function.id.0, function.debug_name).expect("write"); - writeln!( - output, - " sig: ({}) -> ({})", - function - .signature - .wasm_params - .iter() - .map(format_val_type) - .collect::>() - .join(", "), - function - .signature - .wasm_results - .iter() - .map(format_val_type) - .collect::>() - .join(", ") - ) - .expect("write"); - writeln!(output, " places:").expect("write"); - for place in &function.places { - writeln!( - output, - " - [p{}] {}: {}{}", - place.id.0, - place.label, - format_place_ty(place.ty), - match place.kind { - PlaceKind::Local => "", - PlaceKind::Scratch(_) => " scratch", - } - ) - .expect("write"); - } - writeln!(output, " body:").expect("write"); - for stmt in &function.body.stmts { - writeln!(output, " - {}", format_stmt(db, stmt)).expect("write"); - } - } -} - -#[cfg(test)] -fn format_stmt(db: &dyn salsa::Database, stmt: &Stmt<'_>) -> String { - match stmt { - Stmt::Let { value, rhs, .. } => format!("v{} = {}", value.0, format_rvalue(db, rhs)), - Stmt::Store { place, value } => format!("store p{} <- {}", place.0, format_operand(value)), - Stmt::If { cond, result_places, .. } => format!( - "if {} -> ({})", - format_operand(cond), - result_places - .iter() - .map(|place| format!("p{}", place.0)) - .collect::>() - .join(", ") - ), - Stmt::Eval { rhs } => format_rvalue(db, rhs), - Stmt::Return { values } => { - format!("return ({})", values.iter().map(format_operand).collect::>().join(", ")) - } - } -} - -#[cfg(test)] -fn format_rvalue(db: &dyn salsa::Database, rhs: &RValue<'_>) -> String { - match rhs { - RValue::ReadParam { param } => format!("read_param {param}"), - RValue::NormalizeBool { operand } => format!("normalize_bool {}", format_operand(operand)), - RValue::LoadPlace { place } => format!("load p{}", place.0), - RValue::CallDirect { target, args, result, .. } => format!( - "call_direct {} ({}){}", - match target { - WrapperCallTarget::DirectFunction(instance) - | WrapperCallTarget::RawImport(instance) => { - instance.location.source(db).name().map_or("", |name| name.as_str()) - } - }, - args.iter().map(format_operand).collect::>().join(", "), - result.map_or_else(String::new, |place| format!(" -> p{}", place.0)) - ), - RValue::CallIndirect { args, result, .. } => format!( - "call_indirect ({}){}", - args.iter().map(format_operand).collect::>().join(", "), - result.map_or_else(String::new, |place| format!(" -> p{}", place.0)) - ), - RValue::EncodeImmediate { source, dest, slot } => { - format!( - "encode_immediate p{} -> p{} ({})", - source.0, - dest.0, - slot.semantic_ty.display(db) - ) - } - RValue::DecodeImmediate { source, dest, slot } => { - format!( - "decode_immediate p{} -> p{} ({})", - source.0, - dest.0, - slot.semantic_ty.display(db) - ) - } - RValue::EncodeCanonical { source, dest, slot } => { - format!( - "encode_canonical p{} -> p{} ({})", - source.0, - dest.0, - slot.semantic_ty.display(db) - ) - } - RValue::DecodeCanonical { source, dest, slot } => { - format!( - "decode_canonical p{} -> p{} ({})", - source.0, - dest.0, - slot.semantic_ty.display(db) - ) - } - RValue::WrapHandle { source, dest, slot } => { - format!("wrap_handle p{} -> p{} ({})", source.0, dest.0, slot.semantic_ty.display(db)) - } - RValue::UnwrapHandle { source, dest, slot } => { - format!("unwrap_handle p{} -> p{} ({})", source.0, dest.0, slot.semantic_ty.display(db)) - } - RValue::RetainNestedHandles { place, ty } => { - format!("retain_nested_handles p{} ({})", place.0, ty.display(db)) - } - RValue::ReleaseValue { place, abi } => { - format!("release_value p{} ({})", place.0, format_abi_ty(abi)) - } - RValue::ReleaseCanonicalBlob { place } => format!("release_blob p{}", place.0), - RValue::ReleaseHandleObject { place } => format!("release_handle p{}", place.0), - RValue::AllocTempBuffer { place, layout } => { - format!("alloc_temp p{} size={} align={}", place.0, layout.size, layout.align) - } - RValue::DeallocTempBuffer { place, layout } => { - format!("dealloc_temp p{} size={} align={}", place.0, layout.size, layout.align) - } - RValue::ZeroTempBuffer { place, size } => format!("zero_temp p{} size={size}", place.0), - RValue::ReadHandleField { handle, field } => format!( - "read_handle_field {}.{}", - format_operand(handle), - match field { - WrapperHandleField::Slot => "slot", - WrapperHandleField::Env => "env", - } - ), - } -} - -#[cfg(test)] -fn format_operand(operand: &Operand) -> String { - match operand { - Operand::Value(value) => format!("v{}", value.0), - Operand::Place(place) => format!("p{}", place.0), - } -} - -#[cfg(test)] -fn format_place_ty(ty: WrapperPlaceTy) -> &'static str { - match ty { - WrapperPlaceTy::I32 => "i32", - WrapperPlaceTy::I64 => "i64", - WrapperPlaceTy::F64 => "f64", - WrapperPlaceTy::Unit => "unit", - } -} - -#[cfg(test)] -fn format_abi_ty(abi: &AbiTy) -> String { - match abi { - AbiTy::Scalar(ty) => format!("{ty:?}"), - AbiTy::Aggregate(layout) => { - format!("aggregate(size={}, align={})", layout.size, layout.align) - } - } -} - -#[cfg(test)] -fn format_val_type(ty: &ValType) -> &'static str { - match ty { - ValType::I32 => "i32", - ValType::I64 => "i64", - ValType::F64 => "f64", - ValType::F32 => "f32", - ValType::V128 => "v128", - ValType::Ref(_) => "ref", - } -} - -#[cfg(test)] -mod tests { - use expect_test::{Expect, expect}; - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "wrapper_mir_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!( - diagnostics.is_empty(), - "unexpected diagnostics: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let mut backend = Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - backend - } - - fn assert_wrapper_mir_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.wrapper_mir.dump(backend.db)); - } - - #[test] - fn wrapper_mir_dump_tracks_bool_wrappers() { - assert_wrapper_mir_dump( - r#" -import "env" fun round_trip(flag: bool): bool; - -export fun echo(flag: bool): bool { - round_trip(flag) -} -"#, - &expect![[r#" - wrapper_mir.imports: - - [w0] import round_trip - sig: (i32) -> (i32) - places: - - [p0] param0: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] result.lane: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = normalize_bool p0 - - store p0 <- v1 - - call_direct round_trip (p0) -> p16 - - v2 = normalize_bool p16 - - store p16 <- v2 - - release_value p0 (Bool) - - return (p16) - wrapper_mir.callable_adapters: - - [w1] callable echo - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - v2 = normalize_bool p1 - - store p1 <- v2 - - call_direct echo (p1) -> p2 - - v3 = normalize_bool p2 - - store p2 <- v3 - - return (p2) - - [w2] callable round_trip - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - v2 = normalize_bool p1 - - store p1 <- v2 - - call_direct round_trip (p1) -> p2 - - v3 = normalize_bool p2 - - store p2 <- v3 - - return (p2) - wrapper_mir.trampolines: - wrapper_mir.exports: - - [w3] export echo - sig: (i32) -> (i32) - places: - - [p0] param0.lane: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] runtime.param0: i32 - - [p17] runtime.result: i32 - - [p18] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - decode_immediate p0 -> p16 (bool) - - v1 = normalize_bool p16 - - store p16 <- v1 - - call_direct echo (p16) -> p17 - - encode_immediate p17 -> p18 (bool) - - v2 = normalize_bool p18 - - store p18 <- v2 - - release_value p16 (Bool) - - release_value p17 (Bool) - - return (p18) - "#]], - ); - } - - #[test] - fn wrapper_mir_dump_tracks_canonical_wrappers() { - assert_wrapper_mir_dump( - r#" -import "env" fun mirror(xs: [str]): [str]; - -export fun words(): [str] { - mirror(["a", "b"]) -} -"#, - &expect![[r#" - wrapper_mir.imports: - - [w0] import mirror - sig: (i32) -> (i32) - places: - - [p0] param0: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] param0.blob: i32 - - [p17] result.blob: i32 - - [p18] result.runtime: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - encode_canonical p0 -> p16 ([str]) - - retain_nested_handles p16 ([str]) - - call_direct mirror (p16) -> p17 - - decode_canonical p17 -> p18 ([str]) - - release_value p0 (Ref(Array(10254))) - - release_blob p16 - - return (p18) - wrapper_mir.callable_adapters: - - [w1] callable words - sig: (i32) -> (i32) - places: - - [p0] env: i32 - - [p1] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - call_direct words () -> p1 - - return (p1) - - [w2] callable mirror - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - call_direct mirror (p1) -> p2 - - return (p2) - wrapper_mir.trampolines: - wrapper_mir.exports: - - [w3] export words - sig: () -> (i32) - places: - - [p0] scratch.node_offset: i32 scratch - - [p1] scratch.cursor: i32 scratch - - [p2] scratch.index: i32 scratch - - [p3] scratch.len: i32 scratch - - [p4] scratch.count: i32 scratch - - [p5] scratch.bytes: i32 scratch - - [p6] scratch.base_id: i32 scratch - - [p7] scratch.temp_ptr: i32 scratch - - [p8] scratch.temp_ptr_aux: i32 scratch - - [p9] scratch.child_count: i32 scratch - - [p10] scratch.child_bytes: i32 scratch - - [p11] scratch.handle_count: i32 scratch - - [p12] scratch.handle_index: i32 scratch - - [p13] scratch.handle_cursor: i32 scratch - - [p14] scratch.f64_temp: f64 scratch - - [p15] runtime.result: i32 - - [p16] result.out: i32 - body: - - call_direct words () -> p15 - - encode_canonical p15 -> p16 ([str]) - - retain_nested_handles p16 ([str]) - - release_value p15 (Ref(Array(10254))) - - return (p16) - "#]], - ); - } - - #[test] - fn wrapper_mir_dump_tracks_handle_wrappers_and_trampolines() { - assert_wrapper_mir_dump( - r#" -import "env" fun bounce(f: fun(int) -> int): fun(int) -> int; - -fun add_one(x: int): int { - x + 1 -} - -export fun id(f: fun(int) -> int): fun(int) -> int { - bounce(f) -} - -export fun make_adder(): fun(int) -> int { - add_one -} -"#, - &expect![[r#" - wrapper_mir.imports: - - [w0] import bounce - sig: (i32, i32) -> () - places: - - [p0] result_ptr: i32 - - [p1] param0: i32 - - [p2] scratch.node_offset: i32 scratch - - [p3] scratch.cursor: i32 scratch - - [p4] scratch.index: i32 scratch - - [p5] scratch.len: i32 scratch - - [p6] scratch.count: i32 scratch - - [p7] scratch.bytes: i32 scratch - - [p8] scratch.base_id: i32 scratch - - [p9] scratch.temp_ptr: i32 scratch - - [p10] scratch.temp_ptr_aux: i32 scratch - - [p11] scratch.child_count: i32 scratch - - [p12] scratch.child_bytes: i32 scratch - - [p13] scratch.handle_count: i32 scratch - - [p14] scratch.handle_index: i32 scratch - - [p15] scratch.handle_cursor: i32 scratch - - [p16] scratch.f64_temp: f64 scratch - - [p17] param0.handle: i32 - - [p18] result.handle: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - wrap_handle p1 -> p17 (fun(int) -> int) - - call_direct bounce (p17) -> p18 - - unwrap_handle p18 -> p0 (fun(int) -> int) - - release_value p1 (aggregate(size=8, align=4)) - - dealloc_temp p1 size=8 align=4 - - release_handle p17 - - return () - wrapper_mir.callable_adapters: - - [w1] callable id - sig: (i32, i32, i32) -> () - places: - - [p0] env: i32 - - [p1] result_ptr: i32 - - [p2] param0: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - v2 = read_param 2 - - store p2 <- v2 - - call_direct id (p1, p2) - - return () - - [w2] callable make_adder - sig: (i32, i32) -> () - places: - - [p0] env: i32 - - [p1] result_ptr: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - call_direct make_adder (p1) - - return () - - [w3] callable bounce - sig: (i32, i32, i32) -> () - places: - - [p0] env: i32 - - [p1] result_ptr: i32 - - [p2] param0: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - v2 = read_param 2 - - store p2 <- v2 - - call_direct bounce (p1, p2) - - return () - - [w4] callable add_one - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - call_direct add_one (p1) -> p2 - - return (p2) - wrapper_mir.trampolines: - - [w5] trampoline id - sig: (i32, i32) -> (i32) - places: - - [p0] handle: i32 - - [p1] param0.lane: i32 - - [p2] scratch.node_offset: i32 scratch - - [p3] scratch.cursor: i32 scratch - - [p4] scratch.index: i32 scratch - - [p5] scratch.len: i32 scratch - - [p6] scratch.count: i32 scratch - - [p7] scratch.bytes: i32 scratch - - [p8] scratch.base_id: i32 scratch - - [p9] scratch.temp_ptr: i32 scratch - - [p10] scratch.temp_ptr_aux: i32 scratch - - [p11] scratch.child_count: i32 scratch - - [p12] scratch.child_bytes: i32 scratch - - [p13] scratch.handle_count: i32 scratch - - [p14] scratch.handle_index: i32 scratch - - [p15] scratch.handle_cursor: i32 scratch - - [p16] scratch.f64_temp: f64 scratch - - [p17] runtime.param0: i32 - - [p18] runtime.result: i32 - - [p19] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - alloc_temp p17 size=8 align=4 - - zero_temp p17 size=8 - - unwrap_handle p1 -> p17 (fun(int) -> int) - - v2 = read_handle_field p0.env - - v3 = read_handle_field p0.slot - - alloc_temp p18 size=8 align=4 - - zero_temp p18 size=8 - - call_indirect (p18, p17) - - wrap_handle p18 -> p19 (fun(int) -> int) - - release_value p17 (aggregate(size=8, align=4)) - - dealloc_temp p17 size=8 align=4 - - release_value p18 (aggregate(size=8, align=4)) - - dealloc_temp p18 size=8 align=4 - - return (p19) - - [w6] trampoline make_adder - sig: (i32) -> (i32) - places: - - [p0] handle: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] runtime.result: i32 - - [p17] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_handle_field p0.env - - v2 = read_handle_field p0.slot - - alloc_temp p16 size=8 align=4 - - zero_temp p16 size=8 - - call_indirect (p16) - - wrap_handle p16 -> p17 (fun(int) -> int) - - release_value p16 (aggregate(size=8, align=4)) - - dealloc_temp p16 size=8 align=4 - - return (p17) - - [w7] trampoline bounce - sig: (i32, i32) -> (i32) - places: - - [p0] handle: i32 - - [p1] param0.lane: i32 - - [p2] scratch.node_offset: i32 scratch - - [p3] scratch.cursor: i32 scratch - - [p4] scratch.index: i32 scratch - - [p5] scratch.len: i32 scratch - - [p6] scratch.count: i32 scratch - - [p7] scratch.bytes: i32 scratch - - [p8] scratch.base_id: i32 scratch - - [p9] scratch.temp_ptr: i32 scratch - - [p10] scratch.temp_ptr_aux: i32 scratch - - [p11] scratch.child_count: i32 scratch - - [p12] scratch.child_bytes: i32 scratch - - [p13] scratch.handle_count: i32 scratch - - [p14] scratch.handle_index: i32 scratch - - [p15] scratch.handle_cursor: i32 scratch - - [p16] scratch.f64_temp: f64 scratch - - [p17] runtime.param0: i32 - - [p18] runtime.result: i32 - - [p19] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - alloc_temp p17 size=8 align=4 - - zero_temp p17 size=8 - - unwrap_handle p1 -> p17 (fun(int) -> int) - - v2 = read_handle_field p0.env - - v3 = read_handle_field p0.slot - - alloc_temp p18 size=8 align=4 - - zero_temp p18 size=8 - - call_indirect (p18, p17) - - wrap_handle p18 -> p19 (fun(int) -> int) - - release_value p17 (aggregate(size=8, align=4)) - - dealloc_temp p17 size=8 align=4 - - release_value p18 (aggregate(size=8, align=4)) - - dealloc_temp p18 size=8 align=4 - - return (p19) - wrapper_mir.exports: - - [w8] export id - sig: (i32) -> (i32) - places: - - [p0] param0.lane: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] runtime.param0: i32 - - [p17] runtime.result: i32 - - [p18] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - alloc_temp p16 size=8 align=4 - - zero_temp p16 size=8 - - unwrap_handle p0 -> p16 (fun(int) -> int) - - alloc_temp p17 size=8 align=4 - - zero_temp p17 size=8 - - call_direct id (p17, p16) - - wrap_handle p17 -> p18 (fun(int) -> int) - - release_value p16 (aggregate(size=8, align=4)) - - dealloc_temp p16 size=8 align=4 - - release_value p17 (aggregate(size=8, align=4)) - - dealloc_temp p17 size=8 align=4 - - return (p18) - - [w9] export make_adder - sig: () -> (i32) - places: - - [p0] scratch.node_offset: i32 scratch - - [p1] scratch.cursor: i32 scratch - - [p2] scratch.index: i32 scratch - - [p3] scratch.len: i32 scratch - - [p4] scratch.count: i32 scratch - - [p5] scratch.bytes: i32 scratch - - [p6] scratch.base_id: i32 scratch - - [p7] scratch.temp_ptr: i32 scratch - - [p8] scratch.temp_ptr_aux: i32 scratch - - [p9] scratch.child_count: i32 scratch - - [p10] scratch.child_bytes: i32 scratch - - [p11] scratch.handle_count: i32 scratch - - [p12] scratch.handle_index: i32 scratch - - [p13] scratch.handle_cursor: i32 scratch - - [p14] scratch.f64_temp: f64 scratch - - [p15] runtime.result: i32 - - [p16] result.out: i32 - body: - - alloc_temp p15 size=8 align=4 - - zero_temp p15 size=8 - - call_direct make_adder (p15) - - wrap_handle p15 -> p16 (fun(int) -> int) - - release_value p15 (aggregate(size=8, align=4)) - - dealloc_temp p15 size=8 align=4 - - return (p16) - "#]], - ); - } - - #[test] - fn wrapper_mir_dump_tracks_generic_export_instances() { - assert_wrapper_mir_dump( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" - wrapper_mir.imports: - wrapper_mir.callable_adapters: - - [w0] callable id - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - call_direct id (p1) -> p2 - - return (p2) - - [w1] callable id - sig: (i32, i32) -> (i32) - places: - - [p0] env: i32 - - [p1] param0: i32 - - [p2] result: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - v1 = read_param 1 - - store p1 <- v1 - - v2 = normalize_bool p1 - - store p1 <- v2 - - call_direct id (p1) -> p2 - - v3 = normalize_bool p2 - - store p2 <- v3 - - return (p2) - wrapper_mir.trampolines: - wrapper_mir.exports: - - [w2] export id - sig: (i32) -> (i32) - places: - - [p0] param0.lane: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] runtime.param0: i32 - - [p17] runtime.result: i32 - - [p18] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - decode_immediate p0 -> p16 (int) - - call_direct id (p16) -> p17 - - encode_immediate p17 -> p18 (int) - - release_value p16 (Int) - - release_value p17 (Int) - - return (p18) - - [w3] export id - sig: (i32) -> (i32) - places: - - [p0] param0.lane: i32 - - [p1] scratch.node_offset: i32 scratch - - [p2] scratch.cursor: i32 scratch - - [p3] scratch.index: i32 scratch - - [p4] scratch.len: i32 scratch - - [p5] scratch.count: i32 scratch - - [p6] scratch.bytes: i32 scratch - - [p7] scratch.base_id: i32 scratch - - [p8] scratch.temp_ptr: i32 scratch - - [p9] scratch.temp_ptr_aux: i32 scratch - - [p10] scratch.child_count: i32 scratch - - [p11] scratch.child_bytes: i32 scratch - - [p12] scratch.handle_count: i32 scratch - - [p13] scratch.handle_index: i32 scratch - - [p14] scratch.handle_cursor: i32 scratch - - [p15] scratch.f64_temp: f64 scratch - - [p16] runtime.param0: i32 - - [p17] runtime.result: i32 - - [p18] result.out: i32 - body: - - v0 = read_param 0 - - store p0 <- v0 - - decode_immediate p0 -> p16 (bool) - - v1 = normalize_bool p16 - - store p16 <- v1 - - call_direct id (p16) -> p17 - - encode_immediate p17 -> p18 (bool) - - v2 = normalize_bool p18 - - store p18 <- v2 - - release_value p16 (Bool) - - release_value p17 (Bool) - - return (p18) - "#]], - ); - } - - #[test] - fn wrapper_mir_dump_is_stable_across_repeated_planning() { - let backend = compiler_for_fixture( - r#" -import "env" fun round_trip(xs: [int]): [int]; - -export fun main(): [int] { - round_trip([20, 22]) -} -"#, - ); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .wrapper_mir - .dump(backend.db); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .wrapper_mir - .dump(backend.db); - assert_eq!(first, second); - } - - #[test] - fn wrapper_mir_validator_rejects_return_arity_mismatches() { - let backend = compiler_for_fixture( - r#" -export fun answer(): int { - 42 -} -"#, - ); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - let mut broken = plan - .wrapper_mir - .exports - .first() - .expect("expected an export wrapper MIR function") - .clone(); - let Some(Stmt::Return { values }) = broken.body.stmts.last_mut() else { - panic!("expected wrapper MIR to end in a return"); - }; - values.clear(); - let error = validate_wrapper_mir_function(&backend, &plan, &broken) - .expect_err("broken wrapper MIR should fail validation"); - assert!( - error.message().contains("return arity drifted"), - "unexpected diagnostic: {}", - error.message() - ); - } -} diff --git a/crates/mitki-backend-wasm/src/model.rs b/crates/mitki-backend-wasm/src/model.rs deleted file mode 100644 index 884cadb..0000000 --- a/crates/mitki-backend-wasm/src/model.rs +++ /dev/null @@ -1,243 +0,0 @@ -use mitki_abi::TransportClass; -use mitki_hir::hir::{ExprId, NameId, ParamId}; -use mitki_hir::ty::Ty; -use mitki_lower::item::scope::FunctionLocation; -use mitki_resolve::RuntimeFunction; -use mitki_span::Symbol; - -use super::{AbiTy, AggregateLayout, FieldLayout, FunctionSignature, StageIntrinsic}; -use crate::layout::VariantLayout; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(super) enum HelperFunction { - MemoryEq, - StringEq, - ArcRetain, - ArcRelease, -} - -impl HelperFunction { - pub(super) fn all() -> [Self; 4] { - [Self::MemoryEq, Self::StringEq, Self::ArcRetain, Self::ArcRelease] - } - - pub(super) fn name(self) -> &'static str { - match self { - Self::MemoryEq => "memory_eq", - Self::StringEq => "string_eq", - Self::ArcRetain => "arc_retain", - Self::ArcRelease => "arc_release", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(super) enum ValueOwnership { - None, - Borrowed, - Owned, -} - -impl ValueOwnership { - pub(super) fn is_owned(self) -> bool { - matches!(self, Self::Owned) - } - - pub(super) fn is_borrowed(self) -> bool { - matches!(self, Self::Borrowed) - } -} - -#[derive(Clone, Copy)] -pub(super) enum ExprPosition { - Value, -} - -#[derive(Clone, Debug)] -pub(super) struct BoundaryFunctionSignatures<'db> { - pub(super) param_tys: Vec>, - pub(super) result_ty: Ty<'db>, - pub(super) param_transport_plans: Vec, - pub(super) result_transport_plan: BoundaryTransportPlan, - pub(super) param_runtime_abis: Vec, - pub(super) result_runtime_abi: AbiTy, -} - -impl BoundaryFunctionSignatures<'_> { - pub(super) fn internal_signature(&self) -> FunctionSignature { - FunctionSignature { - params: self.param_runtime_abis.clone(), - result: self.result_runtime_abi.clone(), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct BoundaryTransportPlan { - pub(crate) transport_class: TransportClass, -} - -#[derive(Clone, Debug)] -pub(super) enum BackendPattern { - Binding(NameId), - Wildcard, - Literal(BackendPatternLiteral), - Tuple(Vec), - Struct(Vec), - Variant { variant: VariantLayout, fields: Vec }, -} - -impl BackendPattern { - pub(super) fn binding_names(&self, names: &mut Vec) { - match self { - Self::Binding(name) => names.push(*name), - Self::Tuple(fields) | Self::Struct(fields) => { - for field in fields { - field.pattern.binding_names(names); - } - } - Self::Variant { fields, .. } => { - for field in fields { - field.pattern.binding_names(names); - } - } - Self::Wildcard | Self::Literal(_) => {} - } - } -} - -#[derive(Clone, Debug)] -pub(super) struct BackendPatternField { - pub(super) field: FieldLayout, - pub(super) pattern: BackendPattern, -} - -#[derive(Clone, Debug)] -pub(super) enum BackendPatternLiteral { - Bool(bool), - Int(i64), - String(u32), - Char(char), -} - -#[derive(Clone, Debug)] -pub(super) struct BackendCallTarget<'db> { - pub(super) callable: BackendCallable<'db>, - pub(super) signature: FunctionSignature, -} - -#[derive(Clone, Debug)] -pub(super) enum BackendCallable<'db> { - Runtime(RuntimeFunction), - StageIntrinsic(StageIntrinsic), - Function(InstanceKey<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(super) enum FunctionValueTarget<'db> { - Function(InstanceKey<'db>), - Closure(ClosureInstanceKey<'db>), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum BackendBinaryOp { - Add, - Sub, - Mul, - Div, - Rem, - Lt, - Gt, - Le, - Ge, - Eq, - Ne, - And, - Or, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum BackendPrefixOp { - Not, - Neg, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(super) struct InstanceKey<'db> { - pub(super) location: FunctionLocation<'db>, - pub(super) type_args: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(super) struct ClosureInstanceKey<'db> { - pub(super) owner: FunctionLocation<'db>, - pub(super) type_args: Vec>, - pub(super) closure: ExprId, -} - -impl<'db> ClosureInstanceKey<'db> { - pub(super) fn owner_instance(&self) -> InstanceKey<'db> { - InstanceKey { location: self.owner, type_args: self.type_args.clone() } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(super) enum ReachableInstance<'db> { - Function(InstanceKey<'db>), - Closure(ClosureInstanceKey<'db>), -} - -impl<'db> ReachableInstance<'db> { - pub(super) fn owner_location(&self) -> FunctionLocation<'db> { - match self { - Self::Function(instance) => instance.location, - Self::Closure(instance) => instance.owner, - } - } - - pub(super) fn owner_instance(&self) -> InstanceKey<'db> { - match self { - Self::Function(instance) => instance.clone(), - Self::Closure(instance) => instance.owner_instance(), - } - } -} - -#[derive(Clone, Debug)] -pub(super) struct ClosureCapture<'db> { - pub(super) binding: NameId, - pub(super) field: FieldLayout, - pub(super) _marker: std::marker::PhantomData<&'db ()>, -} - -#[derive(Clone, Debug)] -pub(super) struct PendingClosureCapture { - pub(super) binding: NameId, - pub(super) ty: AbiTy, -} - -#[derive(Clone, Debug)] -pub(super) struct ClosureInfo<'db> { - pub(super) body: ExprId, - pub(super) params: Vec, - pub(super) captures: Vec>, - pub(super) env_layout: AggregateLayout, - pub(super) signature: FunctionSignature, -} - -#[derive(Clone, Debug)] -pub(super) struct ReachableClosureInfo<'db> { - pub(super) closure: ClosureInstanceKey<'db>, - pub(super) info: ClosureInfo<'db>, -} - -#[derive(Clone, Debug)] -pub(super) struct ExportedFunction<'db> { - pub(super) instance: InstanceKey<'db>, - pub(super) name: Symbol<'db>, -} - -pub(super) struct SignaturePattern<'db> { - pub(super) params: Vec>, - pub(super) result: Ty<'db>, -} diff --git a/crates/mitki-backend-wasm/src/obligations.rs b/crates/mitki-backend-wasm/src/obligations.rs deleted file mode 100644 index 9ad4f71..0000000 --- a/crates/mitki-backend-wasm/src/obligations.rs +++ /dev/null @@ -1,283 +0,0 @@ -use mitki_abi::TransportClass; -use rustc_hash::FxHashSet; - -use super::plan::{ - BoundaryWrapperNeed, CallableAdapterNeed, CanonicalSupportNeed, EmissionObligations, - LayoutNeed, ReachabilityGraph, ReachabilityInstance, RuntimeImportNeed, -}; -use super::reachability::collect_instance_emission_obligations; -use super::*; - -pub(in crate::backend) struct ObligationBuild<'db> { - pub(in crate::backend) shadow_obligations: EmissionObligations<'db>, -} - -pub(in crate::backend) struct ObligationCollector; - -impl ObligationCollector { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - graph: &ReachabilityGraph<'db>, - ) -> ObligationBuild<'db> { - let mut scratch = EmissionObligationScratch::default(); - let mut callable_adapters = FxHashSet::default(); - let mut boundary_wrappers = FxHashSet::default(); - let mut canonical_types = FxHashSet::default(); - let mut needs_blob_helpers = false; - let mut needs_handle_helpers = false; - - for instance in &graph.instances { - let reachable = match instance { - ReachabilityInstance::Function(instance) => { - ReachableInstance::Function(instance.clone()) - } - ReachabilityInstance::Closure(instance) => { - ReachableInstance::Closure(instance.clone()) - } - }; - collect_instance_emission_obligations(backend, &reachable, &mut scratch); - - if let ReachableInstance::Closure(closure) = &reachable { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - if let Ok(info) = backend.closure_info(closure, function, inference) - && info.env_layout.size > 0 - { - callable_adapters.insert(CallableAdapterNeed::ClosureEnv(closure.clone())); - } - } - } - - for instance in &graph.imports { - let function = instance.location.hir_function(backend.db).function(backend.db); - let inference = instance.location.infer(backend.db); - match function.linkage() { - WasmLinkage::Import { .. } => { - boundary_wrappers.insert(BoundaryWrapperNeed::Import(instance.clone())); - let needs = collect_boundary_support_for_function( - backend, - instance, - function, - inference, - BoundaryExposure::Import, - &mut scratch, - &mut canonical_types, - ); - needs_blob_helpers |= needs.blob_helpers; - needs_handle_helpers |= needs.handle_helpers; - } - WasmLinkage::RawImport { .. } => { - collect_boundary_support_for_function( - backend, - instance, - function, - inference, - BoundaryExposure::RawImport, - &mut scratch, - &mut canonical_types, - ); - } - WasmLinkage::Internal | WasmLinkage::ImplicitMainExport | WasmLinkage::Export => {} - } - } - - for instance in &graph.exports { - let function = instance.location.hir_function(backend.db).function(backend.db); - let inference = instance.location.infer(backend.db); - boundary_wrappers.insert(BoundaryWrapperNeed::Export(instance.clone())); - let needs = collect_boundary_support_for_function( - backend, - instance, - function, - inference, - BoundaryExposure::Export, - &mut scratch, - &mut canonical_types, - ); - needs_blob_helpers |= needs.blob_helpers; - needs_handle_helpers |= needs.handle_helpers; - } - - if needs_handle_helpers { - for instance in &graph.imports { - let function = instance.location.hir_function(backend.db).function(backend.db); - if matches!(function.linkage(), WasmLinkage::Import { .. }) { - callable_adapters.insert(CallableAdapterNeed::BoundaryInvoke(instance.clone())); - } - } - for instance in &graph.exports { - callable_adapters.insert(CallableAdapterNeed::BoundaryInvoke(instance.clone())); - } - } - - let mut shadow = scratch.into_plan(backend); - shadow.runtime_import_needs = - shadow.runtime_imports.iter().copied().map(RuntimeImportNeed::Runtime).collect(); - shadow.callable_adapters = graph - .closures - .iter() - .filter(|closure| { - callable_adapters.contains(&CallableAdapterNeed::ClosureEnv((*closure).clone())) - }) - .cloned() - .map(CallableAdapterNeed::ClosureEnv) - .chain( - graph - .functions - .iter() - .filter(|instance| { - callable_adapters - .contains(&CallableAdapterNeed::BoundaryInvoke((*instance).clone())) - }) - .cloned() - .map(CallableAdapterNeed::BoundaryInvoke), - ) - .collect(); - shadow.boundary_wrappers = graph - .imports - .iter() - .filter(|instance| { - boundary_wrappers.contains(&BoundaryWrapperNeed::Import((*instance).clone())) - }) - .cloned() - .map(BoundaryWrapperNeed::Import) - .chain( - graph - .exports - .iter() - .filter(|instance| { - boundary_wrappers - .contains(&BoundaryWrapperNeed::Export((*instance).clone())) - }) - .cloned() - .map(BoundaryWrapperNeed::Export), - ) - .collect(); - shadow.canonical_support = graph - .types - .iter() - .copied() - .filter(|ty| canonical_types.contains(ty)) - .map(CanonicalSupportNeed::Type) - .collect(); - if needs_blob_helpers { - shadow.canonical_support.push(CanonicalSupportNeed::BlobHelpers); - } - if needs_handle_helpers { - shadow.canonical_support.push(CanonicalSupportNeed::HandleHelpers); - } - shadow.layout_needs = shadow - .reachable_arrays - .iter() - .copied() - .map(LayoutNeed::Array) - .chain(shadow.reachable_nominals.iter().copied().map(LayoutNeed::Nominal)) - .collect(); - - ObligationBuild { shadow_obligations: shadow } - } -} - -#[derive(Clone, Copy)] -enum BoundaryExposure { - Import, - RawImport, - Export, -} - -#[derive(Default)] -struct BoundarySupportSummary { - blob_helpers: bool, - handle_helpers: bool, -} - -fn collect_boundary_support_for_function<'db>( - backend: &Backend<'db>, - instance: &InstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - exposure: BoundaryExposure, - scratch: &mut EmissionObligationScratch<'db>, - canonical_types: &mut FxHashSet>, -) -> BoundarySupportSummary { - let Ok((params, result_ty)) = backend.function_signature_types(instance, function, inference) - else { - return BoundarySupportSummary::default(); - }; - - let mut summary = BoundarySupportSummary::default(); - let typed_boundary = matches!(exposure, BoundaryExposure::Import | BoundaryExposure::Export); - let is_export = matches!(exposure, BoundaryExposure::Export); - let mut boundary_params_need_runtime_allocs = false; - let transport_profile = backend.boundary_transport_profile(); - - for ty in params { - scratch.register_nominals_in_ty(backend.db, ty); - if typed_boundary - && let Ok(plan) = transport_profile.plan_or_message( - backend.db, - ty, - "Wasm backend does not support this function signature type", - ) - { - if matches!(plan.transport_class, TransportClass::CanonicalValue) { - canonical_types.insert(ty); - if is_export { - summary.blob_helpers = true; - } - } - if matches!(plan.transport_class, TransportClass::CapabilityHandle) { - summary.handle_helpers = true; - } - boundary_params_need_runtime_allocs |= - crate::capability::supported_value_abi(backend.db, ty).is_some_and(|runtime_abi| { - transport_profile.requires_runtime_allocs(backend.db, ty, &runtime_abi) - }); - } - } - - scratch.register_nominals_in_ty(backend.db, result_ty); - let result_needs_runtime_allocs = if matches!( - exposure, - BoundaryExposure::Import | BoundaryExposure::RawImport | BoundaryExposure::Export - ) { - transport_profile - .plan_or_message( - backend.db, - result_ty, - "Wasm backend does not support this function signature type", - ) - .is_ok_and(|plan| { - if typed_boundary && matches!(plan.transport_class, TransportClass::CanonicalValue) - { - canonical_types.insert(result_ty); - if is_export { - summary.blob_helpers = true; - } - } - if typed_boundary - && matches!(plan.transport_class, TransportClass::CapabilityHandle) - { - summary.handle_helpers = true; - } - crate::capability::supported_value_abi(backend.db, result_ty).is_some_and( - |runtime_abi| { - transport_profile.requires_runtime_allocs( - backend.db, - result_ty, - &runtime_abi, - ) - }, - ) - }) - } else { - false - }; - if boundary_params_need_runtime_allocs || result_needs_runtime_allocs { - scratch.used_runtime_functions.insert(RuntimeFunction::Alloc); - scratch.used_runtime_functions.insert(RuntimeFunction::Dealloc); - } - - summary -} diff --git a/crates/mitki-backend-wasm/src/planning.rs b/crates/mitki-backend-wasm/src/planning.rs deleted file mode 100644 index d3573ca..0000000 --- a/crates/mitki-backend-wasm/src/planning.rs +++ /dev/null @@ -1,2381 +0,0 @@ -#[cfg(test)] -use std::fmt::Write as _; -#[cfg(test)] -use std::sync::Arc; - -#[cfg(test)] -use mitki_abi::LinkageKind as AbiV2LinkageKind; -use mitki_abi::SigId; -use mitki_abi_lower::BuiltAbiV2; -use mitki_errors::Diagnostic; -use mitki_hir::ty::Ty; - -use super::boundary::{BoundaryPlan, BoundaryPlanner, BoundarySig, InternalSig}; -#[cfg(test)] -use super::boundary::{BoundarySlot, TransportOp}; -use super::function_kernel::{FunctionKernelBuilder, FunctionKernelBundle}; -use super::function_legalize::FunctionLegalizer; -use super::function_ownership::OwnershipLowering; -use super::function_wasm_ir::{StructuredWasmBundle, StructuredWasmLowering}; -#[cfg(test)] -use super::registry::SectionExportTarget; -use super::registry::{ - CallableRegistry, CallableRegistryPlan, HelperRegistry, HelperRegistryPlan, NameAssigner, - SectionAssigner, SectionPlan, -}; -use super::wrapper_mir::{WrapperMirBuilder, WrapperMirBundle}; -use super::*; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct ReachabilityRoot<'db> { - pub(in crate::backend) logical_name: String, - pub(in crate::backend) instance: InstanceKey<'db>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ReachabilityGraph<'db> { - pub(in crate::backend) mode: CompilationMode, - pub(in crate::backend) target_profile: TargetProfile, - pub(in crate::backend) roots: Vec>, - pub(in crate::backend) instances: Vec>, - pub(in crate::backend) functions: Vec>, - pub(in crate::backend) closures: Vec>, - pub(in crate::backend) imports: Vec>, - pub(in crate::backend) exports: Vec>, - pub(in crate::backend) types: Vec>, - pub(in crate::backend) callable_signatures: Vec, - pub(in crate::backend) edges: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum ReachabilityInstance<'db> { - Function(InstanceKey<'db>), - Closure(ClosureInstanceKey<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum ReachabilityNode<'db> { - Root(usize), - Function(InstanceKey<'db>), - Closure(ClosureInstanceKey<'db>), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum ReachabilityEdgeKind { - Root, - DirectCall, - ClosureLiteral, - FunctionValue, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) struct ReachabilityEdge<'db> { - pub(in crate::backend) from: ReachabilityNode<'db>, - pub(in crate::backend) to: ReachabilityNode<'db>, - pub(in crate::backend) kind: ReachabilityEdgeKind, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum RuntimeImportNeed { - Runtime(RuntimeFunction), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum CallableAdapterNeed<'db> { - ClosureEnv(ClosureInstanceKey<'db>), - BoundaryInvoke(InstanceKey<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum BoundaryWrapperNeed<'db> { - Import(InstanceKey<'db>), - Export(InstanceKey<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum CanonicalSupportNeed<'db> { - Type(Ty<'db>), - BlobHelpers, - HandleHelpers, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(in crate::backend) enum LayoutNeed<'db> { - Array(Ty<'db>), - Nominal(Ty<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EmissionObligations<'db> { - pub(in crate::backend) runtime_imports: Vec, - pub(in crate::backend) stage_intrinsics: Vec, - pub(in crate::backend) helpers: Vec, - pub(in crate::backend) reachable_arrays: Vec>, - pub(in crate::backend) reachable_nominals: Vec>, - pub(in crate::backend) runtime_import_needs: Vec, - pub(in crate::backend) callable_adapters: Vec>, - pub(in crate::backend) boundary_wrappers: Vec>, - pub(in crate::backend) canonical_support: Vec>, - pub(in crate::backend) layout_needs: Vec>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct FunctionInstanceId(pub(in crate::backend) u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct FunctionInstancePlan<'db> { - pub(in crate::backend) id: FunctionInstanceId, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) internal_signature: InternalSig, - pub(in crate::backend) boundary_signature: Option>, - pub(in crate::backend) metadata_index: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) enum HelperNeed { - MemoryEq, - StringEq, - ArcRetain, - ArcRelease, - AbiAlloc, - AbiBlobRelease, - AbiHandleRetain, - AbiHandleRelease, - NominalDestroy(u32), - NominalEq(u32), - ArrayDestroy(u32), - ArrayEq(u32), - HandleInvoke(SigId), -} - -impl HelperNeed { - pub(in crate::backend) fn from_helper_function(helper: HelperFunction) -> Self { - match helper { - HelperFunction::MemoryEq => Self::MemoryEq, - HelperFunction::StringEq => Self::StringEq, - HelperFunction::ArcRetain => Self::ArcRetain, - HelperFunction::ArcRelease => Self::ArcRelease, - } - } - - pub(in crate::backend) fn dump_name(self) -> String { - match self { - Self::MemoryEq => "memory_eq".to_owned(), - Self::StringEq => "string_eq".to_owned(), - Self::ArcRetain => "arc_retain".to_owned(), - Self::ArcRelease => "arc_release".to_owned(), - Self::AbiAlloc => mitki_abi::ABI_V2_ALLOC_EXPORT.to_owned(), - Self::AbiBlobRelease => mitki_abi::ABI_V2_BLOB_RELEASE_EXPORT.to_owned(), - Self::AbiHandleRetain => mitki_abi::ABI_V2_HANDLE_RETAIN_EXPORT.to_owned(), - Self::AbiHandleRelease => mitki_abi::ABI_V2_HANDLE_RELEASE_EXPORT.to_owned(), - Self::NominalDestroy(bits) => format!("nominal_destroy({bits})"), - Self::NominalEq(bits) => format!("nominal_eq({bits})"), - Self::ArrayDestroy(bits) => format!("array_destroy({bits})"), - Self::ArrayEq(bits) => format!("array_eq({bits})"), - Self::HandleInvoke(signature) => mitki_abi::handle_invoke_export_name(signature), - } - } - - pub(in crate::backend) fn export_name(self) -> Option { - match self { - Self::AbiAlloc - | Self::AbiBlobRelease - | Self::AbiHandleRetain - | Self::AbiHandleRelease - | Self::HandleInvoke(_) => Some(self.dump_name()), - Self::MemoryEq - | Self::StringEq - | Self::ArcRetain - | Self::ArcRelease - | Self::NominalDestroy(_) - | Self::NominalEq(_) - | Self::ArrayDestroy(_) - | Self::ArrayEq(_) => None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct ImportPlan { - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) module_name: String, - pub(in crate::backend) field_name: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct ExportPlan { - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) metadata_index: usize, - pub(in crate::backend) logical_name: String, - pub(in crate::backend) export_name: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct MemoryPlan { - pub(in crate::backend) memory_index: u32, - pub(in crate::backend) export_name: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct TablePlan; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct GlobalPlan; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct DataSegmentPlan; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct NamePlan { - pub(in crate::backend) logical_functions: Vec<(FunctionInstanceId, String)>, - pub(in crate::backend) typed_exports: Vec<(String, FunctionInstanceId)>, - pub(in crate::backend) helper_exports: Vec, -} - -#[derive(Clone)] -pub struct ModulePlan<'db> { - pub(in crate::backend) mode: CompilationMode, - pub(in crate::backend) target_profile: TargetProfile, - pub(in crate::backend) target_decisions: TargetDecisionSnapshot, - pub(in crate::backend) reachability: ReachabilityGraph<'db>, - pub(in crate::backend) obligations: EmissionObligations<'db>, - pub(in crate::backend) function_instances: Vec>, - pub(in crate::backend) imports: Vec, - pub(in crate::backend) exports: Vec, - pub(in crate::backend) helpers: HelperRegistryPlan, - pub(in crate::backend) callables: CallableRegistryPlan<'db>, - pub(in crate::backend) sections: SectionPlan<'db>, - pub(in crate::backend) memories: Vec, - pub(in crate::backend) tables: Vec, - pub(in crate::backend) globals: Vec, - pub(in crate::backend) data_segments: Vec, - pub(in crate::backend) abi_preview: BuiltAbiV2, - pub(in crate::backend) names: NamePlan, - pub(in crate::backend) boundary: BoundaryPlan<'db>, - pub(in crate::backend) wrapper_mir: WrapperMirBundle<'db>, - pub(in crate::backend) function_kernel: Option>, - pub(in crate::backend) function_wasm_ir: Option>, -} - -impl<'db> Backend<'db> { - pub fn build_module_plan(&self) -> Result, Diagnostic> { - self.target_policies().validate_backend_support(self.file_range())?; - let reachability = self.shadow_reachability_graph()?; - let obligations = self.shadow_emission_obligations()?; - let function_ids = reachability - .functions - .iter() - .enumerate() - .map(|(index, instance)| { - ( - instance.clone(), - FunctionInstanceId( - u32::try_from(index).expect("function count should fit u32"), - ), - ) - }) - .collect::>(); - let boundary = BoundaryPlanner::build(self, &reachability, &function_ids)?; - let function_instances = - self.build_function_instances(&reachability.functions, &function_ids, &boundary)?; - let abi_preview = boundary - .metadata - .build_preview(self.db) - .map_err(|message| Diagnostic::error(message, self.file_range()))?; - let helpers = HelperRegistry::build(&obligations, &boundary, &abi_preview); - let callables = CallableRegistry::build( - self, - &reachability, - &function_instances, - &boundary, - &obligations, - &abi_preview, - )?; - let sections = SectionAssigner::build( - self, - &reachability, - &obligations, - &boundary, - &abi_preview, - &helpers, - &callables, - )?; - let imports = Self::build_import_plan(&boundary); - let exports = Self::build_export_plan(&boundary); - let memories = vec![MemoryPlan { memory_index: 0, export_name: Some("memory".to_owned()) }]; - let names = NameAssigner::build(&function_instances, &boundary, &helpers); - let mut plan = ModulePlan { - mode: reachability.mode, - target_profile: self.target_profile(), - target_decisions: self.target_decision_snapshot(), - reachability, - obligations, - function_instances, - imports, - exports, - helpers, - callables, - sections, - memories, - tables: Vec::new(), - globals: Vec::new(), - data_segments: Vec::new(), - abi_preview, - names, - boundary, - wrapper_mir: WrapperMirBundle { - imports: Vec::new(), - callable_adapters: Vec::new(), - trampolines: Vec::new(), - exports: Vec::new(), - import_indices: FxHashMap::default(), - callable_adapter_indices: FxHashMap::default(), - trampoline_indices: FxHashMap::default(), - export_indices: FxHashMap::default(), - }, - function_kernel: None, - function_wasm_ir: None, - }; - plan.wrapper_mir = WrapperMirBuilder::build(self, &plan)?; - let function_kernel = FunctionKernelBuilder::build(self, &plan)?; - let function_kernel = OwnershipLowering::run(self, &plan, &function_kernel)?; - let function_kernel = FunctionLegalizer::run(self, &plan, &function_kernel)?; - let function_wasm_ir = StructuredWasmLowering::build(self, &plan, &function_kernel)?; - plan.function_kernel = Some(function_kernel); - plan.function_wasm_ir = Some(function_wasm_ir.clone()); - Ok(plan) - } - - #[cfg(test)] - pub(in crate::backend) fn build_reachability_graph(&self) -> ReachabilityGraph<'db> { - self.shadow_reachability_graph().unwrap_or_else(|diagnostic| { - panic!( - "reachability shadow state should exist before module planning: {}", - diagnostic.message() - ) - }) - } - - #[cfg(test)] - pub(in crate::backend) fn build_emission_obligations(&self) -> EmissionObligations<'db> { - self.shadow_emission_obligations().unwrap_or_else(|diagnostic| { - panic!( - "emission obligation shadow state should exist before module planning: {}", - diagnostic.message() - ) - }) - } - - fn shadow_reachability_graph(&self) -> Result, Diagnostic> { - self.shadow_reachability.clone().ok_or_else(|| { - Diagnostic::error( - "internal error: module planning requires collected reachability shadow state", - self.file_range(), - ) - }) - } - - fn shadow_emission_obligations(&self) -> Result, Diagnostic> { - self.shadow_obligations.clone().ok_or_else(|| { - Diagnostic::error( - "internal error: module planning requires collected emission obligations", - self.file_range(), - ) - }) - } - - fn build_function_instances( - &self, - reachable_functions: &[InstanceKey<'db>], - function_ids: &FxHashMap, FunctionInstanceId>, - boundary: &BoundaryPlan<'db>, - ) -> Result>, Diagnostic> { - let boundary_entries = boundary - .instances - .iter() - .map(|entry| (entry.instance.clone(), entry)) - .collect::>(); - let mut functions = Vec::with_capacity(reachable_functions.len()); - for instance in reachable_functions { - let hir_function = instance.location.hir_function(self.db); - let function = hir_function.function(self.db); - let inference = instance.location.infer(self.db); - let logical_name = instance - .location - .source(self.db) - .name() - .map_or("".to_owned(), |name| name.as_str().to_owned()); - let internal_signature = InternalSig::from_function_signature( - self.function_signature(instance, function, inference)?, - ); - let boundary_entry = boundary_entries.get(instance); - functions.push(FunctionInstancePlan { - id: *function_ids - .get(instance) - .expect("reachable function should have an assigned id"), - instance: instance.clone(), - logical_name, - internal_signature, - boundary_signature: boundary_entry.map(|entry| entry.signature.clone()), - metadata_index: boundary_entry.map(|entry| entry.metadata_index), - }); - } - Ok(functions) - } - - fn build_import_plan(boundary: &BoundaryPlan<'db>) -> Vec { - boundary - .imports - .iter() - .map(|entry| ImportPlan { - function_id: entry.function_id, - metadata_index: entry.metadata_index, - logical_name: entry.logical_name.clone(), - module_name: entry.module_name.clone(), - field_name: entry.field_name.clone(), - }) - .collect() - } - - fn build_export_plan(boundary: &BoundaryPlan<'db>) -> Vec { - boundary - .exports - .iter() - .map(|entry| ExportPlan { - function_id: entry.function_id, - metadata_index: entry.metadata_index, - logical_name: entry.logical_name.clone(), - export_name: entry.export_name.clone(), - }) - .collect() - } -} - -impl<'db> EmissionObligations<'db> { - pub(in crate::backend) fn needs_blob_helpers(&self) -> bool { - self.canonical_support.iter().any(|need| matches!(need, CanonicalSupportNeed::BlobHelpers)) - } - - pub(in crate::backend) fn needs_handle_helpers(&self) -> bool { - self.canonical_support - .iter() - .any(|need| matches!(need, CanonicalSupportNeed::HandleHelpers)) - } -} - -#[cfg(test)] -impl<'db> ReachabilityGraph<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - let mode = self.mode.dump_name(); - writeln!(&mut output, "reachability.mode: {mode}").expect("write to string"); - writeln!(&mut output, "reachability.target: {}", self.target_profile.canonical_name()) - .expect("write to string"); - writeln!(&mut output, "reachability.roots:").expect("write to string"); - for root in &self.roots { - writeln!( - &mut output, - " - {} => {}", - root.logical_name, - format_instance_key(db, &root.instance) - ) - .expect("write to string"); - } - writeln!(&mut output, "reachability.functions:").expect("write to string"); - for function in &self.functions { - writeln!(&mut output, " - {}", format_instance_key(db, function)) - .expect("write to string"); - } - writeln!(&mut output, "reachability.closures:").expect("write to string"); - for closure in &self.closures { - writeln!(&mut output, " - {}", format_closure_key(db, closure)) - .expect("write to string"); - } - output - } - - pub(crate) fn dump_details(&self, db: &dyn salsa::Database) -> String { - let mut output = self.dump(db); - writeln!(&mut output, "reachability.instances:").expect("write to string"); - for instance in &self.instances { - match instance { - ReachabilityInstance::Function(instance) => { - writeln!(&mut output, " - function {}", format_instance_key(db, instance)) - .expect("write to string"); - } - ReachabilityInstance::Closure(instance) => { - writeln!(&mut output, " - closure {}", format_closure_key(db, instance)) - .expect("write to string"); - } - } - } - writeln!(&mut output, "reachability.imports:").expect("write to string"); - for instance in &self.imports { - writeln!(&mut output, " - {}", format_instance_key(db, instance)) - .expect("write to string"); - } - writeln!(&mut output, "reachability.exports:").expect("write to string"); - for instance in &self.exports { - writeln!(&mut output, " - {}", format_instance_key(db, instance)) - .expect("write to string"); - } - writeln!(&mut output, "reachability.types:").expect("write to string"); - for ty in &self.types { - writeln!(&mut output, " - {}", ty.display(db)).expect("write to string"); - } - writeln!(&mut output, "reachability.callables:").expect("write to string"); - for signature in &self.callable_signatures { - writeln!( - &mut output, - " - ({}) -> {}", - signature.params.iter().map(format_abi_ty).collect::>().join(", "), - format_abi_ty(&signature.result) - ) - .expect("write to string"); - } - writeln!(&mut output, "reachability.edges:").expect("write to string"); - for edge in &self.edges { - writeln!( - &mut output, - " - {} -> {} ({})", - format_reachability_node(db, &self.roots, &edge.from), - format_reachability_node(db, &self.roots, &edge.to), - format_reachability_edge_kind(edge.kind) - ) - .expect("write to string"); - } - output - } -} - -#[cfg(test)] -impl<'db> EmissionObligations<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "obligations.runtime_imports:").expect("write to string"); - for runtime in &self.runtime_imports { - writeln!(&mut output, " - {}::{}", runtime.import_module(), runtime.import_name()) - .expect("write to string"); - } - writeln!(&mut output, "obligations.stage_intrinsics:").expect("write to string"); - for intrinsic in &self.stage_intrinsics { - writeln!(&mut output, " - {}", intrinsic.name()).expect("write to string"); - } - writeln!(&mut output, "obligations.helpers:").expect("write to string"); - for helper in &self.helpers { - writeln!(&mut output, " - {}", helper.name()).expect("write to string"); - } - writeln!(&mut output, "obligations.arrays:").expect("write to string"); - for ty in &self.reachable_arrays { - writeln!(&mut output, " - {}", ty.display(db)).expect("write to string"); - } - writeln!(&mut output, "obligations.nominals:").expect("write to string"); - for ty in &self.reachable_nominals { - writeln!(&mut output, " - {}", ty.display(db)).expect("write to string"); - } - output - } - - pub(crate) fn dump_details(&self, db: &dyn salsa::Database) -> String { - let mut output = self.dump(db); - writeln!(&mut output, "obligations.runtime_import_needs:").expect("write to string"); - for need in &self.runtime_import_needs { - let RuntimeImportNeed::Runtime(runtime) = need; - writeln!(&mut output, " - {}::{}", runtime.import_module(), runtime.import_name()) - .expect("write to string"); - } - writeln!(&mut output, "obligations.callable_adapters:").expect("write to string"); - for need in &self.callable_adapters { - match need { - CallableAdapterNeed::ClosureEnv(closure) => { - writeln!(&mut output, " - closure_env {}", format_closure_key(db, closure)) - .expect("write to string"); - } - CallableAdapterNeed::BoundaryInvoke(instance) => { - writeln!( - &mut output, - " - boundary_invoke {}", - format_instance_key(db, instance) - ) - .expect("write to string"); - } - } - } - writeln!(&mut output, "obligations.boundary_wrappers:").expect("write to string"); - for need in &self.boundary_wrappers { - match need { - BoundaryWrapperNeed::Import(instance) => { - writeln!(&mut output, " - import {}", format_instance_key(db, instance)) - .expect("write to string"); - } - BoundaryWrapperNeed::Export(instance) => { - writeln!(&mut output, " - export {}", format_instance_key(db, instance)) - .expect("write to string"); - } - } - } - writeln!(&mut output, "obligations.canonical_support:").expect("write to string"); - for need in &self.canonical_support { - match need { - CanonicalSupportNeed::Type(ty) => { - writeln!(&mut output, " - type {}", ty.display(db)).expect("write to string"); - } - CanonicalSupportNeed::BlobHelpers => { - writeln!(&mut output, " - blob_helpers").expect("write to string"); - } - CanonicalSupportNeed::HandleHelpers => { - writeln!(&mut output, " - handle_helpers").expect("write to string"); - } - } - } - writeln!(&mut output, "obligations.layout_needs:").expect("write to string"); - for need in &self.layout_needs { - match need { - LayoutNeed::Array(ty) => { - writeln!(&mut output, " - array {}", ty.display(db)).expect("write to string"); - } - LayoutNeed::Nominal(ty) => { - writeln!(&mut output, " - nominal {}", ty.display(db)) - .expect("write to string"); - } - } - } - output - } -} - -#[cfg(test)] -impl<'db> BoundaryPlan<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "boundary.entries:").expect("write to string"); - for entry in &self.instances { - let module = entry.wasm_module_name.as_deref().unwrap_or("-"); - writeln!( - &mut output, - " - [{}] [f{}] {} {} {} -> {}", - entry.metadata_index, - entry.function_id.0, - match entry.linkage { - AbiV2LinkageKind::WasmImport => "import", - AbiV2LinkageKind::WasmExport => "export", - AbiV2LinkageKind::StageEntry => "stage", - AbiV2LinkageKind::RawImport => "raw-import", - }, - entry.logical_name, - format_instance_key(db, &entry.instance), - entry.wasm_field_name - ) - .expect("write to string"); - writeln!(&mut output, " module: {module}").expect("write to string"); - if let Some(origin) = &entry.generic_origin_name { - writeln!(&mut output, " generic_origin: {origin}").expect("write to string"); - } - writeln!( - &mut output, - " internal: ({}) -> {}", - entry - .signature - .internal - .params - .iter() - .map(format_abi_ty) - .collect::>() - .join(", "), - format_internal_results(&entry.signature.internal.results) - ) - .expect("write to string"); - writeln!(&mut output, " boundary.params:").expect("write to string"); - for param in &entry.signature.params { - writeln!(&mut output, " - {}", format_boundary_slot(db, param)) - .expect("write to string"); - } - writeln!(&mut output, " boundary.results:").expect("write to string"); - if entry.signature.results.is_empty() { - writeln!(&mut output, " - ").expect("write to string"); - } else { - for result in &entry.signature.results { - writeln!(&mut output, " - {}", format_boundary_slot(db, result)) - .expect("write to string"); - } - } - } - writeln!(&mut output, "boundary.export_aliases:").expect("write to string"); - for alias in &self.aliases { - writeln!( - &mut output, - " - {} => {}", - alias.alias, - format_instance_key(db, &alias.instance) - ) - .expect("write to string"); - } - output - } - - pub(crate) fn dump_aliases(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "boundary.aliases:").expect("write to string"); - for alias in &self.aliases { - writeln!( - &mut output, - " - [{}] [f{}] {} => {} ({})", - alias.metadata_index, - alias.function_id.0, - alias.alias, - format_instance_key(db, &alias.instance), - alias.logical_name - ) - .expect("write to string"); - } - output - } - - pub(crate) fn dump_metadata(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "boundary.metadata:").expect("write to string"); - for (index, function) in self.metadata.functions.iter().enumerate() { - let module = function.wasm_module_name.as_deref().unwrap_or("-"); - writeln!( - &mut output, - " - [{}] {:?} {} {} -> {}", - index, function.linkage, function.logical_name, module, function.wasm_field_name - ) - .expect("write to string"); - writeln!( - &mut output, - " params: {}", - function - .param_tys - .iter() - .map(|ty| ty.display(db).to_string()) - .collect::>() - .join(", ") - ) - .expect("write to string"); - writeln!(&mut output, " result: {}", function.result_ty.display(db)) - .expect("write to string"); - } - output - } - - pub(crate) fn dump_wrapper_ops(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "boundary.wrapper_ops:").expect("write to string"); - for import in &self.imports { - writeln!( - &mut output, - " - import {} ({})", - import.logical_name, - format_instance_key(db, &import.instance) - ) - .expect("write to string"); - writeln!(&mut output, " params:").expect("write to string"); - for op in &import.wrapper.param_ops { - writeln!(&mut output, " - {}", format_transport_op(db, op)) - .expect("write to string"); - } - writeln!(&mut output, " results:").expect("write to string"); - for op in &import.wrapper.result_ops { - writeln!(&mut output, " - {}", format_transport_op(db, op)) - .expect("write to string"); - } - } - for export in &self.exports { - writeln!( - &mut output, - " - export {} ({})", - export.logical_name, - format_instance_key(db, &export.instance) - ) - .expect("write to string"); - writeln!(&mut output, " params:").expect("write to string"); - for op in &export.wrapper.param_ops { - writeln!(&mut output, " - {}", format_transport_op(db, op)) - .expect("write to string"); - } - writeln!(&mut output, " results:").expect("write to string"); - for op in &export.wrapper.result_ops { - writeln!(&mut output, " - {}", format_transport_op(db, op)) - .expect("write to string"); - } - } - output - } -} - -#[cfg(test)] -impl<'db> ModulePlan<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - let mode = self.mode.dump_name(); - writeln!(&mut output, "module.mode: {mode}").expect("write to string"); - writeln!(&mut output, "module.target: {}", self.target_profile.canonical_name()) - .expect("write to string"); - writeln!(&mut output, "module.target_decisions: {}", self.target_decisions.dump()) - .expect("write to string"); - output.push_str(&self.reachability.dump(db)); - output.push_str(&self.obligations.dump(db)); - writeln!(&mut output, "function_instances:").expect("write to string"); - for function in &self.function_instances { - writeln!( - &mut output, - " - [f{}] {} => {}", - function.id.0, - function.logical_name, - format_instance_key(db, &function.instance) - ) - .expect("write to string"); - writeln!( - &mut output, - " internal: ({}) -> {}", - function - .internal_signature - .params - .iter() - .map(format_abi_ty) - .collect::>() - .join(", "), - format_internal_results(&function.internal_signature.results) - ) - .expect("write to string"); - writeln!( - &mut output, - " metadata_index: {}", - function.metadata_index.map_or_else(|| "-".to_owned(), |index| index.to_string()) - ) - .expect("write to string"); - writeln!( - &mut output, - " boundary: {}", - if function.boundary_signature.is_some() { "present" } else { "none" } - ) - .expect("write to string"); - } - writeln!(&mut output, "imports:").expect("write to string"); - for import in &self.imports { - writeln!( - &mut output, - " - [f{}] [{}] {}::{} ({})", - import.function_id.0, - import.metadata_index, - import.module_name, - import.field_name, - import.logical_name - ) - .expect("write to string"); - } - writeln!(&mut output, "exports:").expect("write to string"); - for export in &self.exports { - writeln!( - &mut output, - " - [f{}] [{}] {} ({})", - export.function_id.0, - export.metadata_index, - export.export_name, - export.logical_name - ) - .expect("write to string"); - } - writeln!(&mut output, "helpers:").expect("write to string"); - for helper in &self.helpers.needs { - writeln!(&mut output, " - {}", helper.dump_name()).expect("write to string"); - } - writeln!(&mut output, "memories:").expect("write to string"); - for memory in &self.memories { - let export_name = memory.export_name.as_deref().unwrap_or("-"); - writeln!(&mut output, " - memory#{} export={export_name}", memory.memory_index) - .expect("write to string"); - } - writeln!(&mut output, "tables: {}", self.tables.len()).expect("write to string"); - writeln!(&mut output, "globals: {}", self.globals.len()).expect("write to string"); - writeln!(&mut output, "data_segments: {}", self.data_segments.len()) - .expect("write to string"); - writeln!(&mut output, "abi_preview.instances:").expect("write to string"); - for (index, function) in self.abi_preview.functions.iter().enumerate() { - writeln!( - &mut output, - " - [{}] {:?} {} sig=s{}", - index, function.linkage, function.wasm_field_name, function.signature_id.0 - ) - .expect("write to string"); - } - writeln!(&mut output, "names.logical_functions:").expect("write to string"); - for (function_id, logical_name) in &self.names.logical_functions { - writeln!(&mut output, " - [f{}] {}", function_id.0, logical_name) - .expect("write to string"); - } - writeln!(&mut output, "names.typed_exports:").expect("write to string"); - for (alias, function_id) in &self.names.typed_exports { - writeln!(&mut output, " - {alias} => f{}", function_id.0).expect("write to string"); - } - writeln!(&mut output, "names.helper_exports:").expect("write to string"); - for helper in &self.names.helper_exports { - writeln!(&mut output, " - {helper}").expect("write to string"); - } - output.push_str(&self.boundary.dump(db)); - output - } -} - -#[cfg(test)] -impl HelperRegistryPlan { - pub(crate) fn dump(&self) -> String { - let mut output = String::new(); - writeln!(&mut output, "helper_registry.needs:").expect("write to string"); - for helper in &self.needs { - writeln!(&mut output, " - {}", helper.dump_name()).expect("write to string"); - } - writeln!(&mut output, "helper_registry.builtin:").expect("write to string"); - for helper in &self.builtin_helpers { - writeln!(&mut output, " - {}", helper.name()).expect("write to string"); - } - writeln!(&mut output, "helper_registry.exports:").expect("write to string"); - for helper in &self.exported_helpers { - writeln!( - &mut output, - " - {}", - helper.export_name().expect("exported helper should have an export name") - ) - .expect("write to string"); - } - output - } -} - -#[cfg(test)] -impl<'db> CallableRegistryPlan<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "callable_registry.signatures:").expect("write to string"); - for callable in &self.signatures { - writeln!( - &mut output, - " - [c{}] ({}) -> {}", - callable.id.0, - callable.signature.params.iter().map(format_abi_ty).collect::>().join(", "), - format_abi_ty(&callable.signature.result) - ) - .expect("write to string"); - } - writeln!(&mut output, "callable_registry.trampolines:").expect("write to string"); - for trampoline in &self.invoke_trampolines { - writeln!( - &mut output, - " - s{} [f{}] {} [{}]", - trampoline.signature_id.0, - trampoline.function_id.0, - format_instance_key(db, &trampoline.instance), - trampoline.metadata_index - ) - .expect("write to string"); - } - output - } -} - -#[cfg(test)] -impl<'db> SectionPlan<'db> { - pub(crate) fn dump(&self, db: &dyn salsa::Database) -> String { - let mut output = String::new(); - writeln!(&mut output, "sections.runtime_imports:").expect("write to string"); - for runtime in &self.runtime_imports { - writeln!( - &mut output, - " - {}::{} [t{}] [f{}]", - runtime.import_module(), - runtime.import_name(), - self.runtime_type_indices[runtime], - self.runtime_function_indices[runtime] - ) - .expect("write to string"); - } - writeln!(&mut output, "sections.raw_imports:").expect("write to string"); - for instance in &self.raw_imports { - writeln!( - &mut output, - " - {} [t{}] [f{}]", - format_instance_key(db, instance), - self.external_type_indices[instance], - self.raw_import_function_indices[instance] - ) - .expect("write to string"); - } - writeln!(&mut output, "sections.callables:").expect("write to string"); - let mut callables = self.callable_type_indices.iter().collect::>(); - callables.sort_by_key(|(_, index)| **index); - for (signature, index) in callables { - writeln!( - &mut output, - " - [t{}] ({}) -> {}", - index, - signature.params.iter().map(format_abi_ty).collect::>().join(", "), - format_abi_ty(&signature.result) - ) - .expect("write to string"); - } - writeln!(&mut output, "sections.functions:").expect("write to string"); - for instance in &self.direct_functions { - writeln!( - &mut output, - " - direct {} [t{}] [f{}] wrapper=[f{}]", - format_instance_key(db, instance), - self.direct_type_indices[instance], - self.direct_function_indices[instance], - self.wrapper_function_indices[instance] - ) - .expect("write to string"); - } - for closure in &self.closure_functions { - writeln!( - &mut output, - " - closure {} [f{}]", - format_closure_key(db, closure), - self.closure_function_indices[closure] - ) - .expect("write to string"); - } - writeln!(&mut output, "sections.exports:").expect("write to string"); - for export in &self.exports { - match export.target { - SectionExportTarget::Func(index) => { - writeln!(&mut output, " - {} => func[{}]", export.name, index) - .expect("write to string"); - } - SectionExportTarget::Memory(index) => { - writeln!(&mut output, " - {} => memory[{}]", export.name, index) - .expect("write to string"); - } - } - } - writeln!(&mut output, "sections.table_slots:").expect("write to string"); - let mut slots = self.table_slots.iter().collect::>(); - slots.sort_by_key(|(_, slot)| **slot); - for (target, slot) in slots { - match target { - FunctionValueTarget::Function(instance) => { - writeln!( - &mut output, - " - slot[{}] function {} -> f{}", - slot, - format_instance_key(db, instance), - self.wrapper_function_indices[instance] - ) - .expect("write to string"); - } - FunctionValueTarget::Closure(closure) => { - writeln!( - &mut output, - " - slot[{}] closure {} -> f{}", - slot, - format_closure_key(db, closure), - self.closure_function_indices[closure] - ) - .expect("write to string"); - } - } - } - output - } -} - -#[cfg(test)] -fn format_instance_key(db: &dyn salsa::Database, instance: &InstanceKey<'_>) -> String { - let name = instance.location.source(db).name().map_or("", |name| name.as_str()); - if instance.type_args.is_empty() { - name.to_owned() - } else { - let type_args = instance - .type_args - .iter() - .map(|ty| ty.display(db).to_string()) - .collect::>() - .join(", "); - format!("{name}[{type_args}]") - } -} - -#[cfg(test)] -fn format_closure_key(db: &dyn salsa::Database, closure: &ClosureInstanceKey<'_>) -> String { - let owner = format_instance_key(db, &closure.owner_instance()); - format!("{owner}::closure#{:?}", closure.closure) -} - -#[cfg(test)] -fn format_abi_ty(abi: &AbiTy) -> String { - match abi { - AbiTy::Scalar(BackendTy::Int) => "i32".to_owned(), - AbiTy::Scalar(BackendTy::I64) => "i64".to_owned(), - AbiTy::Scalar(BackendTy::Bool) => "bool".to_owned(), - AbiTy::Scalar(BackendTy::Float) => "f64".to_owned(), - AbiTy::Scalar(BackendTy::Char) => "char".to_owned(), - AbiTy::Scalar(BackendTy::Ref(kind)) => format!("ref({kind:?})"), - AbiTy::Scalar(BackendTy::Unit) => "unit".to_owned(), - AbiTy::Aggregate(layout) => { - format!("aggregate(size={}, align={})", layout.size, layout.align) - } - } -} - -#[cfg(test)] -fn format_boundary_slot(db: &dyn salsa::Database, slot: &BoundarySlot<'_>) -> String { - format!( - "{} | {:?} | {}", - slot.semantic_ty.display(db), - slot.transport.transport_class, - format_abi_ty(&slot.runtime_abi) - ) -} - -#[cfg(test)] -fn format_transport_op(db: &dyn salsa::Database, op: &TransportOp<'_>) -> String { - match op { - TransportOp::ReadLane { lane, ty } => { - format!("read lane {lane} as {}", ty.display(db)) - } - TransportOp::NormalizeBool { lane } => { - format!("normalize bool lane {lane}") - } - TransportOp::DecodeCanonical { lane, ty } => { - format!("decode canonical lane {lane} as {}", ty.display(db)) - } - TransportOp::EncodeCanonical { lane, ty } => { - format!("encode canonical {} into lane {lane}", ty.display(db)) - } - TransportOp::HandleToFunction { lane, ty } => { - format!("handle lane {lane} -> {}", ty.display(db)) - } - TransportOp::FunctionToHandle { lane, ty } => { - format!("{} -> handle lane {lane}", ty.display(db)) - } - TransportOp::RetainNestedHandles { ty } => { - format!("retain nested handles in {}", ty.display(db)) - } - TransportOp::ReleaseCanonicalTemp { lane } => { - format!("release canonical temp lane {lane}") - } - } -} - -#[cfg(test)] -fn format_internal_results(results: &[AbiTy]) -> String { - match results { - [] => "()".to_owned(), - [result] => format_abi_ty(result), - many => format!("({})", many.iter().map(format_abi_ty).collect::>().join(", ")), - } -} - -#[cfg(test)] -fn format_reachability_node( - db: &dyn salsa::Database, - roots: &[ReachabilityRoot<'_>], - node: &ReachabilityNode<'_>, -) -> String { - match node { - ReachabilityNode::Root(index) => roots.get(*index).map_or_else( - || format!("root[{index}] "), - |root| format!("root[{index}] {}", root.logical_name), - ), - ReachabilityNode::Function(instance) => { - format!("function {}", format_instance_key(db, instance)) - } - ReachabilityNode::Closure(closure) => { - format!("closure {}", format_closure_key(db, closure)) - } - } -} - -#[cfg(test)] -fn format_reachability_edge_kind(kind: ReachabilityEdgeKind) -> &'static str { - match kind { - ReachabilityEdgeKind::Root => "root", - ReachabilityEdgeKind::DirectCall => "direct_call", - ReachabilityEdgeKind::ClosureLiteral => "closure_literal", - ReachabilityEdgeKind::FunctionValue => "function_value", - } -} - -#[cfg(test)] -mod tests { - use std::fmt::Write as _; - - use expect_test::{Expect, expect}; - use mitki_comptime_wasm::compile_file_to_wasm; - use mitki_db::RootDatabase; - use mitki_inputs::File; - use mitki_wasm_runtime::{WasmExternAbiKind, describe_module_abi}; - - use super::*; - - fn new_compiler_for_fixture(fixture: &str) -> Backend<'_> { - let db = Box::leak(Box::new(RootDatabase::default())); - let file = File::new(db, "module_plan_fixture.mitki".into(), fixture.to_owned()); - let diagnostics = mitki_analysis::check_file(db, file); - assert!( - diagnostics.is_empty(), - "unexpected diagnostics: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let runtime_diagnostics = mitki_analysis::check_runtime_file(db, file); - assert!( - runtime_diagnostics.is_empty(), - "unexpected runtime diagnostics: {:?}", - runtime_diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - Backend::new_file_with_options( - db, - file, - crate::CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ) - } - - fn compiler_for_fixture(fixture: &str) -> Backend<'_> { - let mut backend = new_compiler_for_fixture(fixture); - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - backend - } - - fn assert_module_plan_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.dump(backend.db)); - } - - fn assert_reachability_graph_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let reachability = backend.build_reachability_graph(); - expected.assert_eq(&reachability.dump(backend.db)); - } - - fn assert_reachability_graph_details_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let reachability = backend.build_reachability_graph(); - expected.assert_eq(&reachability.dump_details(backend.db)); - } - - fn assert_emission_obligations_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let obligations = backend.build_emission_obligations(); - expected.assert_eq(&obligations.dump(backend.db)); - } - - fn assert_emission_obligations_details_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let obligations = backend.build_emission_obligations(); - expected.assert_eq(&obligations.dump_details(backend.db)); - } - - fn assert_function_instance_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - let mut output = String::new(); - writeln!(&mut output, "function_instances:").expect("write to string"); - for function in &plan.function_instances { - writeln!( - &mut output, - " - [f{}] {} => {}", - function.id.0, - function.logical_name, - format_instance_key(backend.db, &function.instance) - ) - .expect("write to string"); - writeln!( - &mut output, - " internal: ({}) -> {}", - function - .internal_signature - .params - .iter() - .map(format_abi_ty) - .collect::>() - .join(", "), - format_internal_results(&function.internal_signature.results) - ) - .expect("write to string"); - writeln!( - &mut output, - " metadata_index: {}", - function.metadata_index.map_or_else(|| "-".to_owned(), |index| index.to_string()) - ) - .expect("write to string"); - writeln!( - &mut output, - " boundary: {}", - if function.boundary_signature.is_some() { "present" } else { "none" } - ) - .expect("write to string"); - } - expected.assert_eq(&output); - } - - fn assert_helper_needs_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - let mut output = String::new(); - writeln!(&mut output, "helpers:").expect("write to string"); - for helper in &plan.helpers.needs { - writeln!(&mut output, " - {}", helper.dump_name()).expect("write to string"); - } - expected.assert_eq(&output); - } - - fn assert_helper_registry_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.helpers.dump()); - } - - fn assert_callable_registry_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.callables.dump(backend.db)); - } - - fn assert_section_plan_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.sections.dump(backend.db)); - } - - fn assert_boundary_alias_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.boundary.dump_aliases(backend.db)); - } - - fn assert_boundary_metadata_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.boundary.dump_metadata(backend.db)); - } - - fn assert_boundary_wrapper_ops_dump(fixture: &str, expected: &Expect) { - let backend = compiler_for_fixture(fixture); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - expected.assert_eq(&plan.boundary.dump_wrapper_ops(backend.db)); - } - - fn assert_abi_summary(fixture: &str, expected: &Expect) { - let db = RootDatabase::default(); - let file = File::new(&db, "module_plan_abi.mitki".into(), fixture.to_owned()); - let bytes = compile_file_to_wasm(&db, file).unwrap_or_else(|diagnostics| { - panic!( - "fixture should compile: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ) - }); - let abi = describe_module_abi(&bytes).expect("ABI should decode"); - let mut summary = String::new(); - writeln!(&mut summary, "imports:").expect("write to string"); - for import in &abi.imports { - writeln!( - &mut summary, - " - {}::{} {}", - import.module, - import.name, - format_extern_kind(&import.kind) - ) - .expect("write to string"); - } - writeln!(&mut summary, "exports:").expect("write to string"); - for export in &abi.exports { - writeln!(&mut summary, " - {} {}", export.name, format_extern_kind(&export.kind)) - .expect("write to string"); - } - if let Some(metadata) = abi.metadata.as_ref() { - writeln!(&mut summary, "metadata.instances:").expect("write to string"); - for instance in &metadata.function_instances { - let name = metadata - .nominal_symbols - .get(instance.logical_symbol.0 as usize) - .and_then(|&string_id| metadata.strings.get(string_id.0 as usize)) - .cloned() - .unwrap_or_else(|| "".to_owned()); - let wasm_name = instance - .wasm_field_name - .and_then(|id| metadata.strings.get(id.0 as usize)) - .map_or("-", String::as_str); - writeln!(&mut summary, " - {:?} {} -> {}", instance.linkage, name, wasm_name) - .expect("write to string"); - } - } - expected.assert_eq(&summary); - } - - fn assert_normalized_wat(fixture: &str, expected: &Expect) { - let db = RootDatabase::default(); - let file = File::new(&db, "module_plan_wat.mitki".into(), fixture.to_owned()); - let bytes = compile_file_to_wasm(&db, file).unwrap_or_else(|diagnostics| { - panic!( - "fixture should compile: {:?}", - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ) - }); - let wat = wasmprinter::print_bytes(&bytes).expect("WAT should print"); - let normalized = wat - .lines() - .map(str::trim) - .filter(|line| { - line.starts_with("(import") - || line.starts_with("(memory") - || line.starts_with("(export") - }) - .collect::>() - .join("\n"); - let normalized = format!("{}\n", normalized.trim_end()); - expected.assert_eq(&normalized); - } - - fn format_extern_kind(kind: &WasmExternAbiKind) -> String { - match kind { - WasmExternAbiKind::Function(function) => { - format!("fn({}) -> ({})", function.params.join(", "), function.results.join(", ")) - } - WasmExternAbiKind::Table => "table".to_owned(), - WasmExternAbiKind::Memory => "memory".to_owned(), - WasmExternAbiKind::Global => "global".to_owned(), - WasmExternAbiKind::Tag => "tag".to_owned(), - } - } - - #[test] - fn module_plan_dump_tracks_roots_boundary_instances_and_obligations() { - assert_module_plan_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -module.mode: runtime -module.target: wasm-core-v2/m32 -module.target_decisions: guest_word=i32 result_lowering=spill_to_locals callable_representation=handle_and_table boundary_transport=abi_v2_wrappers reference_representation=linear_memory_managed failure_lowering=trap_only -reachability.mode: runtime -reachability.target: wasm-core-v2/m32 -reachability.roots: - - id => id[int] - - id => id[bool] -reachability.functions: - - id[int] - - id[bool] - - round_trip[int] - - round_trip[bool] -reachability.closures: -obligations.runtime_imports: -obligations.stage_intrinsics: -obligations.helpers: -obligations.arrays: -obligations.nominals: -function_instances: - - [f0] id => id[int] - internal: (i32) -> i32 - metadata_index: 2 - boundary: present - - [f1] id => id[bool] - internal: (bool) -> bool - metadata_index: 3 - boundary: present - - [f2] round_trip => round_trip[int] - internal: (i32) -> i32 - metadata_index: 0 - boundary: present - - [f3] round_trip => round_trip[bool] - internal: (bool) -> bool - metadata_index: 1 - boundary: present -imports: - - [f2] [0] env::mitki:typed/2/f$0 (round_trip) - - [f3] [1] env::mitki:typed/2/f$1 (round_trip) -exports: - - [f0] [2] mitki:typed/2/f$2 (id) - - [f1] [3] mitki:typed/2/f$3 (id) -helpers: -memories: - - memory#0 export=memory -tables: 0 -globals: 0 -data_segments: 0 -abi_preview.instances: - - [0] WasmImport mitki:typed/2/f$0 sig=s0 - - [1] WasmImport mitki:typed/2/f$1 sig=s1 - - [2] WasmExport mitki:typed/2/f$2 sig=s2 - - [3] WasmExport mitki:typed/2/f$3 sig=s3 -names.logical_functions: - - [f0] id - - [f1] id - - [f2] round_trip - - [f3] round_trip -names.typed_exports: - - mitki:typed/2/f$2 => f0 - - mitki:typed/2/f$3 => f1 -names.helper_exports: -boundary.entries: - - [0] [f2] import round_trip round_trip[int] -> mitki:typed/2/f$0 - module: env - generic_origin: round_trip - internal: (i32) -> i32 - boundary.params: - - int | Immediate | i32 - boundary.results: - - int | Immediate | i32 - - [1] [f3] import round_trip round_trip[bool] -> mitki:typed/2/f$1 - module: env - generic_origin: round_trip - internal: (bool) -> bool - boundary.params: - - bool | Immediate | bool - boundary.results: - - bool | Immediate | bool - - [2] [f0] export id id[int] -> mitki:typed/2/f$2 - module: - - generic_origin: id - internal: (i32) -> i32 - boundary.params: - - int | Immediate | i32 - boundary.results: - - int | Immediate | i32 - - [3] [f1] export id id[bool] -> mitki:typed/2/f$3 - module: - - generic_origin: id - internal: (bool) -> bool - boundary.params: - - bool | Immediate | bool - boundary.results: - - bool | Immediate | bool -boundary.export_aliases: - - mitki:typed/2/f$2 => id[int] - - mitki:typed/2/f$3 => id[bool] -"#]], - ); - } - - #[test] - fn module_plan_target_decisions_are_stable_across_repeated_planning() { - let backend = compiler_for_fixture( - r#" -export fun main(): int { - 42 -} -"#, - ); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .target_decisions - .dump(); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .target_decisions - .dump(); - assert_eq!(first, second); - assert_eq!( - second, - "guest_word=i32 result_lowering=spill_to_locals \ - callable_representation=handle_and_table boundary_transport=abi_v2_wrappers \ - reference_representation=linear_memory_managed failure_lowering=trap_only" - ); - } - - #[test] - fn reachability_graph_dump_tracks_roots_and_reachable_instances() { - assert_reachability_graph_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -reachability.mode: runtime -reachability.target: wasm-core-v2/m32 -reachability.roots: - - id => id[int] - - id => id[bool] -reachability.functions: - - id[int] - - id[bool] - - round_trip[int] - - round_trip[bool] -reachability.closures: -"#]], - ); - } - - #[test] - fn reachability_graph_details_dump_tracks_edges_imports_exports_types_and_callables() { - assert_reachability_graph_details_dump( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - &expect![[r#" -reachability.mode: runtime -reachability.target: wasm-core-v2/m32 -reachability.roots: - - id => id -reachability.functions: - - id - - round_trip -reachability.closures: -reachability.instances: - - function id - - function round_trip -reachability.imports: - - round_trip -reachability.exports: - - id -reachability.types: - - fun(int) -> int - - int -reachability.callables: - - (aggregate(size=8, align=4)) -> aggregate(size=8, align=4) -reachability.edges: - - root[0] id -> function id (root) - - function id -> function round_trip (direct_call) -"#]], - ); - } - - #[test] - fn abi_summary_dump_is_stable_for_typed_exports_and_helpers() { - assert_abi_summary( - r#" -export fun words(): [str] { - ["a", "b"] -} -"#, - &expect![[r#" -imports: - - mitki::alloc fn(i32, i32) -> (i32) - - mitki::dealloc fn(i32, i32, i32) -> () -exports: - - mitki:typed/2/f$0 fn() -> (i32) - - mitki:abi/2/alloc fn(i32, i32) -> (i32) - - mitki:abi/2/blob_release fn(i32) -> () - - memory memory -metadata.instances: - - WasmExport words -> mitki:typed/2/f$0 -"#]], - ); - } - - #[test] - fn normalized_wat_fragments_track_imports_and_exports() { - assert_normalized_wat( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; - -export fun main(): int { - round_trip(41) -} -"#, - &expect![[r#" -(import "env" "mitki:typed/2/f$0" (func (;0;) (type 7))) -(memory (;0;) 1) -(export "mitki:typed/2/f$1" (func 9)) -(export "memory" (memory 0)) -"#]], - ); - } - - #[test] - fn repeated_reachability_collection_rebuilds_graph_state() { - let mut backend = new_compiler_for_fixture( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - backend.collect_reachable_program(); - let first = backend.build_reachability_graph().dump(backend.db); - - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let second = backend.build_reachability_graph().dump(backend.db); - - assert_eq!(first, second); - } - - #[test] - fn repeated_obligation_collection_rebuilds_obligation_details_stably() { - let mut backend = new_compiler_for_fixture( - r#" -export fun words(): [str] { - ["a", "b"] -} -"#, - ); - - backend.collect_reachable_program(); - let first = backend.build_emission_obligations().dump_details(backend.db); - - backend.collect_reachable_program(); - assert!( - backend.diagnostics.is_empty(), - "unexpected Backend diagnostics: {:?}", - backend.diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>() - ); - let second = backend.build_emission_obligations().dump_details(backend.db); - - assert_eq!(first, second); - } - - #[test] - fn function_instance_ids_are_stable_across_repeated_collection() { - let mut backend = new_compiler_for_fixture( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - backend.collect_reachable_program(); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .function_instances - .into_iter() - .map(|function| (function.id, format_instance_key(backend.db, &function.instance))) - .collect::>(); - - backend.collect_reachable_program(); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .function_instances - .into_iter() - .map(|function| (function.id, format_instance_key(backend.db, &function.instance))) - .collect::>(); - - assert_eq!(first, second); - assert_eq!( - second, - vec![ - (FunctionInstanceId(0), "id[int]".to_owned()), - (FunctionInstanceId(1), "id[bool]".to_owned()), - ] - ); - } - - #[test] - fn function_instance_dump_tracks_passive_ids_and_metadata() { - assert_function_instance_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -function_instances: - - [f0] id => id[int] - internal: (i32) -> i32 - metadata_index: 2 - boundary: present - - [f1] id => id[bool] - internal: (bool) -> bool - metadata_index: 3 - boundary: present - - [f2] round_trip => round_trip[int] - internal: (i32) -> i32 - metadata_index: 0 - boundary: present - - [f3] round_trip => round_trip[bool] - internal: (bool) -> bool - metadata_index: 1 - boundary: present -"#]], - ); - } - - #[test] - fn emission_obligations_dump_tracks_runtime_helpers_arrays_and_nominals() { - assert_emission_obligations_dump( - r#" -struct Boxed { - items: [str], -} - -export fun build(): Boxed { - val same = "a" == "b"; - Boxed { items: ["a", "b"] } -} -"#, - &expect![[r#" -obligations.runtime_imports: - - mitki::alloc - - mitki::dealloc -obligations.stage_intrinsics: -obligations.helpers: - - arc_release - - arc_retain - - memory_eq - - string_eq -obligations.arrays: - - [str] -obligations.nominals: - - Boxed -"#]], - ); - } - - #[test] - fn emission_obligations_details_dump_tracks_canonical_boundary_support() { - assert_emission_obligations_details_dump( - r#" -export fun words(): [str] { - ["a", "b"] -} -"#, - &expect![[r#" -obligations.runtime_imports: - - mitki::alloc - - mitki::dealloc -obligations.stage_intrinsics: -obligations.helpers: - - arc_release - - arc_retain -obligations.arrays: - - [str] -obligations.nominals: -obligations.runtime_import_needs: - - mitki::alloc - - mitki::dealloc -obligations.callable_adapters: -obligations.boundary_wrappers: - - export words -obligations.canonical_support: - - type [str] - - blob_helpers -obligations.layout_needs: - - array [str] -"#]], - ); - } - - #[test] - fn emission_obligations_details_dump_tracks_callable_boundary_support() { - assert_emission_obligations_details_dump( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - &expect![[r#" -obligations.runtime_imports: - - mitki::alloc - - mitki::dealloc -obligations.stage_intrinsics: -obligations.helpers: -obligations.arrays: -obligations.nominals: -obligations.runtime_import_needs: - - mitki::alloc - - mitki::dealloc -obligations.callable_adapters: - - boundary_invoke id - - boundary_invoke round_trip -obligations.boundary_wrappers: - - import round_trip - - export id -obligations.canonical_support: - - handle_helpers -obligations.layout_needs: -"#]], - ); - } - - #[test] - fn helper_needs_dump_tracks_semantic_and_abi_helpers() { - assert_helper_needs_dump( - r#" -struct Boxed { - items: [str], -} - -export fun build(): Boxed { - val same = "a" == "b"; - Boxed { items: ["a", "b"] } -} -"#, - &expect![[r#" -helpers: - - memory_eq - - string_eq - - arc_retain - - arc_release - - mitki:abi/2/alloc - - mitki:abi/2/blob_release - - nominal_destroy(11265) - - nominal_eq(11265) - - array_destroy(11280) - - array_eq(11280) -"#]], - ); - } - - #[test] - fn helper_needs_dump_tracks_handle_helpers_and_invoke_exports() { - assert_helper_needs_dump( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - &expect![[r#" -helpers: - - mitki:abi/2/handle_retain - - mitki:abi/2/handle_release - - mitki:abi/2/invoke$0 - - mitki:abi/2/invoke$1 -"#]], - ); - } - - #[test] - fn helper_registry_dump_tracks_builtin_and_exported_helpers() { - assert_helper_registry_dump( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - &expect![[r#" -helper_registry.needs: - - mitki:abi/2/handle_retain - - mitki:abi/2/handle_release - - mitki:abi/2/invoke$0 - - mitki:abi/2/invoke$1 -helper_registry.builtin: - - memory_eq - - string_eq - - arc_retain - - arc_release -helper_registry.exports: - - mitki:abi/2/handle_retain - - mitki:abi/2/handle_release - - mitki:abi/2/invoke$0 - - mitki:abi/2/invoke$1 -"#]], - ); - } - - #[test] - fn callable_registry_dump_deduplicates_repeated_signatures() { - assert_callable_registry_dump( - r#" -fun first(value: int): int { - value -} - -fun second(value: int): int { - first(value) -} - -export fun third(value: int): int { - second(value) -} -"#, - &expect![[r#" -callable_registry.signatures: - - [c0] (i32) -> i32 -callable_registry.trampolines: -"#]], - ); - } - - #[test] - fn section_plan_dump_tracks_indices_and_exports() { - assert_section_plan_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -sections.runtime_imports: -sections.raw_imports: - - round_trip[int] [t10] [f0] - - round_trip[bool] [t11] [f1] -sections.callables: - - [t12] (i32) -> i32 - - [t13] (bool) -> bool -sections.functions: - - direct id[int] [t4] [f6] wrapper=[f10] - - direct id[bool] [t5] [f7] wrapper=[f11] - - direct round_trip[int] [t6] [f8] wrapper=[f12] - - direct round_trip[bool] [t7] [f9] wrapper=[f13] -sections.exports: - - mitki:typed/2/f$2 => func[14] - - mitki:typed/2/f$3 => func[15] - - memory => memory[0] -sections.table_slots: - - slot[0] function id[int] -> f10 - - slot[1] function id[bool] -> f11 - - slot[2] function round_trip[int] -> f12 - - slot[3] function round_trip[bool] -> f13 -"#]], - ); - } - - #[test] - fn boundary_alias_dump_tracks_logical_to_physical_mapping() { - assert_boundary_alias_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -boundary.aliases: - - [2] [f0] mitki:typed/2/f$2 => id[int] (id) - - [3] [f1] mitki:typed/2/f$3 => id[bool] (id) -"#]], - ); - } - - #[test] - fn boundary_metadata_dump_tracks_metadata_facing_instances() { - assert_boundary_metadata_dump( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - &expect![[r#" -boundary.metadata: - - [0] WasmImport round_trip env -> mitki:typed/2/f$0 - params: int - result: int - - [1] WasmImport round_trip env -> mitki:typed/2/f$1 - params: bool - result: bool - - [2] WasmExport id - -> mitki:typed/2/f$2 - params: int - result: int - - [3] WasmExport id - -> mitki:typed/2/f$3 - params: bool - result: bool -"#]], - ); - } - - #[test] - fn boundary_wrapper_ops_dump_tracks_bool_normalization() { - assert_boundary_wrapper_ops_dump( - r#" -export fun invert(value: bool): bool { - value -} -"#, - &expect![[r#" -boundary.wrapper_ops: - - export invert (invert) - params: - - read lane 0 as bool - - normalize bool lane 0 - results: - - read lane 0 as bool - - normalize bool lane 0 -"#]], - ); - } - - #[test] - fn boundary_wrapper_ops_dump_tracks_canonical_blob_transport() { - assert_boundary_wrapper_ops_dump( - r#" -export fun words(): [str] { - ["a", "b"] -} -"#, - &expect![[r#" -boundary.wrapper_ops: - - export words (words) - params: - results: - - encode canonical [str] into lane 0 - - retain nested handles in [str] - - release canonical temp lane 0 -"#]], - ); - } - - #[test] - fn boundary_wrapper_ops_dump_tracks_function_handle_transport() { - assert_boundary_wrapper_ops_dump( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - &expect![[r#" -boundary.wrapper_ops: - - import round_trip (round_trip) - params: - - fun(int) -> int -> handle lane 0 - results: - - handle lane 0 -> fun(int) -> int - - export id (id) - params: - - handle lane 0 -> fun(int) -> int - results: - - fun(int) -> int -> handle lane 0 -"#]], - ); - } - - #[test] - fn boundary_signatures_compare_by_embedded_transport_plan() { - let backend = compiler_for_fixture( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - let plan = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - assert_eq!(plan.boundary.instances[0].signature, plan.boundary.instances[2].signature); - assert_ne!(plan.boundary.instances[0].signature, plan.boundary.instances[1].signature); - } - - #[test] - fn boundary_planner_is_stable_across_repeated_planning() { - let mut backend = new_compiler_for_fixture( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - backend.collect_reachable_program(); - let first = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - backend.collect_reachable_program(); - let second = backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - - assert_eq!(first.boundary.metadata.functions, second.boundary.metadata.functions); - assert_eq!(first.boundary.import_indices, second.boundary.import_indices); - assert_eq!(first.boundary.export_indices, second.boundary.export_indices); - assert_eq!(first.boundary.aliases, second.boundary.aliases); - } - - #[test] - fn boundary_alias_order_is_stable_across_repeated_collection() { - let mut backend = new_compiler_for_fixture( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - backend.collect_reachable_program(); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .boundary - .dump_aliases(backend.db); - - backend.collect_reachable_program(); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .boundary - .dump_aliases(backend.db); - - assert_eq!(first, second); - } - - #[test] - fn module_plan_builds_for_else_if_value_branches() { - let backend = compiler_for_fixture( - r#" -fun choose(first: bool, second: bool, start: int): int { - if first { - start + 1 - } else if second { - start + 2 - } else { - start - } -} - -export fun main(): int { - choose(false, true, 10) -} -"#, - ); - - backend.build_module_plan().unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }); - } - - #[test] - fn section_plan_order_is_stable_across_repeated_collection() { - let mut backend = new_compiler_for_fixture( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; -import instance round_trip[bool]; - -fun id[T](value: T): T { - round_trip(value) -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - backend.collect_reachable_program(); - let first = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .sections - .dump(backend.db); - - backend.collect_reachable_program(); - let second = backend - .build_module_plan() - .unwrap_or_else(|diagnostic| { - panic!("module plan should build: {}", diagnostic.message()) - }) - .sections - .dump(backend.db); - - assert_eq!(first, second); - } -} diff --git a/crates/mitki-backend-wasm/src/reachability.rs b/crates/mitki-backend-wasm/src/reachability.rs deleted file mode 100644 index 4e6ec2b..0000000 --- a/crates/mitki-backend-wasm/src/reachability.rs +++ /dev/null @@ -1,2465 +0,0 @@ -use mitki_hir::hir::ParamId; -use mitki_resolve::{BindingId, resolve_method_for_receiver}; - -use super::obligations::ObligationCollector; -use super::reachability_graph::ReachabilityBuilder; -use super::*; - -#[derive(Clone, Copy)] -struct ResolvedMethodCall<'db> { - receiver: ExprId, - function: FunctionLocation<'db>, -} - -pub(super) fn walk_reachable_instance<'db>( - backend: &mut Backend<'db>, - instance: &ReachableInstance<'db>, - pending: &mut VecDeque>, - edges: &mut Vec>, - seen_edges: &mut FxHashSet>, -) { - let location = instance.owner_location(); - let hir_function = location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let source_map = hir_function.source_map(backend.db); - let inference = location.infer(backend.db); - let body = match &instance { - ReachableInstance::Function(_) => function.body(), - ReachableInstance::Closure(closure) => { - match backend.closure_info(closure, function, inference) { - Ok(info) => info.body, - Err(diagnostic) => { - backend.diagnostics.push(diagnostic); - return; - } - } - } - }; - if body == ExprId::ZERO { - return; - } - - let resolver = Resolver::new(backend.db, location); - let mut validator = Validator { - backend, - owner_instance: instance.owner_instance(), - location, - function, - source_map, - inference, - resolver, - pending, - edges, - seen_edges, - current_node: match instance { - ReachableInstance::Function(instance) => { - plan::ReachabilityNode::Function(instance.clone()) - } - ReachableInstance::Closure(instance) => { - plan::ReachabilityNode::Closure(instance.clone()) - } - }, - loop_depth: 0, - }; - validator.expr(body, ExprPosition::Value); -} - -pub(super) fn collect_instance_emission_obligations<'db>( - backend: &Backend<'db>, - instance: &ReachableInstance<'db>, - obligations: &mut EmissionObligationScratch<'db>, -) { - let location = instance.owner_location(); - let hir_function = location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = location.infer(backend.db); - let owner_instance = instance.owner_instance(); - collect_signature_emission_obligations( - backend, - instance, - function, - inference, - &owner_instance, - obligations, - ); - - if let ReachableInstance::Closure(closure) = instance - && let Ok(info) = backend.closure_info(closure, function, inference) - && info.env_layout.size > 0 - { - obligations.used_runtime_functions.insert(RuntimeFunction::Alloc); - obligations.used_runtime_functions.insert(RuntimeFunction::Dealloc); - } - - let body = match instance { - ReachableInstance::Function(_) => function.body(), - ReachableInstance::Closure(closure) => backend - .closure_info(closure, function, inference) - .map_or(ExprId::ZERO, |info| info.body), - }; - if body == ExprId::ZERO { - return; - } - - let resolver = Resolver::new(backend.db, location); - let mut collector = ObligationVisitor { - backend, - obligations, - owner_instance, - location, - function, - inference, - resolver, - }; - collector.expr(body); -} - -fn collect_signature_emission_obligations<'db>( - backend: &Backend<'db>, - instance: &ReachableInstance<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - owner_instance: &InstanceKey<'db>, - obligations: &mut EmissionObligationScratch<'db>, -) { - let location = instance.owner_location(); - let is_top_level = matches!(instance, ReachableInstance::Function(_)); - let is_typed_wasm_boundary = is_top_level - && matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::ImplicitMainExport | WasmLinkage::Export - ); - let is_raw_import = is_top_level && matches!(function.linkage(), WasmLinkage::RawImport { .. }); - let is_stage_root = backend.compilation_mode().is_stage() - && is_top_level - && backend.stage_root() == Some(location); - let crosses_wasm_boundary = is_typed_wasm_boundary || is_raw_import || is_stage_root; - let mut boundary_params_need_runtime_allocs = false; - let transport_profile = backend.boundary_transport_profile(); - let params = match instance { - ReachableInstance::Function(_) => { - match backend.function_signature_types(owner_instance, function, inference) { - Ok((params, _)) => params, - Err(_) => return, - } - } - ReachableInstance::Closure(closure) => { - let Some(closure_ty) = - backend.specialized_expr_ty(owner_instance, inference, closure.closure) - else { - return; - }; - let TyKind::Function { inputs, .. } = closure_ty.kind(backend.db) else { - return; - }; - inputs.clone() - } - }; - - for ty in params { - obligations.register_nominals_in_ty(backend.db, ty); - if is_typed_wasm_boundary { - boundary_params_need_runtime_allocs |= transport_profile - .plan_or_message( - backend.db, - ty, - "Wasm backend does not support this function signature type", - ) - .is_ok_and(|_| { - crate::capability::supported_value_abi(backend.db, ty).is_some_and( - |runtime_abi| { - transport_profile.requires_runtime_allocs(backend.db, ty, &runtime_abi) - }, - ) - }); - } - } - - let return_ty = match instance { - ReachableInstance::Function(_) => backend.specialize_ty( - owner_instance, - backend.function_return_ty(owner_instance.location, function, inference), - ), - ReachableInstance::Closure(closure) => { - let Some(closure_ty) = - backend.specialized_expr_ty(owner_instance, inference, closure.closure) - else { - return; - }; - let TyKind::Function { output, .. } = closure_ty.kind(backend.db) else { - return; - }; - *output - } - }; - obligations.register_nominals_in_ty(backend.db, return_ty); - - if crosses_wasm_boundary { - let result_needs_runtime_allocs = transport_profile - .plan_or_message( - backend.db, - return_ty, - "Wasm backend does not support this function signature type", - ) - .is_ok_and(|_| { - crate::capability::supported_value_abi(backend.db, return_ty).is_some_and( - |runtime_abi| { - transport_profile.requires_runtime_allocs( - backend.db, - return_ty, - &runtime_abi, - ) - }, - ) - }); - if boundary_params_need_runtime_allocs || result_needs_runtime_allocs { - obligations.used_runtime_functions.insert(RuntimeFunction::Alloc); - obligations.used_runtime_functions.insert(RuntimeFunction::Dealloc); - } - } -} - -impl<'db> Backend<'db> { - pub fn collect_reachable_program(&mut self) { - let reachability = ReachabilityBuilder::new(self).build(); - let obligations = ObligationCollector::build(&*self, &reachability); - self.set_shadow_state(reachability, obligations.shadow_obligations); - } - - pub(super) fn is_stage_mode(&self) -> bool { - self.compilation_mode().is_stage() - } - - pub fn collect_stage_diagnostics(&mut self) { - if !self.compilation_mode().is_stage() { - return; - } - - let Some(graph) = self.shadow_reachability.as_ref() else { - return; - }; - let relevant_ranges = graph - .instances - .iter() - .map(|instance| match instance { - plan::ReachabilityInstance::Function(instance) => { - self.function_range(instance.location) - } - plan::ReachabilityInstance::Closure(instance) => { - self.function_range(instance.owner) - } - }) - .collect::>(); - let parse_diagnostics = self - .file - .parse(self.db) - .diagnostics() - .iter() - .filter(|diagnostic| { - relevant_ranges.iter().any(|range| ranges_intersect(*range, diagnostic.range())) - }) - .cloned() - .collect::>(); - self.diagnostics.extend(parse_diagnostics); - - let mut seen_functions = FxHashSet::default(); - for instance in &graph.instances { - let location = match instance { - plan::ReachabilityInstance::Function(instance) => instance.location, - plan::ReachabilityInstance::Closure(instance) => instance.owner, - }; - if !seen_functions.insert(location) { - continue; - } - self.diagnostics - .extend(mitki_analysis::check_function(self.db, location).iter().cloned()); - } - } - - pub(super) fn closure_info( - &self, - closure: &ClosureInstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) -> Result, Diagnostic> { - let nodes = function.node_store(); - let range = self.function_range(closure.owner); - let closure_id = nodes.as_closure(closure.closure).ok_or_else(|| { - Diagnostic::error( - "internal error: expected closure instance to reference a closure expression", - range, - ) - })?; - let (params, body) = nodes.closure_parts(closure_id); - let owner_instance = closure.owner_instance(); - let closure_ty = - self.specialized_expr_ty(&owner_instance, inference, closure.closure).ok_or_else( - || Diagnostic::error("internal error: missing inferred closure type", range), - )?; - let signature = crate::capability::supported_function_signature_or_message( - self.db, - closure_ty, - "Wasm backend does not support this closure type", - ) - .map_err(|message| Diagnostic::error(message, range))?; - - let owned_bindings = self.closure_owned_bindings(function, body, params.iter().collect()); - let captures = self.collect_closure_captures( - &owner_instance, - closure.owner, - function, - inference, - body, - &owned_bindings, - )?; - let env_layout = layout_fields(captures.iter().map(|capture| { - (Some(symbol_bits(nodes.name(capture.binding))), Some(capture.ty.clone())) - })) - .map_or_else( - || AggregateLayout { size: 0, align: 1, kind: AggregateKind::Fields(Vec::new()) }, - |layout| AggregateLayout { - size: layout.size, - align: layout.align, - kind: AggregateKind::Fields(layout.fields), - }, - ); - - let mut captures_with_fields = Vec::with_capacity(captures.len()); - let env_fields = env_layout.fields().unwrap_or(&[]); - for (capture, field) in captures.into_iter().zip(env_fields.iter()) { - captures_with_fields.push(ClosureCapture { - binding: capture.binding, - field: field.clone(), - _marker: std::marker::PhantomData, - }); - } - - Ok(ClosureInfo { - body, - params: params.iter().collect(), - captures: captures_with_fields, - env_layout, - signature, - }) - } - - fn closure_owned_bindings( - &self, - function: &'db Function<'db>, - body: ExprId, - params: Vec, - ) -> FxHashSet { - let nodes = function.node_store(); - let mut owned = params - .into_iter() - .flat_map(|param| { - let (pattern, _) = nodes.param(param); - nodes.pattern_binding_names(pattern) - }) - .collect::>(); - self.collect_owned_bindings_in_expr(function, body, &mut owned); - owned - } - - fn collect_owned_bindings_in_expr( - &self, - function: &'db Function<'db>, - expr: ExprId, - owned: &mut FxHashSet, - ) { - let nodes = function.node_store(); - match nodes.node_kind(expr) { - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(expr).expect("LocalVar node mismatch")); - owned.extend(nodes.pattern_binding_names(var.pattern)); - if var.initializer != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, var.initializer, owned); - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - for stmt in stmts.iter() { - if nodes.node_kind(stmt) == NodeKind::ReturnStmt { - let (value, _) = nodes - .return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, value, owned); - } - } else if let Some(stmt_expr) = stmt_as_expr(nodes, stmt) { - self.collect_owned_bindings_in_expr(function, stmt_expr, owned); - } - } - if tail != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, tail, owned); - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.collect_owned_bindings_in_expr(function, callee, owned); - for arg in args.iter() { - self.collect_owned_bindings_in_expr(function, arg, owned); - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.collect_owned_bindings_in_expr(function, item, owned); - } - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, base, owned); - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.collect_owned_bindings_in_expr(function, binary.lhs, owned); - self.collect_owned_bindings_in_expr(function, binary.rhs, owned); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.collect_owned_bindings_in_expr(function, prefix.expr, owned); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.collect_owned_bindings_in_expr(function, if_expr.cond, owned); - if if_expr.then_branch != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, if_expr.then_branch, owned); - } - if if_expr.else_branch != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, if_expr.else_branch, owned); - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.collect_owned_bindings_in_expr(function, scrutinee, owned); - for arm in arms.iter() { - let (pattern, body) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - owned.extend(nodes.pattern_binding_names(pattern)); - self.collect_owned_bindings_in_expr(function, body, owned); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.collect_owned_bindings_in_expr(function, body, owned); - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.collect_owned_bindings_in_expr(function, items.get(index).unwrap(), owned); - index += 2; - } - } - NodeKind::Closure => {} - _ => {} - } - } - - fn collect_closure_captures( - &self, - owner_instance: &InstanceKey<'db>, - owner_location: FunctionLocation<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - body: ExprId, - owned_bindings: &FxHashSet, - ) -> Result, Diagnostic> { - let mut resolver = Resolver::new(self.db, owner_location); - let mut captures = Vec::new(); - let mut seen = FxHashSet::default(); - self.collect_closure_captures_in_expr( - function, - body, - inference, - owner_instance, - &mut resolver, - owned_bindings, - &mut seen, - &mut captures, - )?; - Ok(captures) - } - - #[allow(clippy::too_many_arguments)] - fn collect_closure_captures_in_expr( - &self, - function: &'db Function<'db>, - expr: ExprId, - inference: &'db mitki_typeck::infer::Inference<'db>, - owner_instance: &InstanceKey<'db>, - resolver: &mut Resolver<'db>, - owned_bindings: &FxHashSet, - seen: &mut FxHashSet, - captures: &mut Vec, - ) -> Result<(), Diagnostic> { - let nodes = function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Name => { - let name = nodes.as_name(expr).expect("Name node mismatch"); - let symbol = nodes.name(name); - let guard = resolver.scopes_for_node(expr); - let resolution = resolver.resolve_value_binding(symbol); - resolver.reset(guard); - - if let Some(BindingId::Local(binding) | BindingId::Param(binding)) = resolution - && !owned_bindings.contains(&binding) - && seen.insert(binding) - { - let ty = inference.type_of_node(binding.into()).ok_or_else(|| { - Diagnostic::error( - "internal error: missing inferred capture type", - self.function_range(owner_instance.location), - ) - })?; - let ty = self.specialize_ty(owner_instance, ty); - let abi = crate::capability::supported_value_abi_or_message( - self.db, - ty, - "Wasm backend does not support capturing this value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(owner_instance.location)) - })?; - captures.push(PendingClosureCapture { binding, ty: abi }); - } - } - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(expr).expect("LocalVar node mismatch")); - if var.initializer != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - var.initializer, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - for stmt in stmts.iter() { - if nodes.node_kind(stmt) == NodeKind::ReturnStmt { - let (value, _) = nodes - .return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - value, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } else if let Some(stmt_expr) = stmt_as_expr(nodes, stmt) { - self.collect_closure_captures_in_expr( - function, - stmt_expr, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - if tail != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - tail, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.collect_closure_captures_in_expr( - function, - callee, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - for arg in args.iter() { - self.collect_closure_captures_in_expr( - function, - arg, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.collect_closure_captures_in_expr( - function, - item, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::Field => { - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - base, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.collect_closure_captures_in_expr( - function, - binary.lhs, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - self.collect_closure_captures_in_expr( - function, - binary.rhs, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.collect_closure_captures_in_expr( - function, - prefix.expr, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.collect_closure_captures_in_expr( - function, - if_expr.cond, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - if if_expr.then_branch != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - if_expr.then_branch, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - if if_expr.else_branch != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - if_expr.else_branch, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.collect_closure_captures_in_expr( - function, - body, - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.collect_closure_captures_in_expr( - function, - items.get(index).unwrap(), - inference, - owner_instance, - resolver, - owned_bindings, - seen, - captures, - )?; - index += 2; - } - } - NodeKind::Closure => {} - _ => {} - } - Ok(()) - } - - pub(super) fn validate_function_signature(&mut self, instance: &ReachableInstance<'db>) { - let location = instance.owner_location(); - let hir_function = location.hir_function(self.db); - let function = hir_function.function(self.db); - let source_map = hir_function.source_map(self.db); - let inference = location.infer(self.db); - let nodes = function.node_store(); - let owner_instance = instance.owner_instance(); - let is_top_level = matches!(instance, ReachableInstance::Function(_)); - let is_typed_wasm_boundary = is_top_level - && matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::ImplicitMainExport | WasmLinkage::Export - ); - let is_raw_import = - is_top_level && matches!(function.linkage(), WasmLinkage::RawImport { .. }); - let is_stage_root = self.compilation_mode().is_stage() - && is_top_level - && self.stage_root() == Some(location); - let crosses_wasm_boundary = is_typed_wasm_boundary || is_raw_import || is_stage_root; - let mut boundary_params_need_runtime_allocs = false; - let transport_profile = self.boundary_transport_profile(); - let params = match instance { - ReachableInstance::Function(_) => function.params().to_vec(), - ReachableInstance::Closure(closure) => { - match self.closure_info(closure, function, inference) { - Ok(info) => info.params, - Err(diagnostic) => { - self.diagnostics.push(diagnostic); - return; - } - } - } - }; - let param_tys = match instance { - ReachableInstance::Function(instance) => { - match self.function_signature_types(instance, function, inference) { - Ok((params, _)) => params, - Err(diagnostic) => { - self.diagnostics.push(diagnostic); - return; - } - } - } - ReachableInstance::Closure(closure) => { - let Some(closure_ty) = - self.specialized_expr_ty(&owner_instance, inference, closure.closure) - else { - self.diagnostics.push(Diagnostic::error( - "internal error: missing inferred closure type", - self.function_range(location), - )); - return; - }; - let TyKind::Function { inputs, .. } = closure_ty.kind(self.db) else { - self.diagnostics.push(Diagnostic::error( - "internal error: closure value did not lower to a function type", - self.function_range(location), - )); - return; - }; - inputs.clone() - } - }; - - for (param, ty) in params.into_iter().zip(param_tys) { - let (pattern, ty_id) = nodes.param(param); - if is_typed_wasm_boundary { - boundary_params_need_runtime_allocs |= transport_profile - .plan_or_message( - self.db, - ty, - "Wasm backend does not support this function signature type", - ) - .is_ok_and(|_| { - crate::capability::supported_value_abi(self.db, ty).is_some_and( - |runtime_abi| { - transport_profile.requires_runtime_allocs(self.db, ty, &runtime_abi) - }, - ) - }); - } - let range = if ty_id != mitki_hir::hir::TyId::ZERO { - source_map - .try_type_syntax(ty_id) - .map_or_else(|| self.function_range(location), |ptr| ptr.range) - } else { - source_map - .try_pat_syntax(pattern) - .map_or_else(|| self.function_range(location), |ptr| ptr.range) - }; - self.validate_signature_ty(ty, range, crosses_wasm_boundary, is_raw_import); - } - - let return_ty = match instance { - ReachableInstance::Function(_) => self.specialize_ty( - &owner_instance, - self.function_return_ty(owner_instance.location, function, inference), - ), - ReachableInstance::Closure(closure) => { - let Some(closure_ty) = - self.specialized_expr_ty(&owner_instance, inference, closure.closure) - else { - self.diagnostics.push(Diagnostic::error( - "internal error: missing inferred closure type", - self.function_range(location), - )); - return; - }; - let TyKind::Function { output, .. } = closure_ty.kind(self.db) else { - self.diagnostics.push(Diagnostic::error( - "internal error: closure value did not lower to a function type", - self.function_range(location), - )); - return; - }; - *output - } - }; - let return_range = self.return_range(location, function, source_map); - self.validate_signature_ty(return_ty, return_range, crosses_wasm_boundary, is_raw_import); - - if crosses_wasm_boundary { - let result_needs_runtime_allocs = transport_profile - .plan_or_message( - self.db, - return_ty, - "Wasm backend does not support this function signature type", - ) - .is_ok_and(|_| { - crate::capability::supported_value_abi(self.db, return_ty).is_some_and( - |runtime_abi| { - transport_profile.requires_runtime_allocs( - self.db, - return_ty, - &runtime_abi, - ) - }, - ) - }); - let _needs_runtime_allocs = - boundary_params_need_runtime_allocs || result_needs_runtime_allocs; - } - } - - pub(in crate::backend) fn return_range( - &self, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - ) -> mitki_errors::TextRange { - if function.ret_type().is_zero() { - self.function_range(location) - } else { - source_map - .try_type_syntax(function.ret_type()) - .map_or_else(|| self.function_range(location), |ptr| ptr.range) - } - } - - pub(super) fn function_signature( - &self, - instance: &InstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) -> Result { - let (inputs, output) = self.function_signature_types(instance, function, inference)?; - let function_ty = Ty::new(self.db, TyKind::Function { inputs, output }); - crate::capability::supported_function_signature_or_message( - self.db, - function_ty, - "Wasm backend does not support this function value type", - ) - .map_err(|message| Diagnostic::error(message, self.function_range(instance.location))) - } - - pub(super) fn function_signature_types( - &self, - instance: &InstanceKey<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - ) -> Result<(Vec>, Ty<'db>), Diagnostic> { - let location = instance.location; - let signature = location.signature(self.db); - let signature_resolver = SignatureTypeResolver::new(self.db, location, signature); - let nodes = signature.nodes(self.db); - let hir_function = location.hir_function(self.db); - let source_map = hir_function.source_map(self.db); - let params = signature - .params(self.db) - .iter() - .enumerate() - .map(|(index, ¶m)| { - let (_, ty) = nodes.param(param); - signature_resolver - .resolve(ty) - .map(|ty| self.specialize_ty(instance, ty)) - .map_err(|error| { - Diagnostic::error( - format!("internal error: {error}"), - self.function_range(location), - ) - }) - .and_then(|ty| { - if function_param_binding_name(function, index) - .is_some_and(|name| source_map.is_mutable_binding(name)) - && crate::capability::supported_value_abi_or_message( - self.db, - ty, - "Wasm backend does not support this value type", - ) - .map_err(|message| { - Diagnostic::error(message, self.function_range(location)) - })? - .is_aggregate() - { - Ok(Ty::new(self.db, TyKind::Pointer { mutable: true, pointee: ty })) - } else { - Ok(ty) - } - }) - }) - .collect::, _>>()?; - - let result = - self.specialize_ty(instance, self.function_return_ty(location, function, inference)); - Ok((params, result)) - } - - fn validate_signature_ty( - &mut self, - ty: Ty<'db>, - range: mitki_errors::TextRange, - crosses_wasm_boundary: bool, - raw_wasm_import: bool, - ) { - let result = if raw_wasm_import { - crate::capability::supported_value_abi_or_message( - self.db, - ty, - "Wasm backend does not support this raw import signature type", - ) - .map(|_abi| ()) - } else if !crosses_wasm_boundary { - crate::capability::supported_value_abi_or_message( - self.db, - ty, - "Wasm backend does not support this reachable function signature type", - ) - .map(|_abi| ()) - } else { - Ok(()) - }; - - if let Err(message) = result { - self.diagnostics.push(Diagnostic::error(message, range)); - } - } - - pub(super) fn instance_for_call( - &self, - caller_instance: &InstanceKey<'db>, - caller_inference: &'db mitki_typeck::infer::Inference<'db>, - target: FunctionLocation<'db>, - call_expr: ExprId, - args: &[ExprId], - range: mitki_errors::TextRange, - ) -> Result, Diagnostic> { - let hir_function = target.hir_function(self.db); - let function = hir_function.function(self.db); - let type_param_count = function.type_params().len(); - if type_param_count == 0 { - return Ok(InstanceKey { location: target, type_args: Vec::new() }); - } - - let pattern = self.signature_pattern(target)?; - let mut type_args = vec![None; type_param_count]; - - for (&pattern, &arg) in pattern.params.iter().zip(args.iter()) { - let actual = self - .specialized_expr_ty(caller_instance, caller_inference, arg) - .ok_or_else(|| { - Diagnostic::error("internal error: missing inferred call argument type", range) - })?; - self.collect_instance_type_args(pattern, actual, &mut type_args, range)?; - } - - let actual_return = self - .specialized_expr_ty(caller_instance, caller_inference, call_expr) - .unwrap_or_else(|| Ty::new(self.db, TyKind::Tuple(Vec::new()))); - self.collect_instance_type_args(pattern.result, actual_return, &mut type_args, range)?; - - let type_args = type_args - .into_iter() - .map(|ty| { - ty.ok_or_else(|| { - Diagnostic::error( - "Wasm backend could not determine concrete type arguments for a generic \ - call", - range, - ) - }) - }) - .collect::, _>>()?; - - Ok(InstanceKey { location: target, type_args }) - } - - pub(super) fn instance_for_function_value( - &self, - caller_instance: &InstanceKey<'db>, - caller_inference: &'db mitki_typeck::infer::Inference<'db>, - target: FunctionLocation<'db>, - value_expr: ExprId, - range: mitki_errors::TextRange, - ) -> Result, Diagnostic> { - let hir_function = target.hir_function(self.db); - let function = hir_function.function(self.db); - let type_param_count = function.type_params().len(); - if type_param_count == 0 { - return Ok(InstanceKey { location: target, type_args: Vec::new() }); - } - - let pattern = self.signature_pattern(target)?; - let actual = - self.specialized_expr_ty(caller_instance, caller_inference, value_expr).ok_or_else( - || Diagnostic::error("internal error: missing inferred function value type", range), - )?; - let pattern_ty = Ty::new( - self.db, - TyKind::Function { inputs: pattern.params.clone(), output: pattern.result }, - ); - let mut type_args = vec![None; type_param_count]; - self.collect_instance_type_args(pattern_ty, actual, &mut type_args, range)?; - let type_args = type_args - .into_iter() - .map(|ty| { - ty.ok_or_else(|| { - Diagnostic::error( - "Wasm backend could not determine concrete type arguments for a generic \ - function value", - range, - ) - }) - }) - .collect::, _>>()?; - - Ok(InstanceKey { location: target, type_args }) - } - - fn signature_pattern( - &self, - location: FunctionLocation<'db>, - ) -> Result, Diagnostic> { - let signature = location.signature(self.db); - let signature_resolver = SignatureTypeResolver::new(self.db, location, signature); - let nodes = signature.nodes(self.db); - let params = signature - .params(self.db) - .iter() - .map(|¶m| { - let (_, ty) = nodes.param(param); - signature_resolver.resolve(ty).map_err(|error| { - Diagnostic::error( - format!("internal error: {error}"), - self.function_range(location), - ) - }) - }) - .collect::, _>>()?; - let result = signature_resolver.resolve(signature.ret_type(self.db)).map_err(|error| { - Diagnostic::error(format!("internal error: {error}"), self.function_range(location)) - })?; - Ok(SignaturePattern { params, result }) - } - - fn collect_instance_type_args( - &self, - pattern: Ty<'db>, - actual: Ty<'db>, - type_args: &mut [Option>], - range: mitki_errors::TextRange, - ) -> Result<(), Diagnostic> { - match pattern.kind(self.db) { - TyKind::Var(id) if (*id as usize) < type_args.len() => { - if let Some(existing) = type_args[*id as usize] { - if existing != actual { - return Err(Diagnostic::error( - "Wasm backend found incompatible concrete types for a generic call", - range, - )); - } - } else { - type_args[*id as usize] = Some(actual); - } - Ok(()) - } - TyKind::Tuple(pattern_items) => { - let TyKind::Tuple(actual_items) = actual.kind(self.db) else { - return Err(Diagnostic::error( - "Wasm backend could not match tuple type arguments for a generic call", - range, - )); - }; - if pattern_items.len() != actual_items.len() { - return Err(Diagnostic::error( - "Wasm backend found a tuple arity mismatch while specializing a generic \ - call", - range, - )); - } - for (&pattern_item, &actual_item) in pattern_items.iter().zip(actual_items.iter()) { - self.collect_instance_type_args(pattern_item, actual_item, type_args, range)?; - } - Ok(()) - } - TyKind::Record(pattern_fields) => { - let TyKind::Record(actual_fields) = actual.kind(self.db) else { - return Err(Diagnostic::error( - "Wasm backend could not match record type arguments for a generic call", - range, - )); - }; - if pattern_fields.len() != actual_fields.len() { - return Err(Diagnostic::error( - "Wasm backend found a record field mismatch while specializing a generic \ - call", - range, - )); - } - for (pattern_name, pattern_ty) in pattern_fields { - let Some((_, actual_ty)) = - actual_fields.iter().find(|(actual_name, _)| actual_name == pattern_name) - else { - return Err(Diagnostic::error( - "Wasm backend found a record field mismatch while specializing a \ - generic call", - range, - )); - }; - self.collect_instance_type_args(*pattern_ty, *actual_ty, type_args, range)?; - } - Ok(()) - } - TyKind::Function { inputs: pattern_inputs, output: pattern_output } => { - let TyKind::Function { inputs: actual_inputs, output: actual_output } = - actual.kind(self.db) - else { - return Err(Diagnostic::error( - "Wasm backend could not match function type arguments for a generic call", - range, - )); - }; - if pattern_inputs.len() != actual_inputs.len() { - return Err(Diagnostic::error( - "Wasm backend found a function arity mismatch while specializing a \ - generic call", - range, - )); - } - for (&pattern_input, &actual_input) in - pattern_inputs.iter().zip(actual_inputs.iter()) - { - self.collect_instance_type_args(pattern_input, actual_input, type_args, range)?; - } - self.collect_instance_type_args(*pattern_output, *actual_output, type_args, range) - } - _ if pattern == actual => Ok(()), - _ => Err(Diagnostic::error( - "Wasm backend could not match concrete type arguments for a generic call", - range, - )), - } - } -} - -struct ObligationVisitor<'a, 'db> { - backend: &'a Backend<'db>, - obligations: &'a mut EmissionObligationScratch<'db>, - owner_instance: InstanceKey<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - inference: &'db mitki_typeck::infer::Inference<'db>, - resolver: Resolver<'db>, -} - -impl<'a, 'db> ObligationVisitor<'a, 'db> { - fn expr(&mut self, expr: ExprId) { - if let Some(ty) = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, expr) - { - self.obligations.register_nominals_in_ty(self.backend.db, ty); - } - - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Name - | NodeKind::True - | NodeKind::False - | NodeKind::Int - | NodeKind::Float - | NodeKind::String - | NodeKind::Char - | NodeKind::BreakExpr - | NodeKind::ContinueExpr - | NodeKind::Error - | NodeKind::Postfix => {} - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.expr(item); - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - for stmt in stmts.iter() { - self.stmt(stmt); - } - if tail != ExprId::ZERO { - self.expr(tail); - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - let mut compiler_intrinsic = None; - if !self.is_enum_variant_constructor_call(expr, callee) - && nodes.node_kind(callee) == NodeKind::Name - { - match self.resolve_name(callee) { - Some(BindingId::CompilerIntrinsic(intrinsic)) => { - compiler_intrinsic = Some(intrinsic); - let args = args.iter().collect::>(); - self.collect_compiler_intrinsic_call(intrinsic, args.as_slice()); - } - Some(BindingId::RuntimeFunction(function)) => { - if !self.backend.is_stage_mode() { - self.obligations.used_runtime_functions.insert(function); - } - } - Some(BindingId::Function(_)) => {} - Some(BindingId::Local(_)) - | Some(BindingId::Param(_)) - | Some(BindingId::Struct(_)) - | Some(BindingId::Enum(_)) - | Some(BindingId::EnumVariant(_)) - | Some(BindingId::BuiltinType(_)) - | None => self.expr(callee), - } - } else if !self.is_enum_variant_constructor_call(expr, callee) { - self.expr(callee); - } - - if compiler_intrinsic.is_none() { - for arg in args.iter() { - self.expr(arg); - } - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.expr(binary.lhs); - self.expr(binary.rhs); - self.collect_binary_helpers(binary.lhs, binary.op, binary.rhs); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.expr(prefix.expr); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.expr(if_expr.cond); - if if_expr.then_branch != ExprId::ZERO { - self.expr(if_expr.then_branch); - } - if if_expr.else_branch != ExprId::ZERO { - self.expr(if_expr.else_branch); - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.expr(scrutinee); - for arm in arms.iter() { - let (_, body) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - self.expr(body); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - self.expr(body); - } - } - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(expr).expect("LocalVar node mismatch")); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(expr).expect("Array node mismatch")); - for item in array.iter() { - self.expr(item); - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - self.expr(value); - self.expr(len); - } - NodeKind::Field => { - if self.is_enum_variant_ref(expr) { - return; - } - - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.expr(base); - } - } - NodeKind::Closure => { - let closure = ClosureInstanceKey { - owner: self.location, - type_args: self.owner_instance.type_args.clone(), - closure: expr, - }; - if let Ok(info) = self.backend.closure_info(&closure, self.function, self.inference) - && info.env_layout.size > 0 - { - self.obligations.used_runtime_functions.insert(RuntimeFunction::Alloc); - self.obligations.used_runtime_functions.insert(RuntimeFunction::Dealloc); - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.expr(items.get(index).unwrap()); - index += 2; - } - } - _ => {} - } - } - - fn stmt(&mut self, stmt: StmtId) { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar node mismatch")); - if var.initializer != ExprId::ZERO { - self.expr(var.initializer); - } - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.expr(value); - } - } - _ => { - if let Some(expr) = stmt_as_expr(nodes, stmt) { - self.expr(expr); - } - } - } - } - - fn collect_binary_helpers(&mut self, lhs: ExprId, op: ExprId, rhs: ExprId) { - let nodes = self.function.node_store(); - let op_sym = nodes.name(nodes.as_name(op).expect("op should be Name")); - let op_text = op_sym.text(self.backend.db); - let lhs_ty = self.expr_abi(lhs); - let rhs_ty = self.expr_abi(rhs); - - if !matches!(op_text, "==" | "!=") { - return; - } - - match (lhs_ty.as_ref(), rhs_ty.as_ref()) { - ( - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::String))), - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::String))), - ) => { - self.obligations.used_helpers.insert(HelperFunction::MemoryEq); - self.obligations.used_helpers.insert(HelperFunction::StringEq); - } - (Some(AbiTy::Aggregate(lhs)), Some(AbiTy::Aggregate(rhs))) if lhs == rhs => { - self.obligations.used_helpers.insert(HelperFunction::MemoryEq); - } - _ => {} - } - } - - fn collect_compiler_intrinsic_call(&mut self, intrinsic: CompilerIntrinsic, args: &[ExprId]) { - if intrinsic == CompilerIntrinsic::Comptime { - if let Some(arg) = args.first().copied() { - self.expr(arg); - } - return; - } - - if intrinsic.is_reflection() { - if self.backend.is_stage_mode() - && let Some(stage_intrinsic) = StageIntrinsic::from_compiler_intrinsic(intrinsic) - { - self.obligations.used_stage_intrinsics.insert(stage_intrinsic); - } - if args.len() > 1 { - self.expr(args[1]); - } - return; - } - - if self.backend.is_stage_mode() { - return; - } - - if intrinsic == CompilerIntrinsic::StrFromUtf8Unchecked { - self.obligations.used_runtime_functions.insert(RuntimeFunction::Alloc); - } - - for &arg in args { - self.expr(arg); - } - } - - fn resolve_name(&mut self, expr: ExprId) -> Option> { - let nodes = self.function.node_store(); - let name = nodes.as_name(expr)?; - let symbol = nodes.name(name); - let guard = self.resolver.scopes_for_node(expr); - let resolution = self.resolver.resolve_value_binding(symbol); - self.resolver.reset(guard); - resolution - } - - fn resolve_name_ty(&mut self, expr: ExprId) -> Option> { - let nodes = self.function.node_store(); - let name = nodes.as_name(expr)?; - let symbol = nodes.name(name); - let guard = self.resolver.scopes_for_node(expr); - let ty = self - .resolver - .resolve_type_binding(symbol) - .and_then(|binding| self.resolver.ty_for_binding(binding)); - self.resolver.reset(guard); - ty - } - - fn expr_abi(&mut self, expr: ExprId) -> Option { - self.lowerable_expr_ty(expr) - .and_then(|ty| crate::capability::supported_value_abi(self.backend.db, ty)) - } - - fn lowerable_expr_ty(&mut self, expr: ExprId) -> Option> { - let ty = self.backend.specialized_expr_ty(&self.owner_instance, self.inference, expr)?; - Some(self.concrete_expr_member_ty(expr, ty).unwrap_or(ty)) - } - - fn concrete_expr_member_ty(&mut self, expr: ExprId, ty: Ty<'db>) -> Option> { - let TyKind::Union(members) = ty.kind(self.backend.db) else { - return None; - }; - - let mut compatible = Vec::new(); - for member in members { - if self.expr_matches_ty(expr, *member) { - compatible.push(*member); - } - } - match compatible.as_slice() { - [selected] => Some(*selected), - _ => None, - } - } - - fn expr_matches_ty(&mut self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::True | NodeKind::False => matches!(ty.kind(self.backend.db), TyKind::Bool), - NodeKind::Int => matches!(ty.kind(self.backend.db), TyKind::Int | TyKind::ExactInt(_)), - NodeKind::Float => matches!(ty.kind(self.backend.db), TyKind::Float), - NodeKind::String => matches!(ty.kind(self.backend.db), TyKind::String), - NodeKind::Char => matches!(ty.kind(self.backend.db), TyKind::Char), - NodeKind::Tuple => { - let items = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - match ty.kind(self.backend.db) { - TyKind::Tuple(member_items) if member_items.len() == items.len() => items - .iter() - .zip(member_items.iter()) - .all(|(item, member_item)| self.expr_matches_ty(item, *member_item)), - _ => false, - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let struct_name = items.iter().next().unwrap_or(ExprId::ZERO); - if struct_name == ExprId::ZERO { - return matches!(ty.kind(self.backend.db), TyKind::Record(_)); - } - - matches!(self.resolve_name_ty(struct_name), Some(struct_ty) if struct_ty == ty) - } - NodeKind::Field => self.expr_is_enum_variant(expr, ty), - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.expr_is_enum_variant(callee, ty) - && matches!(ty.kind(self.backend.db), TyKind::Enum(enum_ty) - if enum_variants(self.backend.db, *enum_ty) - .iter() - .find(|(name, payload)| { - let (_, variant_name_expr) = - nodes.field(nodes.as_field(callee).expect("variant call")); - let variant_name = nodes - .as_name(variant_name_expr) - .expect("variant name should lower to Name"); - *name == nodes.name(variant_name) && payload.len() == args.len() - }) - .is_some()) - } - _ => false, - } - } - - fn expr_is_enum_variant(&mut self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - let TyKind::Enum(enum_ty) = ty.kind(self.backend.db) else { - return false; - }; - if nodes.node_kind(expr) != NodeKind::Field { - return false; - } - - let (base, field_name_expr) = - nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - let variant_name = - nodes.as_name(field_name_expr).expect("variant name should lower to Name"); - let variant_sym = nodes.name(variant_name); - if !enum_variants(self.backend.db, *enum_ty).iter().any(|(name, _)| *name == variant_sym) { - return false; - } - if base == ExprId::ZERO { - return true; - } - - matches!(self.resolve_name_ty(base), Some(base_ty) if base_ty == ty) - } - - fn is_enum_variant_ref(&mut self, expr: ExprId) -> bool { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Field { - return false; - } - - let Some(ty) = self.lowerable_expr_ty(expr) else { - return false; - }; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return false; - } - - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base == ExprId::ZERO { - return true; - } - self.resolve_name_ty(base).is_some() - } - - fn is_enum_variant_constructor_call(&mut self, call_expr: ExprId, callee: ExprId) -> bool { - let nodes = self.function.node_store(); - if nodes.node_kind(callee) != NodeKind::Field { - return false; - } - - let Some(ty) = self.lowerable_expr_ty(call_expr) else { - return false; - }; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return false; - } - - let (base, _) = nodes.field(nodes.as_field(callee).expect("Field node mismatch")); - if base == ExprId::ZERO { - return true; - } - self.resolve_name_ty(base).is_some() - } -} - -struct Validator<'a, 'db> { - backend: &'a mut Backend<'db>, - owner_instance: InstanceKey<'db>, - location: FunctionLocation<'db>, - function: &'db Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - inference: &'db mitki_typeck::infer::Inference<'db>, - resolver: Resolver<'db>, - pending: &'a mut VecDeque>, - edges: &'a mut Vec>, - seen_edges: &'a mut FxHashSet>, - current_node: plan::ReachabilityNode<'db>, - loop_depth: usize, -} - -impl<'a, 'db> Validator<'a, 'db> { - fn expr_diagnostic(&self, expr: ExprId, message: impl Into) -> Diagnostic { - self.backend.diagnostic_at_function(self.location, message.into(), self.node_range(expr)) - } - - fn push_expr_error(&mut self, expr: ExprId, message: impl Into) { - self.backend.diagnostics.push(self.expr_diagnostic(expr, message)); - } - - fn record_edge(&mut self, to: plan::ReachabilityNode<'db>, kind: plan::ReachabilityEdgeKind) { - let edge = plan::ReachabilityEdge { from: self.current_node.clone(), to, kind }; - if self.seen_edges.insert(edge.clone()) { - self.edges.push(edge); - } - } - - fn enqueue_function(&mut self, instance: InstanceKey<'db>, kind: plan::ReachabilityEdgeKind) { - self.record_edge(plan::ReachabilityNode::Function(instance.clone()), kind); - self.pending.push_back(ReachableInstance::Function(instance)); - } - - fn enqueue_closure( - &mut self, - closure: ClosureInstanceKey<'db>, - kind: plan::ReachabilityEdgeKind, - ) { - self.record_edge(plan::ReachabilityNode::Closure(closure.clone()), kind); - self.pending.push_back(ReachableInstance::Closure(closure)); - } - - fn expr(&mut self, expr: ExprId, position: ExprPosition) { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Name => self.name(expr, position), - NodeKind::True | NodeKind::False => {} - NodeKind::Int => { - if let Err(message) = parse_int_literal( - nodes.int(nodes.as_int(expr).expect("Int node mismatch")), - self.backend.db, - ) { - self.push_expr_error(expr, message); - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.expr(item, ExprPosition::Value); - } - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - for stmt in stmts.iter() { - self.stmt(stmt); - } - if tail != ExprId::ZERO { - self.expr(tail, ExprPosition::Value); - } - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - if self.is_enum_variant_constructor_call(expr, callee) { - } else if nodes.node_kind(callee) == NodeKind::Name - && let Some(BindingId::CompilerIntrinsic(intrinsic)) = self.resolve_name(callee) - { - self.validate_compiler_intrinsic_call( - expr, - intrinsic, - args.iter().collect::>().as_slice(), - ); - } else if let Some(method) = self.resolve_method_call(callee) { - let lowered = - method.function.hir_function(self.backend.db).function(self.backend.db); - if self.backend.is_stage_mode() - && matches!( - lowered.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - { - self.push_expr_error( - expr, - "comptime functions cannot call imported Wasm functions", - ); - } - - let mut call_args = Vec::with_capacity(args.len() + 1); - call_args.push(method.receiver); - call_args.extend(args.iter()); - match self.backend.instance_for_call( - &self.owner_instance, - self.inference, - method.function, - expr, - call_args.as_slice(), - self.node_range(expr), - ) { - Ok(instance) => { - self.enqueue_function(instance, plan::ReachabilityEdgeKind::DirectCall); - } - Err(diagnostic) => self.backend.diagnostics.push(diagnostic), - } - self.expr(method.receiver, ExprPosition::Value); - for arg in args.iter() { - self.expr(arg, ExprPosition::Value); - } - } else if nodes.node_kind(callee) == NodeKind::Name { - match self.resolve_name(callee) { - Some(BindingId::RuntimeFunction(_function)) => { - if self.backend.is_stage_mode() { - self.push_expr_error( - expr, - "comptime functions cannot call runtime imports", - ); - } - } - Some(BindingId::Function(function)) => { - let lowered = - function.hir_function(self.backend.db).function(self.backend.db); - if self.backend.is_stage_mode() - && matches!( - lowered.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - { - self.push_expr_error( - expr, - "comptime functions cannot call imported Wasm functions", - ); - } - match self.backend.instance_for_call( - &self.owner_instance, - self.inference, - function, - expr, - args.iter().collect::>().as_slice(), - self.node_range(expr), - ) { - Ok(instance) => { - self.enqueue_function( - instance, - plan::ReachabilityEdgeKind::DirectCall, - ); - } - Err(diagnostic) => self.backend.diagnostics.push(diagnostic), - } - } - Some(BindingId::Local(_) | BindingId::Param(_)) => { - self.validate_indirect_call(callee) - } - Some(BindingId::CompilerIntrinsic(_)) => self.push_expr_error( - expr, - "internal error: Backend intrinsic call validation fell through", - ), - Some(BindingId::Struct(_)) - | Some(BindingId::Enum(_)) - | Some(BindingId::BuiltinType(_)) - | Some(BindingId::EnumVariant(_)) - | None => self.validate_indirect_call(callee), - } - } else { - self.validate_indirect_call(callee); - } - if !matches!(nodes.node_kind(callee), NodeKind::Name) - || !matches!(self.resolve_name(callee), Some(BindingId::CompilerIntrinsic(_))) - { - for arg in args.iter() { - self.expr(arg, ExprPosition::Value); - } - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(expr).expect("Binary node mismatch")); - self.expr(binary.lhs, ExprPosition::Value); - self.expr(binary.rhs, ExprPosition::Value); - self.validate_binary(expr, binary.lhs, binary.op, binary.rhs); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(expr).expect("Prefix node mismatch")); - self.expr(prefix.expr, ExprPosition::Value); - self.validate_prefix(expr, prefix.op, prefix.expr); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - self.require_specific_type( - if_expr.cond, - BackendTy::Bool, - "Wasm backend requires `if` conditions to have type `bool`", - ); - self.expr(if_expr.cond, ExprPosition::Value); - if if_expr.then_branch != ExprId::ZERO { - self.expr(if_expr.then_branch, ExprPosition::Value); - } - if if_expr.else_branch != ExprId::ZERO { - self.expr(if_expr.else_branch, ExprPosition::Value); - } - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(expr).expect("Match node mismatch")); - self.expr(scrutinee, ExprPosition::Value); - for arm in arms.iter() { - let (pattern, body) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm mismatch")); - for name in nodes.pattern_binding_names(pattern) { - self.require_backend_type( - name.into(), - "Wasm backend does not support this pattern binding type", - ); - } - self.expr(body, ExprPosition::Value); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(expr).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - self.loop_depth += 1; - self.expr(body, ExprPosition::Value); - self.loop_depth -= 1; - } - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - self.expr(body, ExprPosition::Value); - } - } - NodeKind::BreakExpr | NodeKind::ContinueExpr => { - if self.loop_depth == 0 { - self.internal_error( - expr, - "internal error: loop control reached Wasm validation outside a loop", - ); - } - } - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(expr).expect("LocalVar node mismatch")); - for name in nodes.pattern_binding_names(var.pattern) { - self.require_backend_type( - name.into(), - "Wasm backend does not support this local binding type", - ); - } - if var.initializer != ExprId::ZERO { - self.expr(var.initializer, ExprPosition::Value); - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(expr).expect("Array node mismatch")); - for item in array.iter() { - self.expr(item, ExprPosition::Value); - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(expr).expect("ArrayRepeat mismatch")); - self.require_specific_type( - len, - BackendTy::Int, - "Wasm backend requires array repeat lengths to have type `int`", - ); - self.expr(value, ExprPosition::Value); - self.expr(len, ExprPosition::Value); - } - NodeKind::Float => { - let literal = nodes.float(nodes.as_float(expr).expect("Float node mismatch")); - if let Err(message) = parse_float_literal(literal, self.backend.db) { - self.push_expr_error(expr, message); - } - } - NodeKind::String => { - let literal = nodes.string(nodes.as_string(expr).expect("String node mismatch")); - if let Err(message) = decode_string_literal(literal, self.backend.db) { - self.push_expr_error(expr, message); - } - } - NodeKind::Char => { - let literal = nodes.char(nodes.as_char(expr).expect("Char node mismatch")); - if let Err(message) = decode_char_literal(literal, self.backend.db) { - self.push_expr_error(expr, message); - } - } - NodeKind::Field => { - if self.is_enum_variant_ref(expr) { - return; - } - - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base != ExprId::ZERO { - self.expr(base, ExprPosition::Value); - } - } - NodeKind::Postfix => self - .internal_error(expr, "internal error: postfix operator reached Wasm validation"), - NodeKind::Closure => { - let closure = ClosureInstanceKey { - owner: self.location, - type_args: self.owner_instance.type_args.clone(), - closure: expr, - }; - match self.backend.closure_info(&closure, self.function, self.inference) { - Ok(_info) => { - self.enqueue_closure(closure, plan::ReachabilityEdgeKind::ClosureLiteral) - } - Err(diagnostic) => self.backend.diagnostics.push(diagnostic), - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - self.expr(items.get(index).unwrap(), ExprPosition::Value); - index += 2; - } - } - NodeKind::Error => self - .internal_error(expr, "internal error: invalid expression reached Wasm validation"), - kind => self.internal_error( - expr, - format!("internal error: unsupported `{kind:?}` node reached Wasm validation"), - ), - } - } - - fn stmt(&mut self, stmt: StmtId) { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::LocalVar => { - let var = - nodes.local_var(nodes.as_local_var(stmt).expect("LocalVar node mismatch")); - for name in nodes.pattern_binding_names(var.pattern) { - self.require_backend_type( - name.into(), - "Wasm backend does not support this local binding type", - ); - } - if var.initializer != ExprId::ZERO { - self.expr(var.initializer, ExprPosition::Value); - } - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - self.expr(value, ExprPosition::Value); - } - } - _ => { - if let Some(expr) = stmt_as_expr(nodes, stmt) { - self.expr(expr, ExprPosition::Value); - } - } - } - } - - fn name(&mut self, expr: ExprId, _position: ExprPosition) { - let Some(resolution) = self.resolve_name(expr) else { - return; - }; - - match resolution { - BindingId::Local(_) | BindingId::Param(_) => { - self.require_backend_type(expr, "Wasm backend does not support this value type"); - } - BindingId::Function(function) => { - match self.backend.instance_for_function_value( - &self.owner_instance, - self.inference, - function, - expr, - self.node_range(expr), - ) { - Ok(instance) => { - self.enqueue_function(instance, plan::ReachabilityEdgeKind::FunctionValue); - } - Err(diagnostic) => self.backend.diagnostics.push(diagnostic), - } - } - BindingId::RuntimeFunction(_) => self.push_expr_error( - expr, - "Wasm backend does not support using runtime functions as values", - ), - BindingId::CompilerIntrinsic(intrinsic) => self.push_expr_error( - expr, - format!( - "Wasm backend does not support using Backend intrinsic `{}` as a value", - intrinsic.source_name() - ), - ), - BindingId::Struct(_) - | BindingId::Enum(_) - | BindingId::EnumVariant(_) - | BindingId::BuiltinType(_) => { - self.push_expr_error(expr, "Wasm backend does not support using types as values") - } - } - } - - fn validate_binary(&mut self, expr: ExprId, lhs: ExprId, op: ExprId, rhs: ExprId) { - let nodes = self.function.node_store(); - let op_sym = nodes.name(nodes.as_name(op).expect("op should be Name")); - let op_text = op_sym.text(self.backend.db); - let lhs_ty = self.expr_abi(lhs); - let rhs_ty = self.expr_abi(rhs); - - let ok = match op_text { - "+" | "-" | "*" | "/" => { - (matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int)))) - || (matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Float))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Float)))) - } - "%" => { - matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - } - "<" | ">" | "<=" | ">=" => { - matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - || (matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Float))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Float)))) - || (matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Char))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Char)))) - } - "==" | "!=" => match (lhs_ty.as_ref(), rhs_ty.as_ref()) { - (Some(AbiTy::Scalar(BackendTy::Int)), Some(AbiTy::Scalar(BackendTy::Int))) - | (Some(AbiTy::Scalar(BackendTy::Bool)), Some(AbiTy::Scalar(BackendTy::Bool))) - | (Some(AbiTy::Scalar(BackendTy::Float)), Some(AbiTy::Scalar(BackendTy::Float))) - | (Some(AbiTy::Scalar(BackendTy::Char)), Some(AbiTy::Scalar(BackendTy::Char))) - | (Some(AbiTy::Scalar(BackendTy::Unit)), Some(AbiTy::Scalar(BackendTy::Unit))) => { - true - } - ( - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::String))), - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::String))), - ) => true, - ( - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(lhs_bits)))), - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(rhs_bits)))), - ) if lhs_bits == rhs_bits => true, - ( - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Array(lhs_bits)))), - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Array(rhs_bits)))), - ) if lhs_bits == rhs_bits => true, - (Some(AbiTy::Aggregate(lhs)), Some(AbiTy::Aggregate(rhs))) if lhs == rhs => true, - _ => false, - }, - "&&" | "||" => { - matches!(lhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Bool))) - && matches!(rhs_ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Bool))) - } - _ => false, - }; - - if !ok { - self.push_expr_error( - expr, - format!("Wasm backend does not support `{op_text}` for this expression"), - ); - } - } - - fn validate_prefix(&mut self, expr: ExprId, op: ExprId, inner: ExprId) { - let nodes = self.function.node_store(); - let op_sym = nodes.name(nodes.as_name(op).expect("op should be Name")); - let op_text = op_sym.text(self.backend.db); - let ty = self.expr_abi(inner); - let ok = match op_text { - "!" => matches!(ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Bool))), - "-" => { - matches!(ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Int))) - || matches!(ty.as_ref(), Some(AbiTy::Scalar(BackendTy::Float))) - } - _ => false, - }; - - if !ok { - self.push_expr_error( - expr, - format!("Wasm backend does not support unary `{op_text}` for this expression"), - ); - } - } - - fn resolve_name(&mut self, expr: ExprId) -> Option> { - let nodes = self.function.node_store(); - let name = nodes.as_name(expr)?; - let symbol = nodes.name(name); - let guard = self.resolver.scopes_for_node(expr); - let resolution = self.resolver.resolve_value_binding(symbol); - self.resolver.reset(guard); - resolution - } - - fn resolve_name_ty(&mut self, expr: ExprId) -> Option> { - let nodes = self.function.node_store(); - let name = nodes.as_name(expr)?; - let symbol = nodes.name(name); - let guard = self.resolver.scopes_for_node(expr); - let ty = self - .resolver - .resolve_type_binding(symbol) - .and_then(|binding| self.resolver.ty_for_binding(binding)); - self.resolver.reset(guard); - ty - } - - fn resolve_method_call(&mut self, callee: ExprId) -> Option> { - let nodes = self.function.node_store(); - let field = nodes.as_field(callee)?; - let (receiver, field_name_expr) = nodes.field(field); - if receiver == ExprId::ZERO { - return None; - } - - let field_name = nodes.as_name(field_name_expr)?; - let receiver_ty = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, receiver)?; - resolve_method_for_receiver(self.backend.db, receiver_ty, nodes.name(field_name)) - .map(|method| ResolvedMethodCall { receiver, function: method.function }) - } - - fn validate_indirect_call(&mut self, callee: ExprId) { - self.expr(callee, ExprPosition::Value); - let Some(ty) = - self.backend.specialized_expr_ty(&self.owner_instance, self.inference, callee) - else { - return; - }; - if !matches!(ty.kind(self.backend.db), TyKind::Function { .. }) { - self.push_expr_error(callee, "Wasm backend requires callees to have a function type"); - } - } - - fn validate_compiler_intrinsic_call( - &mut self, - expr: ExprId, - intrinsic: CompilerIntrinsic, - args: &[ExprId], - ) { - if intrinsic == CompilerIntrinsic::Comptime { - if let Some(arg) = args.first().copied() { - self.expr(arg, ExprPosition::Value); - } - return; - } - - if intrinsic.is_reflection() { - if !self.backend.is_stage_mode() { - self.push_expr_error( - expr, - format!( - "`{}` is only supported during `comptime` execution", - intrinsic.source_name() - ), - ); - return; - } - if args.len() > 1 { - self.expr(args[1], ExprPosition::Value); - } - return; - } - - if self.backend.is_stage_mode() { - self.push_expr_error( - expr, - format!( - "`{}` is not supported during `comptime` execution", - intrinsic.source_name() - ), - ); - return; - } - - for &arg in args { - self.expr(arg, ExprPosition::Value); - } - - if intrinsic == CompilerIntrinsic::StackAlloc - && let Some(&count_expr) = args.first() - { - let nodes = self.function.node_store(); - if nodes.node_kind(count_expr) != NodeKind::Int { - self.push_expr_error( - count_expr, - "`stack_alloc` currently requires a constant integer count", - ); - } else if let Err(message) = parse_int_literal( - nodes.int(nodes.as_int(count_expr).expect("Int node mismatch")), - self.backend.db, - ) { - self.push_expr_error(count_expr, message); - } - } - } - - fn expr_abi(&mut self, expr: ExprId) -> Option { - self.lowerable_expr_ty(expr) - .and_then(|ty| crate::capability::supported_value_abi(self.backend.db, ty)) - } - - fn require_backend_type(&mut self, expr: ExprId, message: &str) { - let Some(ty) = self.lowerable_expr_ty(expr) else { - return; - }; - if let Err(message) = - crate::capability::supported_value_abi_or_message(self.backend.db, ty, message) - { - self.push_expr_error(expr, message); - } - } - - fn require_specific_type(&mut self, expr: ExprId, expected: BackendTy, message: &str) { - if !matches!(self.expr_abi(expr).as_ref(), Some(AbiTy::Scalar(actual)) if *actual == expected) - { - self.push_expr_error(expr, message); - } - } - - fn lowerable_expr_ty(&mut self, expr: ExprId) -> Option> { - let ty = self.backend.specialized_expr_ty(&self.owner_instance, self.inference, expr)?; - Some(self.concrete_expr_member_ty(expr, ty).unwrap_or(ty)) - } - - fn concrete_expr_member_ty(&mut self, expr: ExprId, ty: Ty<'db>) -> Option> { - let TyKind::Union(members) = ty.kind(self.backend.db) else { - return None; - }; - - let mut compatible = Vec::new(); - for member in members { - if self.expr_matches_ty(expr, *member) { - compatible.push(*member); - } - } - match compatible.as_slice() { - [selected] => Some(*selected), - _ => None, - } - } - - fn expr_matches_ty(&mut self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::True | NodeKind::False => matches!(ty.kind(self.backend.db), TyKind::Bool), - NodeKind::Int => matches!(ty.kind(self.backend.db), TyKind::Int | TyKind::ExactInt(_)), - NodeKind::Float => matches!(ty.kind(self.backend.db), TyKind::Float), - NodeKind::String => matches!(ty.kind(self.backend.db), TyKind::String), - NodeKind::Char => matches!(ty.kind(self.backend.db), TyKind::Char), - NodeKind::Tuple => { - let items = nodes.tuple(nodes.as_tuple(expr).expect("Tuple node mismatch")); - match ty.kind(self.backend.db) { - TyKind::Tuple(member_items) if member_items.len() == items.len() => items - .iter() - .zip(member_items.iter()) - .all(|(item, member_item)| self.expr_matches_ty(item, *member_item)), - _ => false, - } - } - NodeKind::StructExpr => { - let items = - nodes.struct_expr(nodes.as_struct_expr(expr).expect("StructExpr mismatch")); - let struct_name = items.iter().next().unwrap_or(ExprId::ZERO); - if struct_name == ExprId::ZERO { - return matches!(ty.kind(self.backend.db), TyKind::Record(_)); - } - - matches!(self.resolve_name_ty(struct_name), Some(struct_ty) if struct_ty == ty) - } - NodeKind::Field => self.expr_is_enum_variant(expr, ty), - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(expr).expect("Call node mismatch")); - self.expr_is_enum_variant(callee, ty) - && matches!(ty.kind(self.backend.db), TyKind::Enum(enum_ty) - if enum_variants(self.backend.db, *enum_ty) - .iter() - .find(|(name, payload)| { - let (_, variant_name_expr) = - nodes.field(nodes.as_field(callee).expect("variant call")); - let variant_name = nodes - .as_name(variant_name_expr) - .expect("variant name should lower to Name"); - *name == nodes.name(variant_name) && payload.len() == args.len() - }) - .is_some()) - } - _ => false, - } - } - - fn expr_is_enum_variant(&mut self, expr: ExprId, ty: Ty<'db>) -> bool { - let nodes = self.function.node_store(); - let TyKind::Enum(enum_ty) = ty.kind(self.backend.db) else { - return false; - }; - if nodes.node_kind(expr) != NodeKind::Field { - return false; - } - - let (base, field_name_expr) = - nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - let variant_name = - nodes.as_name(field_name_expr).expect("variant name should lower to Name"); - let variant_sym = nodes.name(variant_name); - if !enum_variants(self.backend.db, *enum_ty).iter().any(|(name, _)| *name == variant_sym) { - return false; - } - if base == ExprId::ZERO { - return true; - } - - matches!(self.resolve_name_ty(base), Some(base_ty) if base_ty == ty) - } - - fn is_enum_variant_ref(&mut self, expr: ExprId) -> bool { - let nodes = self.function.node_store(); - if nodes.node_kind(expr) != NodeKind::Field { - return false; - } - - let Some(ty) = self.lowerable_expr_ty(expr) else { - return false; - }; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return false; - } - - let (base, _) = nodes.field(nodes.as_field(expr).expect("Field node mismatch")); - if base == ExprId::ZERO { - return true; - } - self.resolve_name_ty(base).is_some() - } - - fn is_enum_variant_constructor_call(&mut self, call_expr: ExprId, callee: ExprId) -> bool { - let nodes = self.function.node_store(); - if nodes.node_kind(callee) != NodeKind::Field { - return false; - } - - let Some(ty) = self.lowerable_expr_ty(call_expr) else { - return false; - }; - if !matches!(ty.kind(self.backend.db), TyKind::Enum(_)) { - return false; - } - - let (base, _) = nodes.field(nodes.as_field(callee).expect("Field node mismatch")); - if base == ExprId::ZERO { - return true; - } - self.resolve_name_ty(base).is_some() - } - - fn internal_error(&mut self, expr: ExprId, message: impl Into) { - self.push_expr_error(expr, message); - } - - fn node_range(&self, expr: ExprId) -> mitki_errors::TextRange { - self.source_map - .try_node_syntax(expr) - .map_or_else(|| self.backend.function_range(self.location), |ptr| ptr.range) - } -} - -fn function_param_binding_name<'db>(function: &Function<'db>, index: usize) -> Option { - let ¶m = function.params().get(index)?; - let (pattern, _) = function.node_store().param(param); - let binding = function.node_store().as_pat_binding(pattern)?; - let (name, _) = function.node_store().pat_binding(binding); - Some(name) -} diff --git a/crates/mitki-backend-wasm/src/reachability_graph.rs b/crates/mitki-backend-wasm/src/reachability_graph.rs deleted file mode 100644 index 63d2243..0000000 --- a/crates/mitki-backend-wasm/src/reachability_graph.rs +++ /dev/null @@ -1,381 +0,0 @@ -use std::collections::VecDeque; - -use mitki_inputs::PackageId; -use mitki_lower::HasPackageDecls as _; -use mitki_span::IntoSymbol as _; -use rustc_hash::FxHashSet; - -use super::plan::{ - ReachabilityEdge, ReachabilityEdgeKind, ReachabilityGraph, ReachabilityInstance, - ReachabilityNode, ReachabilityRoot, -}; -use super::reachability::walk_reachable_instance; -use super::*; - -pub(in crate::backend) struct ReachabilityBuilder<'a, 'db> { - backend: &'a mut Backend<'db>, - roots: Vec>, - exports: Vec>, - pending: VecDeque>, - reachable: Vec>, - seen: FxHashSet>, - edges: Vec>, - seen_edges: FxHashSet>, -} - -impl<'a, 'db> ReachabilityBuilder<'a, 'db> { - pub(in crate::backend) fn new(backend: &'a mut Backend<'db>) -> Self { - Self { - backend, - roots: Vec::new(), - exports: Vec::new(), - pending: VecDeque::new(), - reachable: Vec::new(), - seen: FxHashSet::default(), - edges: Vec::new(), - seen_edges: FxHashSet::default(), - } - } - - pub(in crate::backend) fn build(mut self) -> ReachabilityGraph<'db> { - self.roots = self.collect_roots(); - let root_edges = self - .roots - .iter() - .enumerate() - .map(|(index, root)| { - (ReachabilityNode::Root(index), instance_node(root), ReachabilityEdgeKind::Root) - }) - .collect::>(); - for (from, to, kind) in root_edges { - self.record_edge(from, to, kind); - } - self.pending.extend(self.roots.iter().cloned()); - - while let Some(instance) = self.pending.pop_front() { - self.visit_instance(&instance); - } - - self.shadow_reachability_graph() - } - - fn visit_instance(&mut self, instance: &ReachableInstance<'db>) { - if !self.seen.insert(instance.clone()) { - return; - } - - self.reachable.push(instance.clone()); - self.backend.validate_function_signature(instance); - walk_reachable_instance( - &mut *self.backend, - instance, - &mut self.pending, - &mut self.edges, - &mut self.seen_edges, - ); - } - - fn record_edge( - &mut self, - from: ReachabilityNode<'db>, - to: ReachabilityNode<'db>, - kind: ReachabilityEdgeKind, - ) { - let edge = ReachabilityEdge { from, to, kind }; - if self.seen_edges.insert(edge.clone()) { - self.edges.push(edge); - } - } - - fn shadow_reachability_graph(&self) -> ReachabilityGraph<'db> { - let roots = self - .exports - .iter() - .map(|export| ReachabilityRoot { - logical_name: export.name.text(self.backend.db).to_owned(), - instance: export.instance.clone(), - }) - .collect::>(); - let instances = self - .reachable - .iter() - .map(|instance| match instance { - ReachableInstance::Function(instance) => { - ReachabilityInstance::Function(instance.clone()) - } - ReachableInstance::Closure(instance) => { - ReachabilityInstance::Closure(instance.clone()) - } - }) - .collect::>(); - let functions = self - .reachable - .iter() - .filter_map(|instance| match instance { - ReachableInstance::Function(instance) => Some(instance.clone()), - ReachableInstance::Closure(_) => None, - }) - .collect::>(); - let closures = self - .reachable - .iter() - .filter_map(|instance| match instance { - ReachableInstance::Function(_) => None, - ReachableInstance::Closure(instance) => Some(instance.clone()), - }) - .collect::>(); - let imports = functions - .iter() - .filter(|instance| { - let function = - instance.location.hir_function(self.backend.db).function(self.backend.db); - matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - }) - .cloned() - .collect::>(); - let exports = self.exports.iter().map(|export| export.instance.clone()).collect::>(); - let types = collect_reachable_types(self.backend, &self.reachable); - let callable_signatures = collect_callable_signatures(self.backend, &functions, &closures); - ReachabilityGraph { - mode: self.backend.compilation_mode(), - target_profile: self.backend.target_profile(), - roots, - instances, - functions, - closures, - imports, - exports, - types, - callable_signatures, - edges: self.edges.clone(), - } - } - - fn collect_roots(&mut self) -> Vec> { - if self.backend.compilation_mode().is_stage() - && let Some(root) = self.backend.stage_root() - { - let instance = InstanceKey { location: root, type_args: Vec::new() }; - self.exports.push(ExportedFunction { - instance: instance.clone(), - name: "__mitki_stage_root".into_symbol(self.backend.db), - }); - return vec![ReachableInstance::Function(instance)]; - } - - let mut roots = Vec::new(); - let mut exported_instances = FxHashSet::default(); - - for declaration in PackageId::new(self.backend.db, self.backend.file) - .package_decls(self.backend.db) - .declarations() - { - match *declaration { - Declaration::Function(function) => { - let hir_function = function.hir_function(self.backend.db); - let lowered = hir_function.function(self.backend.db); - let Some(name) = function - .source(self.backend.db) - .name() - .map(|name| name.as_str().into_symbol(self.backend.db)) - else { - continue; - }; - - match lowered.linkage() { - WasmLinkage::Internal => {} - WasmLinkage::Import { .. } => {} - WasmLinkage::RawImport { .. } => {} - WasmLinkage::ImplicitMainExport | WasmLinkage::Export => { - let instance = - InstanceKey { location: function, type_args: Vec::new() }; - if !exported_instances.insert(instance.clone()) { - continue; - } - self.exports - .push(ExportedFunction { instance: instance.clone(), name }); - roots.push(ReachableInstance::Function(instance)); - } - } - } - Declaration::BoundaryInstance(instance_decl) => { - if instance_decl.kind(self.backend.db) != BoundaryInstanceKind::Export { - continue; - } - let Some(instance) = self - .backend - .boundary_instance_key(BoundaryInstanceKind::Export, &instance_decl) - else { - continue; - }; - if !exported_instances.insert(instance.clone()) { - continue; - } - let Some(name) = instance_decl - .source(self.backend.db) - .name() - .map(|name| name.as_str().into_symbol(self.backend.db)) - else { - continue; - }; - self.exports.push(ExportedFunction { instance: instance.clone(), name }); - roots.push(ReachableInstance::Function(instance)); - } - Declaration::Struct(_) | Declaration::Enum(_) => {} - } - } - - if self.exports.is_empty() { - self.backend.diagnostics.push(Diagnostic::error( - "Wasm backend requires a top-level `main` function or at least one `export fun`", - self.backend.file_range(), - )); - } - - roots - } -} - -fn instance_node<'db>(instance: &ReachableInstance<'db>) -> ReachabilityNode<'db> { - match instance { - ReachableInstance::Function(instance) => ReachabilityNode::Function(instance.clone()), - ReachableInstance::Closure(instance) => ReachabilityNode::Closure(instance.clone()), - } -} - -fn collect_callable_signatures<'db>( - backend: &Backend<'db>, - functions: &[InstanceKey<'db>], - closures: &[ClosureInstanceKey<'db>], -) -> Vec { - let mut seen = FxHashSet::default(); - let mut signatures = Vec::new(); - - for instance in functions { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let Ok(signature) = backend.function_signature(instance, function, inference) else { - continue; - }; - if seen.insert(signature.clone()) { - signatures.push(signature); - } - } - - for closure in closures { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - let Ok(info) = backend.closure_info(closure, function, inference) else { - continue; - }; - if seen.insert(info.signature.clone()) { - signatures.push(info.signature); - } - } - - signatures -} - -fn collect_reachable_types<'db>( - backend: &Backend<'db>, - reachable: &[ReachableInstance<'db>], -) -> Vec> { - let mut seen = FxHashSet::default(); - let mut types = Vec::new(); - - for instance in reachable { - match instance { - ReachableInstance::Function(instance) => { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let Ok((params, result)) = - backend.function_signature_types(instance, function, inference) - else { - continue; - }; - for ty in params { - record_type_recursive(backend.db, ty, &mut seen, &mut types); - } - record_type_recursive(backend.db, result, &mut seen, &mut types); - } - ReachableInstance::Closure(closure) => { - let owner_instance = closure.owner_instance(); - let inference = closure.owner.infer(backend.db); - let Some(closure_ty) = - backend.specialized_expr_ty(&owner_instance, inference, closure.closure) - else { - continue; - }; - let TyKind::Function { inputs, output } = closure_ty.kind(backend.db) else { - continue; - }; - for &ty in inputs { - record_type_recursive(backend.db, ty, &mut seen, &mut types); - } - record_type_recursive(backend.db, *output, &mut seen, &mut types); - } - } - } - - types -} - -fn record_type_recursive<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - seen: &mut FxHashSet>, - types: &mut Vec>, -) { - if !seen.insert(ty) { - return; - } - - types.push(ty); - match ty.kind(db) { - TyKind::Array(item) => record_type_recursive(db, *item, seen, types), - TyKind::Tuple(items) | TyKind::Union(items) | TyKind::Inter(items) => { - for &item in items { - record_type_recursive(db, item, seen, types); - } - } - TyKind::Record(fields) => { - for (_, field_ty) in fields { - record_type_recursive(db, *field_ty, seen, types); - } - } - TyKind::Pointer { pointee, .. } => record_type_recursive(db, *pointee, seen, types), - TyKind::Function { inputs, output } => { - for &input in inputs { - record_type_recursive(db, input, seen, types); - } - record_type_recursive(db, *output, seen, types); - } - TyKind::Rec(_, body) => record_type_recursive(db, *body, seen, types), - TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty) => { - for (_, field_ty) in struct_fields(db, *struct_ty) { - record_type_recursive(db, *field_ty, seen, types); - } - } - TyKind::Enum(enum_ty) => { - for (_, fields) in enum_variants(db, *enum_ty) { - for field_ty in fields { - record_type_recursive(db, *field_ty, seen, types); - } - } - } - TyKind::Bool - | TyKind::Float - | TyKind::Int - | TyKind::ExactInt(_) - | TyKind::String - | TyKind::Char - | TyKind::Unknown - | TyKind::Var(_) => {} - } -} diff --git a/crates/mitki-backend-wasm/src/registry.rs b/crates/mitki-backend-wasm/src/registry.rs deleted file mode 100644 index 1a676ff..0000000 --- a/crates/mitki-backend-wasm/src/registry.rs +++ /dev/null @@ -1,657 +0,0 @@ -use std::collections::BTreeSet; - -use mitki_abi::SigId; -use mitki_abi_lower::BuiltAbiV2; -use mitki_errors::Diagnostic; -use rustc_hash::FxHashMap; - -use super::boundary::BoundaryPlan; -use super::plan::{ - CallableAdapterNeed, EmissionObligations, FunctionInstanceId, FunctionInstancePlan, HelperNeed, - NamePlan, ReachabilityGraph, -}; -use super::*; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct HelperId(pub(in crate::backend) u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct HelperRegistryPlan { - pub(in crate::backend) needs: Vec, - pub(in crate::backend) builtin_helpers: Vec, - pub(in crate::backend) exported_helpers: Vec, - pub(in crate::backend) helper_ids: FxHashMap, -} - -impl HelperRegistryPlan { - pub(in crate::backend) fn helper_export_names(&self) -> Vec { - self.exported_helpers.iter().filter_map(|helper| helper.export_name()).collect() - } - - pub(in crate::backend) fn contains(&self, need: HelperNeed) -> bool { - self.helper_ids.contains_key(&need) - } -} - -pub(in crate::backend) struct HelperRegistry; - -impl HelperRegistry { - pub(in crate::backend) fn build<'db>( - obligations: &EmissionObligations<'db>, - boundary: &BoundaryPlan<'db>, - abi_preview: &BuiltAbiV2, - ) -> HelperRegistryPlan { - let mut helpers = BTreeSet::new(); - for helper in &obligations.helpers { - helpers.insert(HelperNeed::from_helper_function(*helper)); - } - for ty in &obligations.reachable_nominals { - let bits = nominal_ty_bits(*ty); - helpers.insert(HelperNeed::NominalDestroy(bits)); - helpers.insert(HelperNeed::NominalEq(bits)); - } - for ty in &obligations.reachable_arrays { - let bits = array_ty_bits(*ty); - helpers.insert(HelperNeed::ArrayDestroy(bits)); - helpers.insert(HelperNeed::ArrayEq(bits)); - } - if obligations.needs_blob_helpers() { - helpers.insert(HelperNeed::AbiAlloc); - helpers.insert(HelperNeed::AbiBlobRelease); - } - if obligations.needs_handle_helpers() { - helpers.insert(HelperNeed::AbiHandleRetain); - helpers.insert(HelperNeed::AbiHandleRelease); - } - for adapter in &obligations.callable_adapters { - let CallableAdapterNeed::BoundaryInvoke(instance) = adapter else { - continue; - }; - let Some(&metadata_index) = boundary - .import_indices - .get(instance) - .or_else(|| boundary.export_indices.get(instance)) - else { - continue; - }; - if let Some(function) = abi_preview.functions.get(metadata_index) { - helpers.insert(HelperNeed::HandleInvoke(function.signature_id)); - } - } - - let needs = helpers.into_iter().collect::>(); - let helper_ids = needs - .iter() - .enumerate() - .map(|(index, helper)| { - ( - *helper, - HelperId(u32::try_from(index).expect("helper registry ids should fit in u32")), - ) - }) - .collect(); - let exported_helpers = - needs.iter().copied().filter(|helper| helper.export_name().is_some()).collect(); - - HelperRegistryPlan { - needs, - builtin_helpers: HelperFunction::all().into_iter().collect(), - exported_helpers, - helper_ids, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(in crate::backend) struct CallableSigId(pub(in crate::backend) u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct CallableSignaturePlan { - pub(in crate::backend) id: CallableSigId, - pub(in crate::backend) signature: FunctionSignature, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct CallableTrampolinePlan<'db> { - pub(in crate::backend) signature_id: SigId, - pub(in crate::backend) function_id: FunctionInstanceId, - pub(in crate::backend) instance: InstanceKey<'db>, - pub(in crate::backend) metadata_index: usize, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct CallableRegistryPlan<'db> { - pub(in crate::backend) signatures: Vec, - pub(in crate::backend) signature_ids: FxHashMap, - pub(in crate::backend) invoke_trampolines: Vec>, -} - -pub(in crate::backend) struct CallableRegistry; - -impl CallableRegistry { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - reachability: &ReachabilityGraph<'db>, - function_instances: &[FunctionInstancePlan<'db>], - boundary: &BoundaryPlan<'db>, - obligations: &EmissionObligations<'db>, - abi_preview: &BuiltAbiV2, - ) -> Result, Diagnostic> { - let function_ids = function_instances - .iter() - .map(|function| (function.instance.clone(), function.id)) - .collect::>(); - let mut signatures = Vec::new(); - let mut signature_ids = FxHashMap::default(); - - for instance in &reachability.functions { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let signature = backend.function_signature(instance, function, inference)?; - if signature_ids.contains_key(&signature) { - continue; - } - let id = CallableSigId( - u32::try_from(signatures.len()).expect("callable registry ids should fit in u32"), - ); - signature_ids.insert(signature.clone(), id); - signatures.push(CallableSignaturePlan { id, signature }); - } - - for closure in &reachability.closures { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - let info = backend.closure_info(closure, function, inference)?; - if signature_ids.contains_key(&info.signature) { - continue; - } - let id = CallableSigId( - u32::try_from(signatures.len()).expect("callable registry ids should fit in u32"), - ); - signature_ids.insert(info.signature.clone(), id); - signatures.push(CallableSignaturePlan { id, signature: info.signature }); - } - - let mut invoke_trampolines = Vec::new(); - for adapter in &obligations.callable_adapters { - let CallableAdapterNeed::BoundaryInvoke(instance) = adapter else { - continue; - }; - let Some(&metadata_index) = boundary - .import_indices - .get(instance) - .or_else(|| boundary.export_indices.get(instance)) - else { - continue; - }; - invoke_trampolines.push(CallableTrampolinePlan { - signature_id: abi_preview.functions[metadata_index].signature_id, - function_id: *function_ids - .get(instance) - .expect("reachable boundary function should have an assigned id"), - instance: instance.clone(), - metadata_index, - }); - } - - Ok(CallableRegistryPlan { signatures, signature_ids, invoke_trampolines }) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) enum SectionExportTarget { - Func(u32), - Memory(u32), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct SectionExportPlan { - pub(in crate::backend) name: String, - pub(in crate::backend) target: SectionExportTarget, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(in crate::backend) struct SectionPlan<'db> { - pub(in crate::backend) runtime_imports: Vec, - pub(in crate::backend) stage_intrinsics: Vec, - pub(in crate::backend) raw_imports: Vec>, - pub(in crate::backend) builtin_helpers: Vec, - pub(in crate::backend) direct_functions: Vec>, - pub(in crate::backend) function_wrappers: Vec>, - pub(in crate::backend) closure_functions: Vec>, - pub(in crate::backend) closure_destroyers: Vec>, - pub(in crate::backend) export_wrappers: Vec>, - pub(in crate::backend) runtime_type_indices: FxHashMap, - pub(in crate::backend) stage_type_indices: FxHashMap, - pub(in crate::backend) helper_type_indices: FxHashMap, - pub(in crate::backend) direct_type_indices: FxHashMap, u32>, - pub(in crate::backend) external_type_indices: FxHashMap, u32>, - pub(in crate::backend) callable_type_indices: FxHashMap, - pub(in crate::backend) nominal_destroy_type_index: Option, - pub(in crate::backend) nominal_eq_type_index: Option, - pub(in crate::backend) array_destroy_type_index: Option, - pub(in crate::backend) array_eq_type_index: Option, - pub(in crate::backend) closure_destroy_type_index: u32, - pub(in crate::backend) abi_alloc_type_index: Option, - pub(in crate::backend) abi_blob_release_type_index: Option, - pub(in crate::backend) abi_handle_retain_type_index: Option, - pub(in crate::backend) abi_handle_release_type_index: Option, - pub(in crate::backend) invoke_trampoline_type_indices: FxHashMap, - pub(in crate::backend) export_wrapper_type_indices: FxHashMap, u32>, - pub(in crate::backend) runtime_function_indices: FxHashMap, - pub(in crate::backend) stage_function_indices: FxHashMap, - pub(in crate::backend) raw_import_function_indices: FxHashMap, u32>, - pub(in crate::backend) helper_function_indices: FxHashMap, - pub(in crate::backend) nominal_destroyer_indices: FxHashMap, - pub(in crate::backend) nominal_eq_indices: FxHashMap, - pub(in crate::backend) array_destroyer_indices: FxHashMap, - pub(in crate::backend) array_eq_indices: FxHashMap, - pub(in crate::backend) direct_function_indices: FxHashMap, u32>, - pub(in crate::backend) wrapper_function_indices: FxHashMap, u32>, - pub(in crate::backend) closure_function_indices: FxHashMap, u32>, - pub(in crate::backend) closure_destroy_indices: FxHashMap, u32>, - pub(in crate::backend) abi_alloc_function_index: Option, - pub(in crate::backend) abi_blob_release_function_index: Option, - pub(in crate::backend) abi_handle_retain_function_index: Option, - pub(in crate::backend) abi_handle_release_function_index: Option, - pub(in crate::backend) invoke_trampoline_indices: Vec<(SigId, u32)>, - pub(in crate::backend) export_wrapper_indices: FxHashMap, u32>, - pub(in crate::backend) table_slots: FxHashMap, u32>, - pub(in crate::backend) table_elements: Vec, - pub(in crate::backend) closure_destroyer_pairs: Vec<(u32, u32)>, - pub(in crate::backend) exports: Vec, - pub(in crate::backend) memory_index: u32, - pub(in crate::backend) stack_global_index: u32, -} - -pub(in crate::backend) struct SectionAssigner; - -impl SectionAssigner { - pub(in crate::backend) fn build<'db>( - backend: &Backend<'db>, - reachability: &ReachabilityGraph<'db>, - obligations: &EmissionObligations<'db>, - boundary: &BoundaryPlan<'db>, - abi_preview: &BuiltAbiV2, - helpers: &HelperRegistryPlan, - callables: &CallableRegistryPlan<'db>, - ) -> Result, Diagnostic> { - let runtime_imports = obligations.runtime_imports.clone(); - let stage_intrinsics = StageIntrinsic::all() - .into_iter() - .filter(|intrinsic| obligations.stage_intrinsics.contains(intrinsic)) - .collect::>(); - let raw_imports = reachability - .functions - .iter() - .filter(|instance| { - let function = instance.location.hir_function(backend.db).function(backend.db); - matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - }) - .cloned() - .collect::>(); - let builtin_helpers = helpers.builtin_helpers.clone(); - let direct_functions = reachability.functions.clone(); - let function_wrappers = reachability.functions.clone(); - let closure_functions = reachability.closures.clone(); - let closure_infos = reachability - .closures - .iter() - .map(|closure| { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - backend - .closure_info(closure, function, inference) - .map(|info| ReachableClosureInfo { closure: closure.clone(), info }) - }) - .collect::, _>>()?; - let closure_destroyers = closure_infos - .iter() - .filter(|closure| closure.info.env_layout.size != 0) - .map(|closure| closure.closure.clone()) - .collect::>(); - let export_wrappers = - boundary.exports.iter().map(|export| export.instance.clone()).collect::>(); - let callable_lowering = backend.callable_lowering_strategy(); - - let mut runtime_type_indices = FxHashMap::default(); - let mut stage_type_indices = FxHashMap::default(); - let mut helper_type_indices = FxHashMap::default(); - let mut direct_type_indices = FxHashMap::default(); - let mut external_type_indices = FxHashMap::default(); - let mut callable_type_indices = FxHashMap::default(); - let mut invoke_trampoline_type_indices = FxHashMap::default(); - let mut export_wrapper_type_indices = FxHashMap::default(); - let mut next_type_index = 0u32; - - for runtime in &runtime_imports { - runtime_type_indices.insert(*runtime, next_type_index); - next_type_index += 1; - } - for intrinsic in &stage_intrinsics { - stage_type_indices.insert(*intrinsic, next_type_index); - next_type_index += 1; - } - for helper in &builtin_helpers { - helper_type_indices.insert(*helper, next_type_index); - next_type_index += 1; - } - let nominal_destroy_type_index = - (!obligations.reachable_nominals.is_empty()).then_some(next_type_index); - if nominal_destroy_type_index.is_some() { - next_type_index += 1; - } - let nominal_eq_type_index = - (!obligations.reachable_nominals.is_empty()).then_some(next_type_index); - if nominal_eq_type_index.is_some() { - next_type_index += 1; - } - let array_destroy_type_index = - (!obligations.reachable_arrays.is_empty()).then_some(next_type_index); - if array_destroy_type_index.is_some() { - next_type_index += 1; - } - let array_eq_type_index = - (!obligations.reachable_arrays.is_empty()).then_some(next_type_index); - if array_eq_type_index.is_some() { - next_type_index += 1; - } - for instance in &direct_functions { - direct_type_indices.insert(instance.clone(), next_type_index); - next_type_index += 1; - } - for instance in &direct_functions { - external_type_indices.insert(instance.clone(), next_type_index); - next_type_index += 1; - } - for callable in &callables.signatures { - callable_type_indices.insert(callable.signature.clone(), next_type_index); - next_type_index += 1; - } - let closure_destroy_type_index = next_type_index; - next_type_index += 1; - - let abi_alloc_type_index = - helpers.contains(HelperNeed::AbiAlloc).then_some(next_type_index); - if abi_alloc_type_index.is_some() { - next_type_index += 1; - } - let abi_blob_release_type_index = - helpers.contains(HelperNeed::AbiBlobRelease).then_some(next_type_index); - if abi_blob_release_type_index.is_some() { - next_type_index += 1; - } - let abi_handle_retain_type_index = - helpers.contains(HelperNeed::AbiHandleRetain).then_some(next_type_index); - if abi_handle_retain_type_index.is_some() { - next_type_index += 1; - } - let abi_handle_release_type_index = - helpers.contains(HelperNeed::AbiHandleRelease).then_some(next_type_index); - if abi_handle_release_type_index.is_some() { - next_type_index += 1; - } - for trampoline in &callables.invoke_trampolines { - invoke_trampoline_type_indices.insert(trampoline.signature_id, next_type_index); - next_type_index += 1; - } - for instance in &export_wrappers { - export_wrapper_type_indices.insert(instance.clone(), next_type_index); - next_type_index += 1; - } - - let mut runtime_function_indices = FxHashMap::default(); - let mut stage_function_indices = FxHashMap::default(); - let mut raw_import_function_indices = FxHashMap::default(); - let mut helper_function_indices = FxHashMap::default(); - let mut nominal_destroyer_indices = FxHashMap::default(); - let mut nominal_eq_indices = FxHashMap::default(); - let mut array_destroyer_indices = FxHashMap::default(); - let mut array_eq_indices = FxHashMap::default(); - let mut direct_function_indices = FxHashMap::default(); - let mut wrapper_function_indices = FxHashMap::default(); - let mut closure_function_indices = FxHashMap::default(); - let mut closure_destroy_indices = FxHashMap::default(); - let mut export_wrapper_indices = FxHashMap::default(); - let mut next_function_index = 0u32; - - for runtime in &runtime_imports { - runtime_function_indices.insert(*runtime, next_function_index); - next_function_index += 1; - } - for intrinsic in &stage_intrinsics { - stage_function_indices.insert(*intrinsic, next_function_index); - next_function_index += 1; - } - for instance in &raw_imports { - raw_import_function_indices.insert(instance.clone(), next_function_index); - next_function_index += 1; - } - for helper in &builtin_helpers { - helper_function_indices.insert(*helper, next_function_index); - next_function_index += 1; - } - for ty in &obligations.reachable_nominals { - nominal_destroyer_indices.insert(nominal_ty_bits(*ty), next_function_index); - next_function_index += 1; - } - for ty in &obligations.reachable_arrays { - array_destroyer_indices.insert(array_ty_bits(*ty), next_function_index); - next_function_index += 1; - } - for ty in &obligations.reachable_nominals { - nominal_eq_indices.insert(nominal_ty_bits(*ty), next_function_index); - next_function_index += 1; - } - for ty in &obligations.reachable_arrays { - array_eq_indices.insert(array_ty_bits(*ty), next_function_index); - next_function_index += 1; - } - for instance in &direct_functions { - direct_function_indices.insert(instance.clone(), next_function_index); - next_function_index += 1; - } - for instance in &function_wrappers { - wrapper_function_indices.insert(instance.clone(), next_function_index); - next_function_index += 1; - } - for closure in &closure_functions { - closure_function_indices.insert(closure.clone(), next_function_index); - next_function_index += 1; - } - for closure in &closure_destroyers { - closure_destroy_indices.insert(closure.clone(), next_function_index); - next_function_index += 1; - } - - let mut table_slots = FxHashMap::default(); - let mut table_elements = Vec::new(); - let closure_destroyer_pairs = if callable_lowering.uses_table_slots() { - table_elements.reserve(direct_functions.len() + closure_functions.len()); - for instance in &function_wrappers { - table_slots.insert( - FunctionValueTarget::Function(instance.clone()), - table_elements.len() as u32, - ); - table_elements.push(wrapper_function_indices[instance]); - } - for closure in &closure_functions { - table_slots.insert( - FunctionValueTarget::Closure(closure.clone()), - table_elements.len() as u32, - ); - table_elements.push(closure_function_indices[closure]); - } - closure_infos - .iter() - .filter_map(|closure| { - closure_destroy_indices.get(&closure.closure).copied().map(|destroy_index| { - ( - table_slots[&FunctionValueTarget::Closure(closure.closure.clone())], - destroy_index, - ) - }) - }) - .collect::>() - } else { - Vec::new() - }; - - let abi_alloc_function_index = - helpers.contains(HelperNeed::AbiAlloc).then_some(next_function_index); - if abi_alloc_function_index.is_some() { - next_function_index += 1; - } - let abi_blob_release_function_index = - helpers.contains(HelperNeed::AbiBlobRelease).then_some(next_function_index); - if abi_blob_release_function_index.is_some() { - next_function_index += 1; - } - let abi_handle_retain_function_index = - helpers.contains(HelperNeed::AbiHandleRetain).then_some(next_function_index); - if abi_handle_retain_function_index.is_some() { - next_function_index += 1; - } - let abi_handle_release_function_index = - helpers.contains(HelperNeed::AbiHandleRelease).then_some(next_function_index); - if abi_handle_release_function_index.is_some() { - next_function_index += 1; - } - let mut invoke_trampoline_indices = Vec::with_capacity(callables.invoke_trampolines.len()); - for trampoline in &callables.invoke_trampolines { - invoke_trampoline_indices.push((trampoline.signature_id, next_function_index)); - next_function_index += 1; - } - for instance in &export_wrappers { - export_wrapper_indices.insert(instance.clone(), next_function_index); - next_function_index += 1; - } - - let mut exports = boundary - .aliases - .iter() - .map(|alias| SectionExportPlan { - name: alias.alias.clone(), - target: SectionExportTarget::Func(export_wrapper_indices[&alias.instance]), - }) - .collect::>(); - if let Some(index) = abi_alloc_function_index { - exports.push(SectionExportPlan { - name: mitki_abi::ABI_V2_ALLOC_EXPORT.to_owned(), - target: SectionExportTarget::Func(index), - }); - } - if let Some(index) = abi_blob_release_function_index { - exports.push(SectionExportPlan { - name: mitki_abi::ABI_V2_BLOB_RELEASE_EXPORT.to_owned(), - target: SectionExportTarget::Func(index), - }); - } - if let Some(index) = abi_handle_retain_function_index { - exports.push(SectionExportPlan { - name: mitki_abi::ABI_V2_HANDLE_RETAIN_EXPORT.to_owned(), - target: SectionExportTarget::Func(index), - }); - } - if let Some(index) = abi_handle_release_function_index { - exports.push(SectionExportPlan { - name: mitki_abi::ABI_V2_HANDLE_RELEASE_EXPORT.to_owned(), - target: SectionExportTarget::Func(index), - }); - } - for (signature_id, function_index) in &invoke_trampoline_indices { - exports.push(SectionExportPlan { - name: mitki_abi::handle_invoke_export_name(*signature_id), - target: SectionExportTarget::Func(*function_index), - }); - } - exports.push(SectionExportPlan { - name: "memory".to_owned(), - target: SectionExportTarget::Memory(0), - }); - - let _ = abi_preview; - - Ok(SectionPlan { - runtime_imports, - stage_intrinsics, - raw_imports, - builtin_helpers, - direct_functions, - function_wrappers, - closure_functions, - closure_destroyers, - export_wrappers, - runtime_type_indices, - stage_type_indices, - helper_type_indices, - direct_type_indices, - external_type_indices, - callable_type_indices, - nominal_destroy_type_index, - nominal_eq_type_index, - array_destroy_type_index, - array_eq_type_index, - closure_destroy_type_index, - abi_alloc_type_index, - abi_blob_release_type_index, - abi_handle_retain_type_index, - abi_handle_release_type_index, - invoke_trampoline_type_indices, - export_wrapper_type_indices, - runtime_function_indices, - stage_function_indices, - raw_import_function_indices, - helper_function_indices, - nominal_destroyer_indices, - nominal_eq_indices, - array_destroyer_indices, - array_eq_indices, - direct_function_indices, - wrapper_function_indices, - closure_function_indices, - closure_destroy_indices, - abi_alloc_function_index, - abi_blob_release_function_index, - abi_handle_retain_function_index, - abi_handle_release_function_index, - invoke_trampoline_indices, - export_wrapper_indices, - table_slots, - table_elements, - closure_destroyer_pairs, - exports, - memory_index: 0, - stack_global_index: 0, - }) - } -} - -pub(in crate::backend) struct NameAssigner; - -impl NameAssigner { - pub(in crate::backend) fn build<'db>( - function_instances: &[FunctionInstancePlan<'db>], - boundary: &BoundaryPlan<'db>, - helpers: &HelperRegistryPlan, - ) -> NamePlan { - NamePlan { - logical_functions: function_instances - .iter() - .map(|function| (function.id, function.logical_name.clone())) - .collect(), - typed_exports: boundary - .aliases - .iter() - .map(|alias| (alias.alias.clone(), alias.function_id)) - .collect(), - helper_exports: helpers.helper_export_names(), - } - } -} diff --git a/crates/mitki-backend-wasm/src/storage.rs b/crates/mitki-backend-wasm/src/storage.rs deleted file mode 100644 index beb0069..0000000 --- a/crates/mitki-backend-wasm/src/storage.rs +++ /dev/null @@ -1,645 +0,0 @@ -use std::ops::Deref; - -use rustc_hash::FxHashMap; -use wasm_encoder::{Function as WasmFunction, Instruction, MemArg, ValType}; - -use super::*; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(super) struct FrameSlotId(pub(super) u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) enum FrameSlotPurpose { - Binding(NameId), - Temp(ExprId), - StackAlloc(ExprId), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct FrameSlot { - pub(super) id: FrameSlotId, - pub(super) purpose: FrameSlotPurpose, - pub(super) size: u32, - pub(super) align: u32, - pub(super) offset: u32, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub(super) struct FramePlan { - pub(super) slots: Vec, - pub(super) size: u32, -} - -impl FramePlan { - pub(super) fn slot(&self, id: FrameSlotId) -> Option<&FrameSlot> { - self.slots.get(id.0 as usize) - } - - pub(super) fn offset(&self, id: FrameSlotId) -> Option { - self.slot(id).map(|slot| slot.offset) - } -} - -#[derive(Clone, Debug, Default)] -pub(super) struct FramePlanBuilder { - slots: Vec, - size: u32, -} - -impl FramePlanBuilder { - pub(super) fn alloc_slot( - &mut self, - purpose: FrameSlotPurpose, - size: u32, - align: u32, - ) -> FrameSlotId { - let align = align.max(1); - let offset = align_to(self.size, align); - let id = FrameSlotId(self.slots.len() as u32); - self.size = offset + size; - self.slots.push(FrameSlot { id, purpose, size, align, offset }); - id - } - - pub(super) fn finish(self) -> FramePlan { - FramePlan { slots: self.slots, size: self.size } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(super) enum ScratchLocalKind { - ScratchI32, - ScratchI32Aux, - ObjectI32, - ScratchF64, - ScratchI64, - FrameBase, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) enum LocalPurpose { - EnvPtrParam, - ResultPtrParam, - RawParam { - ordinal: usize, - }, - UserBinding { - name: NameId, - }, - PatternSource { - expr: ExprId, - }, - NominalTemp { - expr: ExprId, - }, - ArrayRepeatTemp { - expr: ExprId, - }, - AdapterTemp { - ordinal: u32, - }, - #[allow(dead_code)] - ResultJoinI32, - Scratch(ScratchLocalKind), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct PlannedLocal { - pub(super) purpose: LocalPurpose, - pub(super) abi: Option, - pub(super) local_index: Option, - pub(super) value_type: Option, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub(super) struct LocalPlan { - pub(super) params: Vec, - pub(super) user_locals: Vec, - pub(super) spills: Vec, - pub(super) joins: Vec, - pub(super) scratch: Vec, - pub(super) allocation_order: Vec, - wasm_locals: Vec<(u32, ValType)>, -} - -impl LocalPlan { - pub(super) fn wasm_locals(&self) -> &[(u32, ValType)] { - &self.wasm_locals - } - - pub(super) fn env_ptr_local(&self) -> Option { - self.params - .iter() - .find(|local| matches!(local.purpose, LocalPurpose::EnvPtrParam)) - .and_then(|local| local.local_index) - } - - pub(super) fn result_ptr_local(&self) -> Option { - self.params - .iter() - .find(|local| matches!(local.purpose, LocalPurpose::ResultPtrParam)) - .and_then(|local| local.local_index) - } - - pub(super) fn scratch_local(&self, kind: ScratchLocalKind) -> Option { - self.scratch - .iter() - .find(|local| matches!(local.purpose, LocalPurpose::Scratch(found) if found == kind)) - .and_then(|local| local.local_index) - } - - pub(super) fn contains_local_index(&self, index: u32) -> bool { - self.params - .iter() - .chain(self.user_locals.iter()) - .chain(self.spills.iter()) - .chain(self.joins.iter()) - .chain(self.scratch.iter()) - .filter_map(|local| local.local_index) - .any(|local_index| local_index == index) - } - - pub(super) fn next_local_index(&self) -> u32 { - self.params - .iter() - .chain(self.user_locals.iter()) - .chain(self.spills.iter()) - .chain(self.joins.iter()) - .chain(self.scratch.iter()) - .filter_map(|local| local.local_index) - .max() - .map_or(0, |index| index + 1) - } -} - -#[derive(Clone, Debug)] -pub(super) struct LocalPlanBuilder { - next_index: u32, - params: Vec, - user_locals: Vec, - spills: Vec, - joins: Vec, - scratch: Vec, - allocation_order: Vec, - wasm_locals: Vec<(u32, ValType)>, -} - -impl LocalPlanBuilder { - pub(super) fn new(next_index: u32) -> Self { - Self { - next_index, - params: Vec::new(), - user_locals: Vec::new(), - spills: Vec::new(), - joins: Vec::new(), - scratch: Vec::new(), - allocation_order: Vec::new(), - wasm_locals: Vec::new(), - } - } - - pub(super) fn add_param( - &mut self, - purpose: LocalPurpose, - abi: Option, - value_type: Option, - ) -> Option { - let local_index = value_type.map(|_| self.reserve_param_index()); - self.params.push(PlannedLocal { purpose, abi, local_index, value_type }); - local_index - } - - pub(super) fn alloc_user_local( - &mut self, - purpose: LocalPurpose, - abi: Option, - value_type: ValType, - ) -> u32 { - let local_index = self.alloc_local_index(value_type); - let local = PlannedLocal { - purpose, - abi, - local_index: Some(local_index), - value_type: Some(value_type), - }; - self.user_locals.push(local.clone()); - self.allocation_order.push(local); - local_index - } - - pub(super) fn alloc_spill( - &mut self, - purpose: LocalPurpose, - abi: Option, - value_type: ValType, - ) -> u32 { - let local_index = self.alloc_local_index(value_type); - let local = PlannedLocal { - purpose, - abi, - local_index: Some(local_index), - value_type: Some(value_type), - }; - self.spills.push(local.clone()); - self.allocation_order.push(local); - local_index - } - - pub(super) fn alloc_join( - &mut self, - purpose: LocalPurpose, - abi: Option, - value_type: ValType, - ) -> u32 { - let local_index = self.alloc_local_index(value_type); - let local = PlannedLocal { - purpose, - abi, - local_index: Some(local_index), - value_type: Some(value_type), - }; - self.joins.push(local.clone()); - self.allocation_order.push(local); - local_index - } - - pub(super) fn alloc_scratch(&mut self, kind: ScratchLocalKind, value_type: ValType) -> u32 { - let local_index = self.alloc_local_index(value_type); - let local = PlannedLocal { - purpose: LocalPurpose::Scratch(kind), - abi: None, - local_index: Some(local_index), - value_type: Some(value_type), - }; - self.scratch.push(local.clone()); - self.allocation_order.push(local); - local_index - } - - pub(super) fn finish(self) -> LocalPlan { - LocalPlan { - params: self.params, - user_locals: self.user_locals, - spills: self.spills, - joins: self.joins, - scratch: self.scratch, - allocation_order: self.allocation_order, - wasm_locals: self.wasm_locals, - } - } - - fn reserve_param_index(&mut self) -> u32 { - let index = self.next_index; - self.next_index += 1; - index - } - - fn alloc_local_index(&mut self, value_type: ValType) -> u32 { - let index = self.reserve_param_index(); - match self.wasm_locals.last_mut() { - Some((count, existing)) if *existing == value_type => *count += 1, - _ => self.wasm_locals.push((1, value_type)), - } - index - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct LocalSlot { - pub(super) abi: AbiTy, - pub(super) local_index: Option, - pub(super) frame_slot: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) struct TempSlot { - pub(super) frame_slot: FrameSlotId, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum DestBase { - PointerLocal(u32), - FrameSlot(FrameSlotId), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) struct Dest { - pub(super) base: DestBase, - pub(super) offset: u32, -} - -impl Dest { - pub(super) fn pointer_local(local: u32) -> Self { - Self { base: DestBase::PointerLocal(local), offset: 0 } - } - - pub(super) fn frame_slot(frame_slot: FrameSlotId) -> Self { - Self { base: DestBase::FrameSlot(frame_slot), offset: 0 } - } - - pub(super) fn with_offset(self, offset: u32) -> Self { - Self { base: self.base, offset: self.offset + offset } - } -} - -#[derive(Clone, Debug)] -pub(super) struct FunctionLayout { - pub(super) local_plan: LocalPlan, - pub(super) frame_plan: FramePlan, - pub(super) lookups: FunctionLayoutLookups, -} - -#[derive(Clone, Debug)] -pub(super) struct FunctionLayoutLookups { - pub(super) slots: FxHashMap, - pub(super) param_names: Vec, - pub(super) raw_params: Vec, - pub(super) temps: FxHashMap, - pub(super) pattern_scalar_locals: FxHashMap, - pub(super) nominal_locals: FxHashMap, - pub(super) array_repeat_locals: FxHashMap, -} - -impl FunctionLayout { - pub(super) fn new( - local_plan: LocalPlan, - frame_plan: FramePlan, - lookups: FunctionLayoutLookups, - ) -> Self { - Self { local_plan, frame_plan, lookups } - } - - pub(super) fn wasm_locals(&self) -> &[(u32, ValType)] { - self.local_plan.wasm_locals() - } - - pub(super) fn env_ptr_local(&self) -> Option { - self.local_plan.env_ptr_local() - } - - pub(super) fn result_ptr_local(&self) -> Option { - self.local_plan.result_ptr_local() - } - - pub(super) fn scratch_i32_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::ScratchI32) - } - - pub(super) fn scratch_i32_aux_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::ScratchI32Aux) - } - - pub(super) fn object_i32_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::ObjectI32) - } - - pub(super) fn scratch_f64_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::ScratchF64) - } - - pub(super) fn scratch_i64_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::ScratchI64) - } - - pub(super) fn frame_base_local(&self) -> Option { - self.local_plan.scratch_local(ScratchLocalKind::FrameBase) - } - - pub(super) fn frame_size(&self) -> u32 { - self.frame_plan.size - } - - pub(super) fn frame_slot_offset(&self, slot: FrameSlotId) -> Option { - self.frame_plan.offset(slot) - } - - pub(super) fn contains_local_index(&self, index: u32) -> bool { - self.local_plan.contains_local_index(index) - } - - pub(super) fn next_local_index(&self) -> u32 { - self.local_plan.next_local_index() - } - - #[cfg(test)] - pub(super) fn dump_storage(&self) -> String { - use std::fmt::Write as _; - - let mut output = String::new(); - dump_locals(&mut output, "params", &self.local_plan.params); - dump_locals(&mut output, "user_locals", &self.local_plan.user_locals); - dump_locals(&mut output, "spills", &self.local_plan.spills); - dump_locals(&mut output, "joins", &self.local_plan.joins); - dump_locals(&mut output, "scratch", &self.local_plan.scratch); - writeln!(&mut output, "frame.size: {}", self.frame_plan.size).expect("write string"); - writeln!(&mut output, "frame.slots:").expect("write string"); - for slot in &self.frame_plan.slots { - writeln!( - &mut output, - " - {:?} size={} align={} offset={}", - slot.purpose, slot.size, slot.align, slot.offset - ) - .expect("write string"); - } - output - } -} - -impl Deref for FunctionLayout { - type Target = FunctionLayoutLookups; - - fn deref(&self) -> &Self::Target { - &self.lookups - } -} - -#[cfg(test)] -fn dump_locals(output: &mut String, label: &str, locals: &[PlannedLocal]) { - use std::fmt::Write as _; - - writeln!(output, "{label}:").expect("write string"); - for local in locals { - writeln!( - output, - " - {:?} local={:?} type={:?} abi={:?}", - local.purpose, local.local_index, local.value_type, local.abi - ) - .expect("write string"); - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum MemAccessKind { - Byte, - I32, - I64, - F64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) struct MemAccess { - pub(super) offset: u32, - pub(super) align_log2: u32, - pub(super) kind: MemAccessKind, -} - -impl MemAccess { - pub(super) fn byte(offset: u32) -> Self { - Self { offset, align_log2: 0, kind: MemAccessKind::Byte } - } - - pub(super) fn i32(offset: u32, align_log2: u32) -> Self { - Self { offset, align_log2, kind: MemAccessKind::I32 } - } - - pub(super) fn i64(offset: u32) -> Self { - Self { offset, align_log2: 3, kind: MemAccessKind::I64 } - } - - pub(super) fn f64(offset: u32) -> Self { - Self { offset, align_log2: 3, kind: MemAccessKind::F64 } - } - - pub(super) fn scalar(offset: u32, ty: BackendTy) -> Option { - match ty { - BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_) => { - Some(Self::i32(offset, 2)) - } - BackendTy::I64 => Some(Self::i64(offset)), - BackendTy::Float => Some(Self::f64(offset)), - BackendTy::Unit => None, - } - } - - pub(super) fn array_len() -> Self { - Self::i32(ARRAY_LEN_OFFSET, 2) - } - - pub(super) fn array_capacity() -> Self { - Self::i32(ARRAY_CAPACITY_OFFSET, 2) - } - - pub(super) fn arc_ref_count() -> Self { - Self::i32(0, 2) - } - - pub(super) fn arc_type_bits() -> Self { - Self::i32(4, 2) - } - - pub(super) fn enum_tag(offset: u32) -> Self { - Self::i32(offset, 2) - } - - pub(super) fn function_word(offset: u32) -> Self { - Self::i32(offset, 2) - } - - pub(super) fn memarg(self) -> MemArg { - MemArg { offset: self.offset.into(), align: self.align_log2, memory_index: 0 } - } - - pub(super) fn emit_load(self, function: &mut WasmFunction) { - match self.kind { - MemAccessKind::Byte => { - function.instruction(&Instruction::I32Load8U(self.memarg())); - } - MemAccessKind::I32 => { - function.instruction(&Instruction::I32Load(self.memarg())); - } - MemAccessKind::I64 => { - function.instruction(&Instruction::I64Load(self.memarg())); - } - MemAccessKind::F64 => { - function.instruction(&Instruction::F64Load(self.memarg())); - } - } - } - - pub(super) fn emit_store(self, function: &mut WasmFunction) { - match self.kind { - MemAccessKind::Byte => { - function.instruction(&Instruction::I32Store8(self.memarg())); - } - MemAccessKind::I32 => { - function.instruction(&Instruction::I32Store(self.memarg())); - } - MemAccessKind::I64 => { - function.instruction(&Instruction::I64Store(self.memarg())); - } - MemAccessKind::F64 => { - function.instruction(&Instruction::F64Store(self.memarg())); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn local_plan_builder_tracks_categories_and_stable_order() { - let mut builder = LocalPlanBuilder::new(2); - builder.add_param( - LocalPurpose::RawParam { ordinal: 0 }, - Some(AbiTy::Scalar(BackendTy::Int)), - Some(ValType::I32), - ); - builder.alloc_user_local( - LocalPurpose::UserBinding { name: NameId::ZERO }, - Some(AbiTy::Scalar(BackendTy::Bool)), - ValType::I32, - ); - builder.alloc_spill( - LocalPurpose::PatternSource { expr: ExprId::ZERO }, - Some(AbiTy::Scalar(BackendTy::Int)), - ValType::I32, - ); - builder.alloc_join(LocalPurpose::ResultJoinI32, None, ValType::I32); - builder.alloc_scratch(ScratchLocalKind::ScratchI32, ValType::I32); - - let plan = builder.finish(); - assert_eq!(plan.params.len(), 1); - assert_eq!(plan.user_locals.len(), 1); - assert_eq!(plan.spills.len(), 1); - assert_eq!(plan.joins.len(), 1); - assert_eq!(plan.scratch.len(), 1); - assert_eq!( - plan.wasm_locals(), - &[(4, ValType::I32)], - "non-param locals should preserve stable grouped ordering" - ); - } - - #[test] - fn frame_plan_builder_assigns_aligned_non_overlapping_slots() { - let mut builder = FramePlanBuilder::default(); - let first = builder.alloc_slot(FrameSlotPurpose::Temp(ExprId::ZERO), 4, 4); - let second = builder.alloc_slot(FrameSlotPurpose::StackAlloc(ExprId::ZERO), 8, 8); - let third = builder.alloc_slot(FrameSlotPurpose::Binding(NameId::ZERO), 4, 4); - let plan = builder.finish(); - - assert_eq!(plan.offset(first), Some(0)); - assert_eq!(plan.offset(second), Some(8)); - assert_eq!(plan.offset(third), Some(16)); - assert_eq!(plan.size, 20); - } - - #[test] - fn mem_access_derives_scalar_and_runtime_layout_accesses() { - assert_eq!( - MemAccess::scalar(12, BackendTy::Int), - Some(MemAccess { offset: 12, align_log2: 2, kind: MemAccessKind::I32 }) - ); - assert_eq!( - MemAccess::scalar(24, BackendTy::Float), - Some(MemAccess { offset: 24, align_log2: 3, kind: MemAccessKind::F64 }) - ); - assert_eq!(MemAccess::array_len(), MemAccess::i32(ARRAY_LEN_OFFSET, 2)); - assert_eq!(MemAccess::array_capacity(), MemAccess::i32(ARRAY_CAPACITY_OFFSET, 2)); - assert_eq!(MemAccess::arc_ref_count(), MemAccess::i32(0, 2)); - assert_eq!(MemAccess::function_word(4), MemAccess::i32(4, 2)); - assert_eq!(MemAccess::byte(0).kind, MemAccessKind::Byte); - } -} diff --git a/crates/mitki-backend-wasm/src/target.rs b/crates/mitki-backend-wasm/src/target.rs deleted file mode 100644 index 16b5ee4..0000000 --- a/crates/mitki-backend-wasm/src/target.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use mitki_abi::{FunctionSignature as AbiV2FunctionSignature, SemanticTypeGraph, TransportClass}; -use mitki_abi_lower::boundary_transport_class; -use mitki_errors::{Diagnostic, TextRange}; -use mitki_resolve::RuntimeFunction; -use wasm_encoder::{ConstExpr, RefType, ValType}; - -use super::boundary::semantic_type_uses_recursive_group; -use super::{BoundaryTransportPlan, StageIntrinsic}; -#[cfg(test)] -use crate::abi::BackendTy; -use crate::abi::{AbiTy, BackendTy as RuntimeBackendTy, FunctionSignature, backend_ty_value_type}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum PointerWidth { - M32, - #[allow(dead_code)] - M64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum GuestWord { - I32, - I64, -} - -impl GuestWord { - pub(crate) const fn val_type(self) -> ValType { - match self { - Self::I32 => ValType::I32, - Self::I64 => ValType::I64, - } - } - - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::I32 => "i32", - Self::I64 => "i64", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum ResultLoweringMode { - SingleValueOnly, - MultiValue, - SpillToLocals, -} - -impl ResultLoweringMode { - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::SingleValueOnly => "single_value_only", - Self::MultiValue => "multi_value", - Self::SpillToLocals => "spill_to_locals", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum CallableRepresentation { - HandleAndTable, - TypedFuncRef, -} - -impl CallableRepresentation { - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::HandleAndTable => "handle_and_table", - Self::TypedFuncRef => "typed_funcref", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum ReferenceRep { - LinearMemoryManaged, - GcRef, -} - -impl ReferenceRep { - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::LinearMemoryManaged => "linear_memory_managed", - Self::GcRef => "gc_ref", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum FailureLowering { - TrapOnly, - #[allow(dead_code)] - ExplicitErrorCarrier, - Exceptions, -} - -impl FailureLowering { - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::TrapOnly => "trap_only", - Self::ExplicitErrorCarrier => "explicit_error_carrier", - Self::Exceptions => "exceptions", - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum BoundaryTransportModel { - AbiV2Wrappers, -} - -impl BoundaryTransportModel { - #[cfg_attr(not(test), allow(dead_code))] - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::AbiV2Wrappers => "abi_v2_wrappers", - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct PhysicalWasmSignature { - pub(crate) params: Vec, - pub(crate) results: Vec, -} - -impl PhysicalWasmSignature { - pub(crate) fn new(params: Vec, results: Vec) -> Self { - Self { params, results } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct SignatureStrategy { - profile: TargetProfile, -} - -impl SignatureStrategy { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn guest_word(self) -> GuestWord { - match self.profile.pointer_width() { - PointerWidth::M32 => GuestWord::I32, - PointerWidth::M64 => GuestWord::I64, - } - } - - pub(crate) const fn result_pointer_lane(self) -> ValType { - self.guest_word().val_type() - } - - pub(crate) fn direct_signature(self, signature: &FunctionSignature) -> PhysicalWasmSignature { - let mut params = Vec::with_capacity( - signature.params.len() + usize::from(signature.result.is_aggregate()), - ); - if signature.result.is_aggregate() { - params.push(self.result_pointer_lane()); - } - params.extend(signature.params.iter().filter_map(|ty| self.param_lane(ty))); - let results = Self::result_lanes(&signature.result); - PhysicalWasmSignature::new(params, results) - } - - fn param_lane(self, ty: &AbiTy) -> Option { - match ty { - AbiTy::Scalar(ty) => backend_ty_value_type(*ty), - AbiTy::Aggregate(_) => Some(self.result_pointer_lane()), - } - } - - fn result_lanes(ty: &AbiTy) -> Vec { - match ty { - AbiTy::Scalar(result) => backend_ty_value_type(*result).into_iter().collect(), - AbiTy::Aggregate(_) => Vec::new(), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) struct TargetProfile { - pointer_width: PointerWidth, - multi_value: bool, - reference_types: bool, - typed_funcref: bool, - wasm_gc: bool, - memory64: bool, - exceptions: bool, -} - -impl TargetProfile { - pub(crate) const fn wasm_core_v2_m32() -> Self { - Self { - pointer_width: PointerWidth::M32, - multi_value: false, - reference_types: true, - typed_funcref: false, - wasm_gc: false, - memory64: false, - exceptions: false, - } - } - - pub(crate) const fn pointer_width(self) -> PointerWidth { - self.pointer_width - } - - pub(crate) const fn supports_multi_value(self) -> bool { - self.multi_value - } - - pub(crate) const fn uses_reference_types(self) -> bool { - self.reference_types - } - - pub(crate) const fn supports_typed_funcref(self) -> bool { - self.typed_funcref - } - - pub(crate) const fn supports_wasm_gc(self) -> bool { - self.wasm_gc - } - - pub(crate) const fn uses_memory64(self) -> bool { - self.memory64 - } - - pub(crate) const fn supports_exceptions(self) -> bool { - self.exceptions - } - - pub(crate) const fn canonical_name(self) -> &'static str { - match self.pointer_width { - PointerWidth::M32 => "wasm-core-v2/m32", - PointerWidth::M64 => "wasm-core-v2/m64", - } - } - - #[cfg(test)] - pub(crate) const fn with_pointer_width(mut self, pointer_width: PointerWidth) -> Self { - self.pointer_width = pointer_width; - self - } - - #[cfg(test)] - pub(crate) const fn with_memory64(mut self, memory64: bool) -> Self { - self.memory64 = memory64; - self - } - - #[cfg(test)] - pub(crate) const fn with_multi_value(mut self, multi_value: bool) -> Self { - self.multi_value = multi_value; - self - } - - #[cfg(test)] - pub(crate) const fn with_reference_types(mut self, reference_types: bool) -> Self { - self.reference_types = reference_types; - self - } - - #[cfg(test)] - pub(crate) const fn with_typed_funcref(mut self, typed_funcref: bool) -> Self { - self.typed_funcref = typed_funcref; - self - } - - #[cfg(test)] - pub(crate) const fn with_wasm_gc(mut self, wasm_gc: bool) -> Self { - self.wasm_gc = wasm_gc; - self - } - - #[cfg(test)] - pub(crate) const fn with_exceptions(mut self, exceptions: bool) -> Self { - self.exceptions = exceptions; - self - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum CompilationMode { - Runtime, - Stage, -} - -impl CompilationMode { - #[cfg(test)] - pub(crate) const fn dump_name(self) -> &'static str { - match self { - Self::Runtime => "runtime", - Self::Stage => "stage", - } - } - - pub(crate) const fn is_stage(self) -> bool { - matches!(self, Self::Stage) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct ImportProvider { - profile: TargetProfile, -} - -impl ImportProvider { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) fn runtime_import(self, runtime: RuntimeFunction) -> (&'static str, &'static str) { - let _ = self.profile; - (runtime.import_module(), runtime.import_name()) - } - - pub(crate) fn stage_intrinsic_import( - self, - intrinsic: StageIntrinsic, - ) -> (&'static str, &'static str) { - let _ = self.profile; - (StageIntrinsic::module(), intrinsic.name()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct BoundaryTransportProfile { - profile: TargetProfile, -} - -impl BoundaryTransportProfile { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn model(self) -> BoundaryTransportModel { - let _ = self.profile; - BoundaryTransportModel::AbiV2Wrappers - } - - pub(crate) fn boundary_signature_context(self) -> String { - format!( - "the current `{}` backend does not support this Wasm boundary signature", - self.profile.canonical_name() - ) - } - - pub(crate) fn plan_or_message( - self, - db: &dyn salsa::Database, - ty: mitki_hir::ty::Ty<'_>, - context: &str, - ) -> Result { - let _ = self.profile; - crate::capability::supported_value_abi_or_message(db, ty, context)?; - Ok(BoundaryTransportPlan { transport_class: boundary_transport_class(db, ty) }) - } - - pub(crate) fn requires_runtime_allocs( - self, - db: &dyn salsa::Database, - ty: mitki_hir::ty::Ty<'_>, - runtime_abi: &AbiTy, - ) -> bool { - let _ = self.profile; - match boundary_transport_class(db, ty) { - TransportClass::Immediate => { - matches!( - ty.kind(db), - mitki_hir::ty::TyKind::Enum(enum_ty) - if mitki_lower::item::scope::enum_variants(db, *enum_ty) - .iter() - .all(|(_, fields)| fields.is_empty()) - ) && !matches!( - runtime_abi, - AbiTy::Scalar( - RuntimeBackendTy::Unit - | RuntimeBackendTy::Int - | RuntimeBackendTy::Bool - | RuntimeBackendTy::Float - | RuntimeBackendTy::Char - ) - ) - } - TransportClass::CanonicalValue | TransportClass::CapabilityHandle => { - !matches!(runtime_abi, AbiTy::Scalar(RuntimeBackendTy::Unit)) - } - } - } - - pub(crate) fn ensure_supported( - self, - graph: &SemanticTypeGraph, - signature: &AbiV2FunctionSignature, - range: TextRange, - ) -> Result<(), Diagnostic> { - for transport in signature.params.iter().chain(std::iter::once(&signature.result)) { - if matches!(transport.transport_class, TransportClass::CanonicalValue) - && semantic_type_uses_recursive_group(graph, transport.semantic_type)? - { - return Err(Diagnostic::error( - format!( - "the current `{}` backend does not support this boundary shape yet; \ - recursive types are not supported yet", - self.profile.canonical_name() - ), - range, - )); - } - } - Ok(()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct CallableLoweringStrategy { - profile: TargetProfile, -} - -impl CallableLoweringStrategy { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn representation(self) -> CallableRepresentation { - if self.profile.supports_typed_funcref() { - CallableRepresentation::TypedFuncRef - } else { - CallableRepresentation::HandleAndTable - } - } - - pub(crate) const fn uses_table_slots(self) -> bool { - matches!(self.representation(), CallableRepresentation::HandleAndTable) - } - - pub(crate) const fn requires_reference_types(self) -> bool { - self.uses_table_slots() - } - - pub(crate) const fn table_element_type(self) -> RefType { - let _ = self.profile; - RefType::FUNCREF - } - - pub(crate) const fn table64(self) -> bool { - let _ = self; - false - } - - pub(crate) const fn environment_lane( - self, - memory_model: MemoryModelStrategy, - ) -> Option { - match self.representation() { - CallableRepresentation::HandleAndTable => Some(memory_model.word_type()), - CallableRepresentation::TypedFuncRef => None, - } - } - - pub(crate) const fn table_index_lane(self, memory_model: MemoryModelStrategy) -> ValType { - let _ = self.profile; - memory_model.word_type() - } - - pub(crate) const fn handle_lane(self, memory_model: MemoryModelStrategy) -> ValType { - let _ = self.profile; - memory_model.word_type() - } - - pub(crate) fn callable_signature( - self, - signature_strategy: SignatureStrategy, - memory_model: MemoryModelStrategy, - signature: &FunctionSignature, - ) -> PhysicalWasmSignature { - let direct = signature_strategy.direct_signature(signature); - let mut params = Vec::with_capacity( - direct.params.len() + usize::from(self.environment_lane(memory_model).is_some()), - ); - if let Some(env_lane) = self.environment_lane(memory_model) { - params.push(env_lane); - } - params.extend(direct.params); - PhysicalWasmSignature::new(params, direct.results) - } - - pub(crate) fn boundary_invoke_signature( - self, - memory_model: MemoryModelStrategy, - signature: PhysicalWasmSignature, - ) -> PhysicalWasmSignature { - match self.representation() { - CallableRepresentation::HandleAndTable => { - let mut params = Vec::with_capacity(signature.params.len() + 1); - params.push(self.handle_lane(memory_model)); - params.extend(signature.params); - PhysicalWasmSignature::new(params, signature.results) - } - CallableRepresentation::TypedFuncRef => signature, - } - } - - pub(crate) fn handle_retain_signature( - self, - memory_model: MemoryModelStrategy, - ) -> PhysicalWasmSignature { - let lane = self.handle_lane(memory_model); - PhysicalWasmSignature::new(vec![lane], vec![lane]) - } - - pub(crate) fn handle_release_signature( - self, - memory_model: MemoryModelStrategy, - ) -> PhysicalWasmSignature { - PhysicalWasmSignature::new(vec![self.handle_lane(memory_model)], Vec::new()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct MemoryModelStrategy { - profile: TargetProfile, -} - -impl MemoryModelStrategy { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn guest_word(self) -> GuestWord { - match self.profile.pointer_width() { - PointerWidth::M32 => GuestWord::I32, - PointerWidth::M64 => GuestWord::I64, - } - } - - pub(crate) const fn word_type(self) -> ValType { - self.guest_word().val_type() - } - - #[cfg(test)] - pub(crate) const fn pointer_backend_ty(self) -> BackendTy { - match self.profile.pointer_width() { - PointerWidth::M32 => BackendTy::Int, - PointerWidth::M64 => BackendTy::I64, - } - } - - pub(crate) const fn memory64(self) -> bool { - self.profile.uses_memory64() - } - - pub(crate) fn const_expr_from_u32(self, value: u32) -> ConstExpr { - match self.profile.pointer_width() { - PointerWidth::M32 => ConstExpr::i32_const(value as i32), - PointerWidth::M64 => ConstExpr::i64_const(i64::from(value)), - } - } - - pub(crate) const fn alloc_align_type(self) -> ValType { - let _ = self.profile; - ValType::I32 - } - - pub(crate) fn alloc_helper_signature(self) -> PhysicalWasmSignature { - PhysicalWasmSignature::new( - vec![self.word_type(), self.alloc_align_type()], - vec![self.word_type()], - ) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct ControlFlowStrategy { - profile: TargetProfile, -} - -impl ControlFlowStrategy { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn failure_lowering(self) -> FailureLowering { - if self.profile.supports_exceptions() { - FailureLowering::Exceptions - } else { - FailureLowering::TrapOnly - } - } - - pub(crate) fn result_lowering_mode(self, abi: &AbiTy) -> ResultLoweringMode { - match abi { - AbiTy::Scalar(RuntimeBackendTy::Unit) | AbiTy::Aggregate(_) => { - ResultLoweringMode::SingleValueOnly - } - AbiTy::Scalar(_) if self.profile.supports_multi_value() => { - ResultLoweringMode::MultiValue - } - AbiTy::Scalar(_) => ResultLoweringMode::SpillToLocals, - } - } - - pub(crate) const fn default_result_lowering(self) -> ResultLoweringMode { - if self.profile.supports_multi_value() { - ResultLoweringMode::MultiValue - } else { - ResultLoweringMode::SpillToLocals - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct ReferenceRepresentationStrategy { - profile: TargetProfile, -} - -impl ReferenceRepresentationStrategy { - pub(crate) const fn new(profile: TargetProfile) -> Self { - Self { profile } - } - - pub(crate) const fn representation(self) -> ReferenceRep { - if self.profile.supports_wasm_gc() { - ReferenceRep::GcRef - } else { - ReferenceRep::LinearMemoryManaged - } - } - - #[allow(dead_code)] - pub(crate) const fn uses_linear_memory_objects(self) -> bool { - matches!(self.representation(), ReferenceRep::LinearMemoryManaged) - } - - #[allow(dead_code)] - pub(crate) const fn requires_runtime_retain_release(self) -> bool { - self.uses_linear_memory_objects() - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct CapabilityMatrix { - profile: TargetProfile, - guest_word: GuestWord, - result_lowering: ResultLoweringMode, - callable_representation: CallableRepresentation, - boundary_transport: BoundaryTransportModel, - reference_representation: ReferenceRep, - failure_lowering: FailureLowering, -} - -impl CapabilityMatrix { - pub(crate) const fn new( - profile: TargetProfile, - signature: SignatureStrategy, - boundary_transport: BoundaryTransportProfile, - callable: CallableLoweringStrategy, - control_flow: ControlFlowStrategy, - reference_representation: ReferenceRepresentationStrategy, - ) -> Self { - Self { - profile, - guest_word: signature.guest_word(), - result_lowering: control_flow.default_result_lowering(), - callable_representation: callable.representation(), - boundary_transport: boundary_transport.model(), - reference_representation: reference_representation.representation(), - failure_lowering: control_flow.failure_lowering(), - } - } - - pub(crate) const fn guest_word(self) -> GuestWord { - self.guest_word - } - - pub(crate) const fn result_lowering(self) -> ResultLoweringMode { - self.result_lowering - } - - pub(crate) const fn callable_representation(self) -> CallableRepresentation { - self.callable_representation - } - - pub(crate) const fn boundary_transport(self) -> BoundaryTransportModel { - self.boundary_transport - } - - pub(crate) const fn reference_representation(self) -> ReferenceRep { - self.reference_representation - } - - pub(crate) const fn failure_lowering(self) -> FailureLowering { - self.failure_lowering - } - - #[allow(dead_code)] - pub(crate) const fn uses_reference_types(self) -> bool { - self.profile.uses_reference_types() - } - - pub(crate) const fn uses_memory64(self) -> bool { - self.profile.uses_memory64() - } - - pub(crate) const fn supports_multi_value(self) -> bool { - self.profile.supports_multi_value() - } - - pub(crate) const fn supports_typed_funcref(self) -> bool { - self.profile.supports_typed_funcref() - } - - pub(crate) const fn supports_wasm_gc(self) -> bool { - self.profile.supports_wasm_gc() - } - - pub(crate) const fn supports_exceptions(self) -> bool { - self.profile.supports_exceptions() - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct TargetDecisionSnapshot { - pub(crate) guest_word: GuestWord, - pub(crate) result_lowering: ResultLoweringMode, - pub(crate) callable_representation: CallableRepresentation, - pub(crate) boundary_transport: BoundaryTransportModel, - pub(crate) reference_representation: ReferenceRep, - pub(crate) failure_lowering: FailureLowering, -} - -impl TargetDecisionSnapshot { - pub(crate) const fn from_matrix(matrix: CapabilityMatrix) -> Self { - Self { - guest_word: matrix.guest_word(), - result_lowering: matrix.result_lowering(), - callable_representation: matrix.callable_representation(), - boundary_transport: matrix.boundary_transport(), - reference_representation: matrix.reference_representation(), - failure_lowering: matrix.failure_lowering(), - } - } - - #[cfg(test)] - pub(crate) fn dump(&self) -> String { - format!( - "guest_word={} result_lowering={} callable_representation={} boundary_transport={} \ - reference_representation={} failure_lowering={}", - self.guest_word.dump_name(), - self.result_lowering.dump_name(), - self.callable_representation.dump_name(), - self.boundary_transport.dump_name(), - self.reference_representation.dump_name(), - self.failure_lowering.dump_name(), - ) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct TargetPolicies { - profile: TargetProfile, - import_provider: ImportProvider, - boundary_transport: BoundaryTransportProfile, - callable_lowering: CallableLoweringStrategy, - memory_model: MemoryModelStrategy, - signature: SignatureStrategy, - control_flow: ControlFlowStrategy, - reference_representation: ReferenceRepresentationStrategy, - capability_matrix: CapabilityMatrix, -} - -impl TargetPolicies { - pub(crate) const fn for_profile(profile: TargetProfile) -> Self { - let import_provider = ImportProvider::new(profile); - let boundary_transport = BoundaryTransportProfile::new(profile); - let callable_lowering = CallableLoweringStrategy::new(profile); - let memory_model = MemoryModelStrategy::new(profile); - let signature = SignatureStrategy::new(profile); - let control_flow = ControlFlowStrategy::new(profile); - let reference_representation = ReferenceRepresentationStrategy::new(profile); - let capability_matrix = CapabilityMatrix::new( - profile, - signature, - boundary_transport, - callable_lowering, - control_flow, - reference_representation, - ); - Self { - profile, - import_provider, - boundary_transport, - callable_lowering, - memory_model, - signature, - control_flow, - reference_representation, - capability_matrix, - } - } - - pub(crate) const fn profile(self) -> TargetProfile { - self.profile - } - - pub(crate) const fn import_provider(self) -> ImportProvider { - self.import_provider - } - - pub(crate) const fn boundary_transport(self) -> BoundaryTransportProfile { - self.boundary_transport - } - - pub(crate) const fn callable_lowering(self) -> CallableLoweringStrategy { - self.callable_lowering - } - - pub(crate) const fn memory_model(self) -> MemoryModelStrategy { - self.memory_model - } - - pub(crate) const fn signature(self) -> SignatureStrategy { - self.signature - } - - pub(crate) const fn control_flow(self) -> ControlFlowStrategy { - self.control_flow - } - - pub(crate) const fn reference_representation(self) -> ReferenceRepresentationStrategy { - self.reference_representation - } - - pub(crate) const fn capability_matrix(self) -> CapabilityMatrix { - self.capability_matrix - } - - pub(crate) const fn decision_snapshot(self) -> TargetDecisionSnapshot { - TargetDecisionSnapshot::from_matrix(self.capability_matrix) - } - - pub(crate) fn validate_backend_support(self, range: TextRange) -> Result<(), Diagnostic> { - if self.capability_matrix.guest_word() != GuestWord::I32 - || self.capability_matrix.uses_memory64() - { - return Err(Diagnostic::error( - format!( - "the target profile `{}` selected guest word `{}` and memory64={}, but the \ - current memory model, helper ABI, and boundary transport still only \ - implement 32-bit guest pointers", - self.profile.canonical_name(), - self.capability_matrix.guest_word().dump_name(), - self.capability_matrix.uses_memory64() - ), - range, - )); - } - if self.capability_matrix.supports_multi_value() { - return Err(Diagnostic::error( - format!( - "the target profile `{}` selected result lowering mode `{}`, but the current \ - stackifier and emitter still only implement the conservative spill-to-locals \ - path for scalar joins", - self.profile.canonical_name(), - self.capability_matrix.result_lowering().dump_name() - ), - range, - )); - } - if self.capability_matrix.supports_typed_funcref() { - return Err(Diagnostic::error( - format!( - "the target profile `{}` selected callable representation `{}`, but callable \ - registry planning, wrapper generation, and indirect-call emission still only \ - implement handle-and-table lowering", - self.profile.canonical_name(), - self.capability_matrix.callable_representation().dump_name() - ), - range, - )); - } - if self.capability_matrix.supports_wasm_gc() { - return Err(Diagnostic::error( - format!( - "the target profile `{}` selected reference representation `{}`, but layout, \ - retain/release planning, and runtime interop still assume \ - linear-memory-managed references", - self.profile.canonical_name(), - self.capability_matrix.reference_representation().dump_name() - ), - range, - )); - } - if self.capability_matrix.supports_exceptions() { - return Err(Diagnostic::error( - format!( - "the target profile `{}` selected failure lowering `{}`, but wrapper and \ - function emission still only implement trap-style failure handling", - self.profile.canonical_name(), - self.capability_matrix.failure_lowering().dump_name() - ), - range, - )); - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn wasm_core_v2_m32_profile_matches_current_backend_defaults() { - let profile = TargetProfile::wasm_core_v2_m32(); - assert_eq!(profile.pointer_width(), PointerWidth::M32); - assert!(!profile.supports_multi_value()); - assert!(profile.uses_reference_types()); - assert!(!profile.supports_typed_funcref()); - assert!(!profile.supports_wasm_gc()); - assert!(!profile.uses_memory64()); - assert!(!profile.supports_exceptions()); - assert_eq!(profile.canonical_name(), "wasm-core-v2/m32"); - } - - #[test] - fn default_policies_match_current_table_and_memory_model() { - let policies = TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32()); - assert_eq!(policies.memory_model().word_type(), ValType::I32); - assert_eq!(policies.memory_model().pointer_backend_ty(), BackendTy::Int); - assert!(policies.callable_lowering().uses_table_slots()); - assert_eq!(policies.callable_lowering().table_element_type(), RefType::FUNCREF); - assert_eq!(policies.signature().guest_word(), GuestWord::I32); - assert_eq!( - policies.control_flow().result_lowering_mode(&AbiTy::Scalar(RuntimeBackendTy::Int)), - ResultLoweringMode::SpillToLocals - ); - assert_eq!( - policies.reference_representation().representation(), - ReferenceRep::LinearMemoryManaged - ); - assert_eq!( - policies.decision_snapshot().dump(), - "guest_word=i32 result_lowering=spill_to_locals \ - callable_representation=handle_and_table boundary_transport=abi_v2_wrappers \ - reference_representation=linear_memory_managed failure_lowering=trap_only" - ); - } - - #[test] - fn future_profiles_select_expected_target_strategies() { - let multi_value = - TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32().with_multi_value(true)); - assert_eq!( - multi_value.control_flow().result_lowering_mode(&AbiTy::Scalar(RuntimeBackendTy::Int)), - ResultLoweringMode::MultiValue - ); - - let typed_funcref = - TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32().with_typed_funcref(true)); - assert_eq!( - typed_funcref.callable_lowering().representation(), - CallableRepresentation::TypedFuncRef - ); - assert!(!typed_funcref.callable_lowering().uses_table_slots()); - - let gc = TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32().with_wasm_gc(true)); - assert_eq!(gc.reference_representation().representation(), ReferenceRep::GcRef); - - let exceptions = - TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32().with_exceptions(true)); - assert_eq!(exceptions.control_flow().failure_lowering(), FailureLowering::Exceptions); - - let memory64 = TargetPolicies::for_profile( - TargetProfile::wasm_core_v2_m32() - .with_pointer_width(PointerWidth::M64) - .with_memory64(true), - ); - assert_eq!(memory64.memory_model().guest_word(), GuestWord::I64); - } - - #[test] - fn callable_strategy_signatures_follow_selected_representation() { - let signature = FunctionSignature { - params: vec![AbiTy::Scalar(RuntimeBackendTy::Int)], - result: AbiTy::Scalar(RuntimeBackendTy::Int), - }; - let default = TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32()); - let lowered = default.callable_lowering().callable_signature( - default.signature(), - default.memory_model(), - &signature, - ); - assert_eq!(lowered.params, vec![ValType::I32, ValType::I32]); - assert_eq!(lowered.results, vec![ValType::I32]); - - let typed_funcref = - TargetPolicies::for_profile(TargetProfile::wasm_core_v2_m32().with_typed_funcref(true)); - let lowered = typed_funcref.callable_lowering().callable_signature( - typed_funcref.signature(), - typed_funcref.memory_model(), - &signature, - ); - assert_eq!(lowered.params, vec![ValType::I32]); - assert_eq!(lowered.results, vec![ValType::I32]); - } -} diff --git a/crates/mitki-backend-wasm/src/validation/capability.rs b/crates/mitki-backend-wasm/src/validation/capability.rs deleted file mode 100644 index 32cf778..0000000 --- a/crates/mitki-backend-wasm/src/validation/capability.rs +++ /dev/null @@ -1,273 +0,0 @@ -#[cfg(test)] -use std::sync::Arc; - -use mitki_abi::TransportClass; -use mitki_abi_lower::BuiltAbiV2; -use mitki_errors::Diagnostic; -use rustc_hash::FxHashMap; - -use super::super::Backend; -use super::super::boundary::BoundaryPlanner; -use super::super::plan::{FunctionInstanceId, ReachabilityEdgeKind, ReachabilityGraph}; - -pub struct CapabilityValidator; - -impl CapabilityValidator { - pub fn check<'db>(backend: &Backend<'db>) -> Result<(), Diagnostic> { - backend.target_policies().validate_backend_support(backend.file_range())?; - let graph = backend.shadow_reachability.as_ref().ok_or_else(|| { - Diagnostic::error( - "internal error: capability validation requires collected reachability state", - backend.file_range(), - ) - })?; - if backend.capability_matrix().guest_word() != backend.memory_model_strategy().guest_word() - { - return Err(Diagnostic::error( - "internal error: target capability matrix drifted from the memory model strategy", - backend.file_range(), - )); - } - if backend.capability_matrix().callable_representation() - != backend.callable_lowering_strategy().representation() - { - return Err(Diagnostic::error( - "internal error: target capability matrix drifted from the callable strategy", - backend.file_range(), - )); - } - if backend.capability_matrix().reference_representation() - != backend.reference_representation_strategy().representation() - { - return Err(Diagnostic::error( - "internal error: target capability matrix drifted from the reference \ - representation strategy", - backend.file_range(), - )); - } - if backend.capability_matrix().failure_lowering() - != backend.control_flow_strategy().failure_lowering() - { - return Err(Diagnostic::error( - "internal error: target capability matrix drifted from the control-flow strategy", - backend.file_range(), - )); - } - - let function_ids = graph - .functions - .iter() - .enumerate() - .map(|(index, instance)| { - ( - instance.clone(), - FunctionInstanceId( - u32::try_from(index).expect("function count should fit u32"), - ), - ) - }) - .collect::>(); - let boundary = BoundaryPlanner::build(backend, graph, &function_ids)?; - let preview = boundary - .metadata - .build_preview(backend.db) - .map_err(|message| Diagnostic::error(message, backend.file_range()))?; - - if !backend.capability_matrix().uses_reference_types() - && backend.callable_lowering_strategy().requires_reference_types() - && (Self::requires_table_backed_callables(graph) - || Self::boundary_uses_capability_handles(&preview)) - { - return Err(Diagnostic::error( - format!( - "the target profile `{}` does not enable reference types, but the current \ - callable lowering strategy requires them for function values and boundary \ - capability handles", - backend.target_profile().canonical_name() - ), - backend.file_range(), - )); - } - - for entry in &boundary.instances { - let built = &preview.functions[entry.metadata_index]; - let signature = &preview.graph.signatures[built.signature_id.0 as usize]; - backend.boundary_transport_profile().ensure_supported( - &preview.graph, - signature, - backend.function_range(entry.instance.location), - )?; - } - - Ok(()) - } - - fn requires_table_backed_callables<'db>(graph: &ReachabilityGraph<'db>) -> bool { - !graph.closures.is_empty() - || graph.edges.iter().any(|edge| edge.kind == ReachabilityEdgeKind::FunctionValue) - } - - fn boundary_uses_capability_handles(preview: &BuiltAbiV2) -> bool { - preview.functions.iter().any(|function| { - let signature = &preview.graph.signatures[function.signature_id.0 as usize]; - signature - .params - .iter() - .chain(std::iter::once(&signature.result)) - .any(|transport| transport.transport_class == TransportClass::CapabilityHandle) - }) - } -} - -#[cfg(test)] -mod tests { - use mitki_comptime_wasm::compile_file_to_wasm; - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - use crate::CompileOptions; - use crate::backend::BoundaryLegalityValidator; - use crate::backend::target::PointerWidth; - - fn build_compiler<'db>( - db: &'db RootDatabase, - fixture: &str, - profile: crate::backend::TargetProfile, - ) -> Backend<'db> { - let file = File::new(db, "capability_target_profile.mitki".into(), fixture.to_owned()); - assert!(mitki_analysis::check_file(db, file).is_empty(), "fixture should parse cleanly"); - assert!( - mitki_analysis::check_runtime_file(db, file).is_empty(), - "fixture should pass runtime checks" - ); - let mut backend = Backend::new_file_with_profile( - db, - file, - profile, - CompileOptions, - Arc::new(crate::NoopComptimeEvaluator), - ); - backend.collect_reachable_program(); - let legality = BoundaryLegalityValidator::check(&backend); - assert!(legality.is_empty(), "fixture should be boundary-legal: {legality:?}"); - backend - } - - #[test] - fn explicit_default_profile_matches_public_default_compilation() { - let fixture = r#" -export fun main(): int { - 42 -} -"#; - let public_db = RootDatabase::default(); - let public_file = File::new( - &public_db, - "capability_target_profile_public.mitki".into(), - fixture.to_owned(), - ); - let public_bytes = - compile_file_to_wasm(&public_db, public_file).expect("public compile should work"); - - let explicit_db = RootDatabase::default(); - let backend = build_compiler( - &explicit_db, - fixture, - crate::backend::TargetProfile::wasm_core_v2_m32(), - ); - CapabilityValidator::check(&backend).expect("default target profile should validate"); - let explicit_bytes = backend.emit_module().expect("explicit profile compile should work"); - - assert_eq!(public_bytes, explicit_bytes); - } - - #[test] - fn unsupported_future_profile_reports_capability_limit() { - let fixture = r#" -export fun main(): int { - 42 -} -"#; - let db = RootDatabase::default(); - let profile = crate::backend::TargetProfile::wasm_core_v2_m32() - .with_pointer_width(PointerWidth::M64) - .with_memory64(true); - let backend = build_compiler(&db, fixture, profile); - let diagnostic = - CapabilityValidator::check(&backend).expect_err("memory64 profile should be rejected"); - assert!(diagnostic.message().contains("target profile `wasm-core-v2/m64`")); - assert!(diagnostic.message().contains("memory64")); - } - - #[test] - fn multi_value_profile_is_rejected_before_lowering() { - let db = RootDatabase::default(); - let backend = build_compiler( - &db, - "export fun main(): int { 42 }", - crate::backend::TargetProfile::wasm_core_v2_m32().with_multi_value(true), - ); - let diagnostic = - CapabilityValidator::check(&backend).expect_err("multi-value should be rejected"); - assert!(diagnostic.message().contains("result lowering mode `multi_value`")); - } - - #[test] - fn typed_funcref_profile_is_rejected_before_callable_planning() { - let db = RootDatabase::default(); - let backend = build_compiler( - &db, - "export fun main(): int { 42 }", - crate::backend::TargetProfile::wasm_core_v2_m32().with_typed_funcref(true), - ); - let diagnostic = - CapabilityValidator::check(&backend).expect_err("typed funcref should be rejected"); - assert!(diagnostic.message().contains("callable representation `typed_funcref`")); - } - - #[test] - fn wasm_gc_profile_is_rejected_before_layout_selection() { - let db = RootDatabase::default(); - let backend = build_compiler( - &db, - "export fun main(): int { 42 }", - crate::backend::TargetProfile::wasm_core_v2_m32().with_wasm_gc(true), - ); - let diagnostic = - CapabilityValidator::check(&backend).expect_err("wasm gc should be rejected"); - assert!(diagnostic.message().contains("reference representation `gc_ref`")); - } - - #[test] - fn exceptions_profile_is_rejected_before_control_flow_lowering() { - let db = RootDatabase::default(); - let backend = build_compiler( - &db, - "export fun main(): int { 42 }", - crate::backend::TargetProfile::wasm_core_v2_m32().with_exceptions(true), - ); - let diagnostic = - CapabilityValidator::check(&backend).expect_err("exceptions should be rejected"); - assert!(diagnostic.message().contains("failure lowering `exceptions`")); - } - - #[test] - fn reference_types_must_remain_enabled_for_handle_based_callables() { - let db = RootDatabase::default(); - let backend = build_compiler( - &db, - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun main(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - crate::backend::TargetProfile::wasm_core_v2_m32().with_reference_types(false), - ); - let diagnostic = CapabilityValidator::check(&backend) - .expect_err("reference types should be required for table-backed callables"); - assert!(diagnostic.message().contains("does not enable reference types")); - } -} diff --git a/crates/mitki-backend-wasm/src/validation/legality.rs b/crates/mitki-backend-wasm/src/validation/legality.rs deleted file mode 100644 index fb70edb..0000000 --- a/crates/mitki-backend-wasm/src/validation/legality.rs +++ /dev/null @@ -1,107 +0,0 @@ -use mitki_errors::Diagnostic; -use mitki_hir::hir::WasmLinkage; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::BoundaryInstanceKind; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::ast::HasName as _; - -use super::super::{Backend, InstanceKey}; - -pub struct BoundaryLegalityValidator; - -impl BoundaryLegalityValidator { - pub fn check<'db>(backend: &Backend<'db>) -> Vec { - let mut diagnostics = Vec::new(); - let Some(graph) = backend.shadow_reachability.as_ref() else { - return vec![Diagnostic::error( - "internal error: boundary legality validation requires collected reachability \ - state", - backend.file_range(), - )]; - }; - - for instance in &graph.imports { - let function = instance.location.hir_function(backend.db).function(backend.db); - if !matches!(function.linkage(), WasmLinkage::Import { .. }) { - continue; - } - if !instance.type_args.is_empty() - && !backend.has_declared_boundary_instance(BoundaryInstanceKind::Import, instance) - { - diagnostics.push(Diagnostic::error( - format!( - "`import instance` declaration is required for `{}` with the reachable \ - concrete type arguments", - instance - .location - .source(backend.db) - .name() - .map_or("", |name| name.as_str()) - ), - backend.function_range(instance.location), - )); - continue; - } - diagnostics.extend(Self::check_signature_legality(backend, instance)); - } - - for instance in &graph.exports { - diagnostics.extend(Self::check_signature_legality(backend, instance)); - } - - diagnostics - } - - fn check_signature_legality<'db>( - backend: &Backend<'db>, - instance: &InstanceKey<'db>, - ) -> Vec { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let source_map = hir_function.source_map(backend.db); - let nodes = function.node_store(); - let mut diagnostics = Vec::new(); - let (param_tys, result_ty) = - match backend.function_signature_types(instance, function, inference) { - Ok(types) => types, - Err(diagnostic) => return vec![diagnostic], - }; - - for (¶m, ty) in function.params().iter().zip(param_tys) { - let (pattern, ty_id) = nodes.param(param); - let range = if ty_id != mitki_hir::hir::TyId::ZERO { - source_map - .try_type_syntax(ty_id) - .map_or_else(|| backend.function_range(instance.location), |ptr| ptr.range) - } else { - source_map - .try_pat_syntax(pattern) - .map_or_else(|| backend.function_range(instance.location), |ptr| ptr.range) - }; - if let Some(failure) = - mitki_analysis::wasm_boundary_legality::typed_wasm_boundary_failure(backend.db, ty) - { - diagnostics.push(Diagnostic::error( - mitki_analysis::wasm_boundary_legality::typed_wasm_boundary_message( - backend.db, failure, - ), - range, - )); - } - } - - if let Some(failure) = mitki_analysis::wasm_boundary_legality::typed_wasm_boundary_failure( - backend.db, result_ty, - ) { - diagnostics.push(Diagnostic::error( - mitki_analysis::wasm_boundary_legality::typed_wasm_boundary_message( - backend.db, failure, - ), - backend.return_range(instance.location, function, source_map), - )); - } - - diagnostics - } -} diff --git a/crates/mitki-backend-wasm/src/validation/mod.rs b/crates/mitki-backend-wasm/src/validation/mod.rs deleted file mode 100644 index 1ac120c..0000000 --- a/crates/mitki-backend-wasm/src/validation/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod capability; -mod legality; -mod plan; - -pub use self::capability::CapabilityValidator; -pub use self::legality::BoundaryLegalityValidator; -pub(in crate::backend) use self::plan::{ - BoundaryPlanValidator, ModulePlanValidator, StoragePlanValidator, -}; diff --git a/crates/mitki-backend-wasm/src/validation/plan.rs b/crates/mitki-backend-wasm/src/validation/plan.rs deleted file mode 100644 index 134e2e7..0000000 --- a/crates/mitki-backend-wasm/src/validation/plan.rs +++ /dev/null @@ -1,1499 +0,0 @@ -use mitki_abi_lower::boundary_transport_class; -use mitki_errors::Diagnostic; -use rustc_hash::FxHashSet; - -use super::super::boundary::BoundaryPlan; -use super::super::plan::{CallableAdapterNeed, FunctionInstanceId, HelperNeed, ModulePlan}; -use super::super::registry::{CallableSigId, HelperId, SectionExportTarget}; -use super::super::*; - -pub(in crate::backend) struct BoundaryPlanValidator; -pub(in crate::backend) struct ModulePlanValidator; -pub(in crate::backend) struct StoragePlanValidator; - -impl BoundaryPlanValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &BoundaryPlan<'db>, - ) -> Result<(), Diagnostic> { - if plan.instances.len() != plan.metadata.functions.len() { - return Err(Diagnostic::error( - format!( - "internal error: boundary plan has {} entries but {} metadata records", - plan.instances.len(), - plan.metadata.functions.len() - ), - backend.file_range(), - )); - } - if plan.imports.len() != plan.import_indices.len() - || plan.exports.len() != plan.export_indices.len() - || plan.instances.len() != plan.instance_indices.len() - { - return Err(Diagnostic::error( - "internal error: boundary lookup maps drifted from the planned vectors", - backend.file_range(), - )); - } - - for (expected_index, entry) in plan.instances.iter().enumerate() { - if entry.metadata_index != expected_index { - return Err(Diagnostic::error( - format!( - "internal error: boundary plan entry `{}` was assigned metadata index {}, \ - expected {}", - entry.logical_name, entry.metadata_index, expected_index - ), - backend.function_range(entry.instance.location), - )); - } - let metadata = &plan.metadata.functions[expected_index]; - if metadata.logical_name != entry.logical_name - || metadata.generic_origin_name != entry.generic_origin_name - || metadata.wasm_module_name != entry.wasm_module_name - || metadata.wasm_field_name != entry.wasm_field_name - || metadata.type_args != entry.instance.type_args - || metadata.domain != entry.domain - || metadata.linkage != entry.linkage - { - return Err(Diagnostic::error( - format!( - "internal error: boundary metadata drifted from the shadow plan for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - if metadata.param_tys.len() != entry.signature.params.len() - || metadata - .param_tys - .iter() - .zip(entry.signature.params.iter()) - .any(|(semantic_ty, slot)| semantic_ty != &slot.semantic_ty) - { - return Err(Diagnostic::error( - format!( - "internal error: boundary metadata params drifted from the boundary \ - signature for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - } - - for (instance, metadata_index) in &plan.import_indices { - let Some(entry) = plan.instances.get(*metadata_index) else { - return Err(Diagnostic::error( - "internal error: boundary import index pointed past the plan entries", - backend.file_range(), - )); - }; - if entry.instance != *instance || entry.linkage != mitki_abi::LinkageKind::WasmImport { - return Err(Diagnostic::error( - format!( - "internal error: boundary import index mismatch for `{}`", - entry.logical_name - ), - backend.function_range(instance.location), - )); - } - } - - for (instance, metadata_index) in &plan.export_indices { - let Some(entry) = plan.instances.get(*metadata_index) else { - return Err(Diagnostic::error( - "internal error: boundary export index pointed past the plan entries", - backend.file_range(), - )); - }; - if entry.instance != *instance || entry.linkage != mitki_abi::LinkageKind::WasmExport { - return Err(Diagnostic::error( - format!( - "internal error: boundary export index mismatch for `{}`", - entry.logical_name - ), - backend.function_range(instance.location), - )); - } - } - - let mut raw_imports = FxHashSet::default(); - for import in &plan.imports { - let Some(entry) = plan.instances.get(import.metadata_index) else { - return Err(Diagnostic::error( - "internal error: import wrapper pointed past the boundary entries", - backend.file_range(), - )); - }; - if entry.function_id != import.function_id - || entry.instance != import.instance - || entry.linkage != mitki_abi::LinkageKind::WasmImport - || entry.signature != import.wrapper.signature - || entry.wasm_module_name.as_deref() != Some(import.module_name.as_str()) - || entry.wasm_field_name != import.field_name - { - return Err(Diagnostic::error( - format!( - "internal error: import wrapper plan drifted from boundary entry `{}`", - import.logical_name - ), - backend.function_range(import.instance.location), - )); - } - if !raw_imports.insert((import.module_name.clone(), import.field_name.clone())) { - return Err(Diagnostic::error( - format!( - "internal error: duplicate boundary raw import mapping `{}::{}`", - import.module_name, import.field_name - ), - backend.function_range(import.instance.location), - )); - } - } - - for export in &plan.exports { - let Some(entry) = plan.instances.get(export.metadata_index) else { - return Err(Diagnostic::error( - "internal error: export wrapper pointed past the boundary entries", - backend.file_range(), - )); - }; - if entry.function_id != export.function_id - || entry.instance != export.instance - || entry.linkage != mitki_abi::LinkageKind::WasmExport - || entry.signature != export.wrapper.signature - || entry.wasm_field_name != export.export_name - { - return Err(Diagnostic::error( - format!( - "internal error: export wrapper plan drifted from boundary entry `{}`", - export.logical_name - ), - backend.function_range(export.instance.location), - )); - } - } - - let mut aliases = FxHashSet::default(); - for alias in &plan.aliases { - if !aliases.insert(alias.alias.clone()) { - return Err(Diagnostic::error( - format!("internal error: duplicate boundary export alias `{}`", alias.alias), - backend.function_range(alias.instance.location), - )); - } - let Some(&metadata_index) = plan.export_indices.get(&alias.instance) else { - return Err(Diagnostic::error( - format!( - "internal error: export alias `{}` had no matching boundary export", - alias.alias - ), - backend.function_range(alias.instance.location), - )); - }; - let entry = &plan.instances[metadata_index]; - if entry.metadata_index != alias.metadata_index || entry.instance != alias.instance { - return Err(Diagnostic::error( - format!( - "internal error: export alias `{}` did not match the boundary plan", - alias.alias - ), - backend.function_range(alias.instance.location), - )); - } - } - - let preview = plan - .metadata - .build_preview(backend.db) - .map_err(|message| Diagnostic::error(message, backend.file_range()))?; - if preview.functions.len() != plan.instances.len() { - return Err(Diagnostic::error( - "internal error: planned ABI preview drifted from boundary entries", - backend.file_range(), - )); - } - for entry in &plan.instances { - let built = &preview.functions[entry.metadata_index]; - let signature = &preview.graph.signatures[built.signature_id.0 as usize]; - if built.wasm_field_name != entry.wasm_field_name || built.linkage != entry.linkage { - return Err(Diagnostic::error( - format!( - "internal error: metadata-facing function instance drifted from boundary \ - plan for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - if signature.params.len() != entry.signature.params.len() - || signature.params.iter().zip(entry.signature.params.iter()).any( - |(transport, slot)| transport.transport_class != slot.transport.transport_class, - ) - { - return Err(Diagnostic::error( - format!( - "internal error: metadata signature drifted from boundary params for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - let expected_result_class = boundary_transport_class( - backend.db, - plan.metadata.functions[entry.metadata_index].result_ty, - ); - if signature.result.transport_class != expected_result_class { - return Err(Diagnostic::error( - format!( - "internal error: metadata signature drifted from boundary result for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - backend.boundary_transport_profile().ensure_supported( - &preview.graph, - signature, - backend.function_range(entry.instance.location), - )?; - } - - Ok(()) - } -} - -impl StoragePlanValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - layout: &FunctionLayout, - range: mitki_errors::TextRange, - ) -> Result<(), Diagnostic> { - let mut frame_end = 0u32; - for (expected_index, slot) in layout.frame_plan.slots.iter().enumerate() { - if slot.id.0 as usize != expected_index { - return Err(Diagnostic::error( - "internal error: frame slot ids drifted from frame slot ordering", - range, - )); - } - if slot.align == 0 || slot.offset % slot.align != 0 { - return Err(Diagnostic::error( - "internal error: frame slot alignment was not applied correctly", - range, - )); - } - if slot.offset < frame_end { - return Err(Diagnostic::error( - "internal error: frame slots overlap in the storage plan", - range, - )); - } - frame_end = slot.offset.saturating_add(slot.size); - } - if layout.frame_plan.size < frame_end { - return Err(Diagnostic::error( - "internal error: frame plan size truncated planned frame slots", - range, - )); - } - if layout.frame_plan.size > 0 && layout.frame_base_local().is_none() { - return Err(Diagnostic::error( - "internal error: non-empty frame plan is missing its frame-base scratch local", - range, - )); - } - if layout.frame_plan.size == 0 && layout.frame_base_local().is_some() { - return Err(Diagnostic::error( - "internal error: empty frame plan unexpectedly reserved a frame-base scratch local", - range, - )); - } - - for slot in &layout.raw_params { - Self::validate_storage_slot(backend, layout, slot, range)?; - if slot.frame_slot.is_some() { - return Err(Diagnostic::error( - "internal error: raw parameter storage unexpectedly used a frame slot", - range, - )); - } - } - for slot in layout.slots.values() { - Self::validate_storage_slot(backend, layout, slot, range)?; - } - for temp in layout.temps.values() { - if layout.frame_plan.slot(temp.frame_slot).is_none() { - return Err(Diagnostic::error( - "internal error: aggregate temp referenced a missing frame slot", - range, - )); - } - } - for &local in layout.pattern_scalar_locals.values() { - if !layout.contains_local_index(local) { - return Err(Diagnostic::error( - "internal error: pattern spill local drifted from the explicit local plan", - range, - )); - } - } - for &local in layout.nominal_locals.values() { - if !layout.contains_local_index(local) { - return Err(Diagnostic::error( - "internal error: nominal spill local drifted from the explicit local plan", - range, - )); - } - } - for &local in layout.array_repeat_locals.values() { - if !layout.contains_local_index(local) { - return Err(Diagnostic::error( - "internal error: array-repeat spill local drifted from the explicit local plan", - range, - )); - } - } - for &name in &layout.param_names { - if !layout.slots.contains_key(&name) { - return Err(Diagnostic::error( - "internal error: parameter binding order referenced a missing storage slot", - range, - )); - } - } - - let expected_non_param_start = layout - .local_plan - .params - .iter() - .filter_map(|local| local.local_index) - .max() - .map_or(0, |index| index + 1); - let mut value_types = Vec::new(); - for (expected_non_param_index, local) in - (expected_non_param_start..).zip(layout.local_plan.allocation_order.iter()) - { - let Some(local_index) = local.local_index else { - return Err(Diagnostic::error( - "internal error: non-parameter local was missing its Wasm local index", - range, - )); - }; - if local_index != expected_non_param_index { - return Err(Diagnostic::error( - "internal error: local-plan ordering drifted from Wasm local assignment", - range, - )); - } - let Some(value_type) = local.value_type else { - return Err(Diagnostic::error( - "internal error: non-parameter local was missing its Wasm value type", - range, - )); - }; - value_types.push(value_type); - } - let grouped_value_types = layout - .wasm_locals() - .iter() - .flat_map(|(count, value_type)| std::iter::repeat_n(*value_type, *count as usize)) - .collect::>(); - if grouped_value_types != value_types { - return Err(Diagnostic::error( - "internal error: grouped Wasm locals drifted from the explicit local plan", - range, - )); - } - - let required_scratch = [ - ScratchLocalKind::ScratchI32, - ScratchLocalKind::ScratchI32Aux, - ScratchLocalKind::ObjectI32, - ScratchLocalKind::ScratchI64, - ]; - for kind in required_scratch { - let exists = layout.local_plan.scratch.iter().any( - |local| matches!(local.purpose, LocalPurpose::Scratch(found) if found == kind), - ); - if !exists { - return Err(Diagnostic::error( - "internal error: storage plan is missing a required scratch local", - backend.file_range(), - )); - } - } - - Ok(()) - } - - fn validate_storage_slot<'db>( - _compiler: &Backend<'db>, - layout: &FunctionLayout, - slot: &LocalSlot, - range: mitki_errors::TextRange, - ) -> Result<(), Diagnostic> { - match (slot.local_index, slot.frame_slot) { - (Some(local_index), None) => { - if !layout.contains_local_index(local_index) { - return Err(Diagnostic::error( - "internal error: storage slot pointed at a missing Wasm local", - range, - )); - } - } - (None, Some(frame_slot)) => { - if layout.frame_plan.slot(frame_slot).is_none() { - return Err(Diagnostic::error( - "internal error: storage slot pointed at a missing frame slot", - range, - )); - } - } - (None, None) if matches!(slot.abi, AbiTy::Scalar(BackendTy::Unit)) => {} - _ => { - return Err(Diagnostic::error( - "internal error: storage slot encoded an invalid local/frame assignment", - range, - )); - } - } - Ok(()) - } -} - -impl ModulePlanValidator { - pub(in crate::backend) fn validate<'db>( - backend: &Backend<'db>, - plan: &ModulePlan<'db>, - ) -> Result<(), Diagnostic> { - BoundaryPlanValidator::validate(backend, &plan.boundary)?; - - if plan.mode != plan.reachability.mode { - return Err(Diagnostic::error( - "internal error: module plan mode disagreed with reachability mode", - backend.file_range(), - )); - } - if plan.target_profile != backend.target_profile() { - return Err(Diagnostic::error( - "internal error: module plan target profile drifted from Backend target policy", - backend.file_range(), - )); - } - if plan.target_decisions != backend.target_decision_snapshot() { - return Err(Diagnostic::error( - "internal error: module plan target decisions drifted from Backend target policy", - backend.file_range(), - )); - } - - let unique_runtime_imports = - plan.obligations.runtime_imports.iter().collect::>(); - if unique_runtime_imports.len() != plan.obligations.runtime_imports.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a runtime import", - backend.file_range(), - )); - } - let unique_stage_intrinsics = - plan.obligations.stage_intrinsics.iter().collect::>(); - if unique_stage_intrinsics.len() != plan.obligations.stage_intrinsics.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a stage intrinsic", - backend.file_range(), - )); - } - let unique_helpers = plan.obligations.helpers.iter().collect::>(); - if unique_helpers.len() != plan.obligations.helpers.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a helper requirement", - backend.file_range(), - )); - } - let unique_runtime_import_needs = - plan.obligations.runtime_import_needs.iter().collect::>(); - if unique_runtime_import_needs.len() != plan.obligations.runtime_import_needs.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a runtime import need", - backend.file_range(), - )); - } - let unique_callable_adapters = - plan.obligations.callable_adapters.iter().collect::>(); - if unique_callable_adapters.len() != plan.obligations.callable_adapters.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a callable adapter need", - backend.file_range(), - )); - } - let unique_boundary_wrappers = - plan.obligations.boundary_wrappers.iter().collect::>(); - if unique_boundary_wrappers.len() != plan.obligations.boundary_wrappers.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a boundary wrapper need", - backend.file_range(), - )); - } - let unique_canonical_support = - plan.obligations.canonical_support.iter().collect::>(); - if unique_canonical_support.len() != plan.obligations.canonical_support.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a canonical support need", - backend.file_range(), - )); - } - let unique_layout_needs = plan.obligations.layout_needs.iter().collect::>(); - if unique_layout_needs.len() != plan.obligations.layout_needs.len() { - return Err(Diagnostic::error( - "internal error: emission obligations duplicated a layout support need", - backend.file_range(), - )); - } - let unique_helper_needs = plan.helpers.needs.iter().collect::>(); - if unique_helper_needs.len() != plan.helpers.needs.len() { - return Err(Diagnostic::error( - "internal error: module plan duplicated a helper need", - backend.file_range(), - )); - } - - if plan.function_instances.len() != plan.reachability.functions.len() { - return Err(Diagnostic::error( - "internal error: function instance planning drifted from reachable functions", - backend.file_range(), - )); - } - for (expected_index, function) in plan.function_instances.iter().enumerate() { - let expected_id = FunctionInstanceId(expected_index as u32); - if function.id != expected_id { - return Err(Diagnostic::error( - format!( - "internal error: function instance `{}` had id f{}, expected f{}", - function.logical_name, function.id.0, expected_id.0 - ), - backend.function_range(function.instance.location), - )); - } - if plan.reachability.functions.get(expected_index) != Some(&function.instance) { - return Err(Diagnostic::error( - format!( - "internal error: function instance `f{}` drifted from reachability order", - function.id.0 - ), - backend.function_range(function.instance.location), - )); - } - if function.internal_signature.results.len() > 1 { - return Err(Diagnostic::error( - format!( - "internal error: function instance `{}` recorded more than one internal \ - result in Step 1", - function.logical_name - ), - backend.function_range(function.instance.location), - )); - } - } - - let reachable_functions = plan.reachability.functions.iter().collect::>(); - for entry in &plan.boundary.instances { - if !reachable_functions.contains(&entry.instance) { - return Err(Diagnostic::error( - format!( - "internal error: boundary entry `{}` was not present in the reachability \ - graph", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - let Some(function) = plan.function_instances.get(entry.function_id.0 as usize) else { - return Err(Diagnostic::error( - format!( - "internal error: boundary entry `{}` referenced missing function id f{}", - entry.logical_name, entry.function_id.0 - ), - backend.function_range(entry.instance.location), - )); - }; - if function.instance != entry.instance { - return Err(Diagnostic::error( - format!( - "internal error: boundary entry `{}` drifted from function id f{}", - entry.logical_name, entry.function_id.0 - ), - backend.function_range(entry.instance.location), - )); - } - if function.metadata_index != Some(entry.metadata_index) - || function.boundary_signature.as_ref() != Some(&entry.signature) - || function.internal_signature != entry.signature.internal - { - return Err(Diagnostic::error( - format!( - "internal error: function instance shadow state drifted from boundary \ - entry `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - } - - if plan.abi_preview.functions.len() != plan.boundary.instances.len() { - return Err(Diagnostic::error( - "internal error: ABI v2 preview disagreed with the boundary plan entry count", - backend.file_range(), - )); - } - for entry in &plan.boundary.instances { - let built = &plan.abi_preview.functions[entry.metadata_index]; - if built.wasm_field_name != entry.wasm_field_name || built.linkage != entry.linkage { - return Err(Diagnostic::error( - format!( - "internal error: ABI v2 preview drifted from the boundary plan for `{}`", - entry.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - } - let rebuilt = plan - .boundary - .metadata - .build_preview(backend.db) - .map_err(|message| Diagnostic::error(message, backend.file_range()))?; - if rebuilt != plan.abi_preview { - return Err(Diagnostic::error( - "internal error: stored ABI v2 preview drifted from the boundary plan", - backend.file_range(), - )); - } - - for import in &plan.imports { - let Some(entry) = plan.boundary.instances.get(import.metadata_index) else { - return Err(Diagnostic::error( - "internal error: import plan pointed past the boundary entries", - backend.file_range(), - )); - }; - if entry.function_id != import.function_id - || entry.linkage != mitki_abi::LinkageKind::WasmImport - || entry.logical_name != import.logical_name - || entry.wasm_field_name != import.field_name - || entry.wasm_module_name.as_deref() != Some(import.module_name.as_str()) - { - return Err(Diagnostic::error( - format!( - "internal error: import plan drifted from boundary entry `{}`", - import.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - } - if plan.imports.len() != plan.boundary.import_indices.len() { - return Err(Diagnostic::error( - "internal error: import plan count drifted from boundary imports", - backend.file_range(), - )); - } - - for export in &plan.exports { - let Some(entry) = plan.boundary.instances.get(export.metadata_index) else { - return Err(Diagnostic::error( - "internal error: export plan pointed past the boundary entries", - backend.file_range(), - )); - }; - if entry.function_id != export.function_id - || entry.linkage != mitki_abi::LinkageKind::WasmExport - || entry.logical_name != export.logical_name - || entry.wasm_field_name != export.export_name - { - return Err(Diagnostic::error( - format!( - "internal error: export plan drifted from boundary entry `{}`", - export.logical_name - ), - backend.function_range(entry.instance.location), - )); - } - } - if plan.boundary.aliases.len() < plan.exports.len() { - return Err(Diagnostic::error( - "internal error: export plan count drifted from boundary export aliases", - backend.file_range(), - )); - } - - let mut expected_helpers = Vec::new(); - expected_helpers - .extend(plan.obligations.helpers.iter().copied().map(HelperNeed::from_helper_function)); - expected_helpers.extend(plan.obligations.reachable_nominals.iter().flat_map(|ty| { - let bits = nominal_ty_bits(*ty); - [HelperNeed::NominalDestroy(bits), HelperNeed::NominalEq(bits)] - })); - expected_helpers.extend(plan.obligations.reachable_arrays.iter().flat_map(|ty| { - let bits = array_ty_bits(*ty); - [HelperNeed::ArrayDestroy(bits), HelperNeed::ArrayEq(bits)] - })); - if plan.obligations.needs_blob_helpers() { - expected_helpers.push(HelperNeed::AbiAlloc); - expected_helpers.push(HelperNeed::AbiBlobRelease); - } - if plan.obligations.needs_handle_helpers() { - expected_helpers.push(HelperNeed::AbiHandleRetain); - expected_helpers.push(HelperNeed::AbiHandleRelease); - } - expected_helpers.extend(plan.obligations.callable_adapters.iter().filter_map(|adapter| { - let CallableAdapterNeed::BoundaryInvoke(instance) = adapter else { - return None; - }; - let metadata_index = plan - .boundary - .import_indices - .get(instance) - .or_else(|| plan.boundary.export_indices.get(instance)) - .copied()?; - Some(HelperNeed::HandleInvoke(plan.abi_preview.functions[metadata_index].signature_id)) - })); - if plan.obligations.layout_needs.len() - != plan.obligations.reachable_arrays.len() + plan.obligations.reachable_nominals.len() - { - return Err(Diagnostic::error( - "internal error: layout support obligations drifted from reachable array/nominal \ - obligations", - backend.file_range(), - )); - } - expected_helpers.sort(); - expected_helpers.dedup(); - if expected_helpers != plan.helpers.needs { - return Err(Diagnostic::error( - "internal error: helper needs drifted from emission obligations", - backend.file_range(), - )); - } - let expected_builtin_helpers = HelperFunction::all().into_iter().collect::>(); - if plan.helpers.builtin_helpers != expected_builtin_helpers { - return Err(Diagnostic::error( - "internal error: helper registry builtin helper order drifted from the backend \ - helper surface", - backend.file_range(), - )); - } - if plan.helpers.helper_ids.len() != plan.helpers.needs.len() { - return Err(Diagnostic::error( - "internal error: helper registry id map drifted from helper needs", - backend.file_range(), - )); - } - for (index, helper) in plan.helpers.needs.iter().enumerate() { - let expected_id = HelperId(index as u32); - if plan.helpers.helper_ids.get(helper).copied() != Some(expected_id) { - return Err(Diagnostic::error( - format!( - "internal error: helper registry assigned `{}` an unexpected id", - helper.dump_name() - ), - backend.file_range(), - )); - } - } - - let mut expected_callable_signatures = Vec::new(); - let mut seen_callable_signatures = FxHashSet::default(); - for instance in &plan.reachability.functions { - let hir_function = instance.location.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = instance.location.infer(backend.db); - let signature = backend.function_signature(instance, function, inference)?; - if seen_callable_signatures.insert(signature.clone()) { - expected_callable_signatures.push(signature); - } - } - for closure in &plan.reachability.closures { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - let info = backend.closure_info(closure, function, inference)?; - if seen_callable_signatures.insert(info.signature.clone()) { - expected_callable_signatures.push(info.signature); - } - } - let planned_callable_signatures = plan - .callables - .signatures - .iter() - .map(|callable| callable.signature.clone()) - .collect::>(); - if expected_callable_signatures != planned_callable_signatures { - return Err(Diagnostic::error( - "internal error: callable registry signature order drifted from reachability", - backend.file_range(), - )); - } - if plan.callables.signature_ids.len() != plan.callables.signatures.len() { - return Err(Diagnostic::error( - "internal error: callable registry signature lookup drifted from callable plans", - backend.file_range(), - )); - } - for (index, callable) in plan.callables.signatures.iter().enumerate() { - let expected_id = CallableSigId(index as u32); - if callable.id != expected_id - || plan.callables.signature_ids.get(&callable.signature).copied() - != Some(expected_id) - { - return Err(Diagnostic::error( - "internal error: callable registry ids drifted from signature order", - backend.file_range(), - )); - } - } - - let expected_trampolines = plan - .obligations - .callable_adapters - .iter() - .filter_map(|adapter| { - let CallableAdapterNeed::BoundaryInvoke(instance) = adapter else { - return None; - }; - let metadata_index = plan - .boundary - .import_indices - .get(instance) - .or_else(|| plan.boundary.export_indices.get(instance)) - .copied()?; - Some(( - plan.abi_preview.functions[metadata_index].signature_id, - plan.function_instances - .iter() - .find(|function| function.instance == *instance) - .expect("reachable function instance should exist") - .id, - instance.clone(), - metadata_index, - )) - }) - .collect::>(); - let planned_trampolines = plan - .callables - .invoke_trampolines - .iter() - .map(|trampoline| { - ( - trampoline.signature_id, - trampoline.function_id, - trampoline.instance.clone(), - trampoline.metadata_index, - ) - }) - .collect::>(); - if expected_trampolines != planned_trampolines { - return Err(Diagnostic::error( - "internal error: handle trampoline registration drifted from boundary-visible \ - callable adapter obligations", - backend.file_range(), - )); - } - - let expected_runtime_imports = plan.obligations.runtime_imports.clone(); - if plan.sections.runtime_imports != expected_runtime_imports { - return Err(Diagnostic::error( - "internal error: section planner runtime import order drifted from obligations", - backend.file_range(), - )); - } - let expected_stage_intrinsics = StageIntrinsic::all() - .into_iter() - .filter(|intrinsic| plan.obligations.stage_intrinsics.contains(intrinsic)) - .collect::>(); - if plan.sections.stage_intrinsics != expected_stage_intrinsics { - return Err(Diagnostic::error( - "internal error: section planner stage intrinsic order drifted from obligations", - backend.file_range(), - )); - } - let expected_raw_imports = plan - .reachability - .functions - .iter() - .filter(|instance| { - let function = instance.location.hir_function(backend.db).function(backend.db); - matches!( - function.linkage(), - WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. } - ) - }) - .cloned() - .collect::>(); - if plan.sections.raw_imports != expected_raw_imports - || plan.sections.direct_functions != plan.reachability.functions - || plan.sections.function_wrappers != plan.reachability.functions - || plan.sections.closure_functions != plan.reachability.closures - || plan.sections.export_wrappers - != plan - .boundary - .exports - .iter() - .map(|export| export.instance.clone()) - .collect::>() - || plan.sections.builtin_helpers != expected_builtin_helpers - { - return Err(Diagnostic::error( - "internal error: section planner function membership drifted from the module plan", - backend.file_range(), - )); - } - - let expected_closure_destroyers = plan - .reachability - .closures - .iter() - .filter_map(|closure| { - let hir_function = closure.owner.hir_function(backend.db); - let function = hir_function.function(backend.db); - let inference = closure.owner.infer(backend.db); - backend - .closure_info(closure, function, inference) - .ok() - .filter(|info| info.env_layout.size != 0) - .map(|_| closure.clone()) - }) - .collect::>(); - if plan.sections.closure_destroyers != expected_closure_destroyers { - return Err(Diagnostic::error( - "internal error: section planner closure destroyer order drifted from reachable \ - closures", - backend.file_range(), - )); - } - - let mut next_type_index = 0u32; - for runtime in &plan.sections.runtime_imports { - if plan.sections.runtime_type_indices.get(runtime).copied() != Some(next_type_index) { - return Err(Diagnostic::error( - "internal error: runtime type indices drifted from section ordering", - backend.file_range(), - )); - } - next_type_index += 1; - } - for intrinsic in &plan.sections.stage_intrinsics { - if plan.sections.stage_type_indices.get(intrinsic).copied() != Some(next_type_index) { - return Err(Diagnostic::error( - "internal error: stage intrinsic type indices drifted from section ordering", - backend.file_range(), - )); - } - next_type_index += 1; - } - for helper in &plan.sections.builtin_helpers { - if plan.sections.helper_type_indices.get(helper).copied() != Some(next_type_index) { - return Err(Diagnostic::error( - "internal error: helper type indices drifted from section ordering", - backend.file_range(), - )); - } - next_type_index += 1; - } - if plan.sections.nominal_destroy_type_index - != (!plan.obligations.reachable_nominals.is_empty()).then_some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: nominal destroy helper type placement drifted", - backend.file_range(), - )); - } - if plan.sections.nominal_destroy_type_index.is_some() { - next_type_index += 1; - } - if plan.sections.nominal_eq_type_index - != (!plan.obligations.reachable_nominals.is_empty()).then_some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: nominal equality helper type placement drifted", - backend.file_range(), - )); - } - if plan.sections.nominal_eq_type_index.is_some() { - next_type_index += 1; - } - if plan.sections.array_destroy_type_index - != (!plan.obligations.reachable_arrays.is_empty()).then_some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: array destroy helper type placement drifted", - backend.file_range(), - )); - } - if plan.sections.array_destroy_type_index.is_some() { - next_type_index += 1; - } - if plan.sections.array_eq_type_index - != (!plan.obligations.reachable_arrays.is_empty()).then_some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: array equality helper type placement drifted", - backend.file_range(), - )); - } - if plan.sections.array_eq_type_index.is_some() { - next_type_index += 1; - } - for instance in &plan.sections.direct_functions { - if plan.sections.direct_type_indices.get(instance).copied() != Some(next_type_index) { - return Err(Diagnostic::error( - "internal error: direct function type indices drifted from reachability", - backend.file_range(), - )); - } - next_type_index += 1; - } - for instance in &plan.sections.direct_functions { - if plan.sections.external_type_indices.get(instance).copied() != Some(next_type_index) { - return Err(Diagnostic::error( - "internal error: external function type indices drifted from reachability", - backend.file_range(), - )); - } - next_type_index += 1; - } - for callable in &plan.callables.signatures { - if plan.sections.callable_type_indices.get(&callable.signature).copied() - != Some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: callable type indices drifted from callable registry order", - backend.file_range(), - )); - } - next_type_index += 1; - } - if plan.sections.closure_destroy_type_index != next_type_index { - return Err(Diagnostic::error( - "internal error: closure destroy type index drifted from callable type section", - backend.file_range(), - )); - } - next_type_index += 1; - for optional in [ - plan.sections - .abi_alloc_type_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiAlloc)), - plan.sections - .abi_blob_release_type_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiBlobRelease)), - plan.sections - .abi_handle_retain_type_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiHandleRetain)), - plan.sections - .abi_handle_release_type_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiHandleRelease)), - ] { - if optional == Some(next_type_index) { - next_type_index += 1; - } else if optional.is_some() { - return Err(Diagnostic::error( - "internal error: late helper type placement drifted from the planned order", - backend.file_range(), - )); - } - } - for trampoline in &plan.callables.invoke_trampolines { - if plan.sections.invoke_trampoline_type_indices.get(&trampoline.signature_id).copied() - != Some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: invoke trampoline type indices drifted from callable \ - registry order", - backend.file_range(), - )); - } - next_type_index += 1; - } - for export in &plan.boundary.exports { - if plan.sections.export_wrapper_type_indices.get(&export.instance).copied() - != Some(next_type_index) - { - return Err(Diagnostic::error( - "internal error: export wrapper type indices drifted from boundary export \ - order", - backend.file_range(), - )); - } - next_type_index += 1; - } - - let mut next_function_index = 0u32; - for runtime in &plan.sections.runtime_imports { - if plan.sections.runtime_function_indices.get(runtime).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: runtime import indices drifted from section ordering", - backend.file_range(), - )); - } - next_function_index += 1; - } - for intrinsic in &plan.sections.stage_intrinsics { - if plan.sections.stage_function_indices.get(intrinsic).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: stage intrinsic indices drifted from section ordering", - backend.file_range(), - )); - } - next_function_index += 1; - } - for instance in &plan.sections.raw_imports { - if plan.sections.raw_import_function_indices.get(instance).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: raw import indices drifted from reachability order", - backend.file_range(), - )); - } - next_function_index += 1; - } - for helper in &plan.sections.builtin_helpers { - if plan.sections.helper_function_indices.get(helper).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: helper indices drifted from helper registry order", - backend.file_range(), - )); - } - next_function_index += 1; - } - for ty in &plan.obligations.reachable_nominals { - if plan.sections.nominal_destroyer_indices.get(&nominal_ty_bits(*ty)).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: nominal destroy helper indices drifted from obligations", - backend.file_range(), - )); - } - next_function_index += 1; - } - for ty in &plan.obligations.reachable_arrays { - if plan.sections.array_destroyer_indices.get(&array_ty_bits(*ty)).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: array destroy helper indices drifted from obligations", - backend.file_range(), - )); - } - next_function_index += 1; - } - for ty in &plan.obligations.reachable_nominals { - if plan.sections.nominal_eq_indices.get(&nominal_ty_bits(*ty)).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: nominal equality helper indices drifted from obligations", - backend.file_range(), - )); - } - next_function_index += 1; - } - for ty in &plan.obligations.reachable_arrays { - if plan.sections.array_eq_indices.get(&array_ty_bits(*ty)).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: array equality helper indices drifted from obligations", - backend.file_range(), - )); - } - next_function_index += 1; - } - for instance in &plan.reachability.functions { - if plan.sections.direct_function_indices.get(instance).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: direct function indices drifted from reachability", - backend.file_range(), - )); - } - next_function_index += 1; - } - for instance in &plan.reachability.functions { - if plan.sections.wrapper_function_indices.get(instance).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: function wrapper indices drifted from reachability", - backend.file_range(), - )); - } - next_function_index += 1; - } - for closure in &plan.reachability.closures { - if plan.sections.closure_function_indices.get(closure).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: closure function indices drifted from reachability", - backend.file_range(), - )); - } - next_function_index += 1; - } - for closure in &plan.sections.closure_destroyers { - if plan.sections.closure_destroy_indices.get(closure).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: closure destroyer indices drifted from closure ordering", - backend.file_range(), - )); - } - next_function_index += 1; - } - for optional in [ - plan.sections - .abi_alloc_function_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiAlloc)), - plan.sections - .abi_blob_release_function_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiBlobRelease)), - plan.sections - .abi_handle_retain_function_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiHandleRetain)), - plan.sections - .abi_handle_release_function_index - .filter(|_| plan.helpers.contains(HelperNeed::AbiHandleRelease)), - ] { - if optional == Some(next_function_index) { - next_function_index += 1; - } else if optional.is_some() { - return Err(Diagnostic::error( - "internal error: late helper function placement drifted from the planned order", - backend.file_range(), - )); - } - } - for (signature_id, function_index) in &plan.sections.invoke_trampoline_indices { - let expected_signature_id = plan - .callables - .invoke_trampolines - .get( - (next_function_index - - plan - .sections - .invoke_trampoline_indices - .first() - .map_or(next_function_index, |(_, first)| *first)) - as usize, - ) - .map(|trampoline| trampoline.signature_id); - if Some(*signature_id) != expected_signature_id - || *function_index != next_function_index - { - return Err(Diagnostic::error( - "internal error: invoke trampoline indices drifted from callable registry \ - order", - backend.file_range(), - )); - } - next_function_index += 1; - } - for export in &plan.boundary.exports { - if plan.sections.export_wrapper_indices.get(&export.instance).copied() - != Some(next_function_index) - { - return Err(Diagnostic::error( - "internal error: export wrapper indices drifted from boundary export order", - backend.file_range(), - )); - } - next_function_index += 1; - } - - let expected_table_elements = if backend.callable_lowering_strategy().uses_table_slots() { - plan.reachability - .functions - .iter() - .map(|instance| plan.sections.wrapper_function_indices[instance]) - .chain( - plan.reachability - .closures - .iter() - .map(|closure| plan.sections.closure_function_indices[closure]), - ) - .collect::>() - } else { - Vec::new() - }; - if plan.sections.table_elements != expected_table_elements { - return Err(Diagnostic::error( - "internal error: table element ordering drifted from wrapper and closure ordering", - backend.file_range(), - )); - } - let expected_table_slots = if backend.callable_lowering_strategy().uses_table_slots() { - plan.reachability - .functions - .iter() - .enumerate() - .map(|(index, instance)| { - ( - FunctionValueTarget::Function(instance.clone()), - u32::try_from(index).expect("table slot should fit u32"), - ) - }) - .chain(plan.reachability.closures.iter().enumerate().map(|(index, closure)| { - ( - FunctionValueTarget::Closure(closure.clone()), - u32::try_from(plan.reachability.functions.len() + index) - .expect("table slot should fit u32"), - ) - })) - .collect::>() - } else { - FxHashMap::default() - }; - if plan.sections.table_slots != expected_table_slots { - return Err(Diagnostic::error( - "internal error: table slot assignment drifted from reachable wrapper order", - backend.file_range(), - )); - } - let expected_closure_destroyer_pairs = - if backend.callable_lowering_strategy().uses_table_slots() { - plan.sections - .closure_destroyers - .iter() - .map(|closure| { - ( - plan.sections.table_slots - [&FunctionValueTarget::Closure(closure.clone())], - plan.sections.closure_destroy_indices[closure], - ) - }) - .collect::>() - } else { - Vec::new() - }; - if plan.sections.closure_destroyer_pairs != expected_closure_destroyer_pairs { - return Err(Diagnostic::error( - "internal error: closure destroyer table pairs drifted from closure destroyer \ - indices", - backend.file_range(), - )); - } - - let expected_exports = plan - .boundary - .aliases - .iter() - .map(|alias| { - ( - alias.alias.clone(), - SectionExportTarget::Func( - plan.sections.export_wrapper_indices[&alias.instance], - ), - ) - }) - .chain( - plan.sections.abi_alloc_function_index.into_iter().map(|index| { - ("mitki:abi/2/alloc".to_owned(), SectionExportTarget::Func(index)) - }), - ) - .chain(plan.sections.abi_blob_release_function_index.into_iter().map(|index| { - ("mitki:abi/2/blob_release".to_owned(), SectionExportTarget::Func(index)) - })) - .chain(plan.sections.abi_handle_retain_function_index.into_iter().map(|index| { - ("mitki:abi/2/handle_retain".to_owned(), SectionExportTarget::Func(index)) - })) - .chain(plan.sections.abi_handle_release_function_index.into_iter().map(|index| { - ("mitki:abi/2/handle_release".to_owned(), SectionExportTarget::Func(index)) - })) - .chain(plan.sections.invoke_trampoline_indices.iter().map(|(signature_id, index)| { - ( - format!("mitki:abi/2/invoke${}", signature_id.0), - SectionExportTarget::Func(*index), - ) - })) - .chain(std::iter::once(("memory".to_owned(), SectionExportTarget::Memory(0)))) - .collect::>(); - let planned_exports = plan - .sections - .exports - .iter() - .map(|export| (export.name.clone(), export.target.clone())) - .collect::>(); - if expected_exports != planned_exports - || plan.sections.memory_index != 0 - || plan.sections.stack_global_index != 0 - { - return Err(Diagnostic::error( - "internal error: export/global/memory section planning drifted from the backend \ - skeleton", - backend.file_range(), - )); - } - - if plan.memories - != [plan::MemoryPlan { memory_index: 0, export_name: Some("memory".to_owned()) }] - { - return Err(Diagnostic::error( - "internal error: memory plan drifted from the known module memory skeleton", - backend.file_range(), - )); - } - if !plan.tables.is_empty() || !plan.globals.is_empty() || !plan.data_segments.is_empty() { - return Err(Diagnostic::error( - "internal error: Step 1 shadow module skeleton should not eagerly populate \ - tables/globals/data segments", - backend.file_range(), - )); - } - - let expected_logical_names = plan - .function_instances - .iter() - .map(|function| (function.id, function.logical_name.clone())) - .collect::>(); - if expected_logical_names != plan.names.logical_functions { - return Err(Diagnostic::error( - "internal error: name plan logical functions drifted from function instances", - backend.file_range(), - )); - } - let expected_typed_exports = plan - .boundary - .aliases - .iter() - .map(|alias| (alias.alias.clone(), alias.function_id)) - .collect::>(); - if expected_typed_exports != plan.names.typed_exports { - return Err(Diagnostic::error( - "internal error: name plan typed exports drifted from export plan", - backend.file_range(), - )); - } - let expected_helper_exports = plan.helpers.helper_export_names(); - if expected_helper_exports != plan.names.helper_exports { - return Err(Diagnostic::error( - "internal error: name plan helper exports drifted from helper needs", - backend.file_range(), - )); - } - - Ok(()) - } -} diff --git a/crates/mitki-backend-wasm/tests/abi_surface.rs b/crates/mitki-backend-wasm/tests/abi_surface.rs deleted file mode 100644 index adb4aeb..0000000 --- a/crates/mitki-backend-wasm/tests/abi_surface.rs +++ /dev/null @@ -1,394 +0,0 @@ -mod common; - -use common::{ - assert_func_signature, assert_metadata_requires_handles, assert_metadata_version_bump_rejected, - assert_shared_abi_contract, compile_err, compile_ok, expect_diagnostic, export_instance, - import_instance, string_value, unsupported_imports, -}; -use mitki_abi::AbiTypeKind; -use mitki_wasm_runtime::describe_module_abi; - -#[test] -fn canonical_pipeline_emits_wasm_matching_shared_abi_contract() { - let bytes = compile_ok( - r#" -import "env" fun round_trip[T](value: T): T; -import instance round_trip[int]; - -export fun main(): int { - round_trip(42) -} -"#, - ); - assert_shared_abi_contract(&bytes); -} - -#[test] -fn describe_module_abi_exposes_metadata_and_generated_typed_exports() { - let bytes = compile_ok( - r#" -export fun answer(): int { - 42 -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let instance = export_instance(metadata, "answer"); - let typed_export = instance - .wasm_field_name - .map(|id| string_value(metadata, id).to_owned()) - .expect("expected generated typed export name"); - - assert_eq!(instance.domain, mitki_abi::ExecutionDomain::Runtime); - assert!(typed_export.starts_with("mitki:typed/2/f$")); - assert!(abi.exports.iter().any(|export| export.name == typed_export)); - assert!(!abi.exports.iter().any(|export| export.name == "answer")); -} - -#[test] -fn typed_wasm_signatures_follow_v2_transport_refs() { - let bytes = compile_ok( - r#" -import "env" fun round_trip(pair: (int, int)): (int, int); - -export fun make_pair(): (int, int) { - round_trip((20, 22)) -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let export_name = export_instance(metadata, "make_pair") - .wasm_field_name - .map(|id| string_value(metadata, id).to_owned()) - .expect("expected generated typed export name"); - let import = import_instance(metadata, "env", "round_trip"); - let import_field = - string_value(metadata, import.wasm_field_name.expect("expected import field")); - - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, &bytes).expect("valid module"); - let import_ty = module - .imports() - .find(|entry| entry.module() == "env" && entry.name() == import_field) - .map(|entry| entry.ty()) - .expect("expected typed import"); - assert_func_signature(&import_ty, &[wasmtime::ValType::I32], &[wasmtime::ValType::I32]); - - let export_ty = module - .exports() - .find(|entry| entry.name() == export_name) - .map(|entry| entry.ty()) - .expect("expected generated typed export"); - assert_func_signature(&export_ty, &[], &[wasmtime::ValType::I32]); -} - -#[test] -fn collect_unsupported_imports_reports_unconfigured_host_imports() { - let bytes = compile_ok( - r#" -import "env" fun host(): int; - -export fun answer(): int { - host() -} -"#, - ); - - assert_eq!(unsupported_imports(&bytes), ["env::host"]); -} - -#[test] -fn describe_module_abi_rejects_newer_metadata_encodings() { - let bytes = compile_ok( - r#" -export fun answer(): int { - 42 -} -"#, - ); - - assert_metadata_version_bump_rejected(&bytes); -} - -#[test] -fn wasm_boundary_supports_top_level_function_typed_parameters() { - let bytes = compile_ok( - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - ); - - assert_metadata_requires_handles(&bytes); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let export_name = export_instance(metadata, "id") - .wasm_field_name - .map(|id| string_value(metadata, id).to_owned()) - .expect("expected generated typed export name"); - let import = import_instance(metadata, "env", "round_trip"); - let import_field = - string_value(metadata, import.wasm_field_name.expect("expected import field")); - - let module = - wasmtime::Module::from_binary(&wasmtime::Engine::default(), &bytes).expect("valid module"); - let import_ty = module - .imports() - .find(|entry| entry.module() == "env" && entry.name() == import_field) - .map(|entry| entry.ty()) - .expect("expected typed function import"); - assert_func_signature(&import_ty, &[wasmtime::ValType::I32], &[wasmtime::ValType::I32]); - - let export_ty = module - .exports() - .find(|entry| entry.name() == export_name) - .map(|entry| entry.ty()) - .expect("expected generated typed export"); - assert_func_signature(&export_ty, &[wasmtime::ValType::I32], &[wasmtime::ValType::I32]); -} - -#[test] -fn wasm_boundary_supports_structs_with_function_fields() { - let bytes = compile_ok( - r#" -struct Wrapper { - f: fun(int) -> int, -} - -fun add_one(x: int): int { - x + 1 -} - -export fun make_wrapper(): Wrapper { - Wrapper { f: add_one } -} - -export fun echo_wrapper(value: Wrapper): Wrapper { - value -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let make_instance = export_instance(metadata, "make_wrapper"); - let make_signature = &metadata.signatures[make_instance.signature.0 as usize]; - let wrapper_ty = make_signature.result.semantic_type; - let AbiTypeKind::Struct { fields, .. } = &metadata.types[wrapper_ty.0 as usize].kind else { - panic!("expected Wrapper semantic type"); - }; - let handle_ty = fields.first().expect("expected Wrapper field").ty; - assert!(matches!(metadata.types[handle_ty.0 as usize].kind, AbiTypeKind::Function { .. })); - assert_eq!(make_signature.result.transport_class, mitki_abi::TransportClass::CanonicalValue); -} - -#[test] -fn wasm_boundary_rejects_recursive_boundary_types() { - let diagnostics = compile_err( - r#" -enum List { - Nil, - Cons(int, List), -} - -export fun echo(xs: List): List { - xs -} -"#, - ); - - assert!( - diagnostics.iter().any(|message| { - message.contains("recursive types are not supported yet") - || message - .contains("typed Wasm imports/exports do not allow non-copy types like `List`") - }), - "expected recursive boundary rejection, got {diagnostics:#?}" - ); -} - -#[test] -fn wasm_boundary_supports_union_boundary_types() { - let bytes = compile_ok( - r#" -export fun echo(value: int | str): int | str { - value -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let instance = export_instance(metadata, "echo"); - let signature = &metadata.signatures[instance.signature.0 as usize]; - let union_ty = signature.params[0].semantic_type; - let AbiTypeKind::Union { members } = &metadata.types[union_ty.0 as usize].kind else { - panic!("expected union semantic type"); - }; - assert_eq!(signature.params[0].transport_class, mitki_abi::TransportClass::CanonicalValue); - assert_eq!(signature.result.transport_class, mitki_abi::TransportClass::CanonicalValue); - assert_eq!(members.len(), 2); -} - -#[test] -fn wasm_boundary_round_trips_erased_intersection_boundary_types() { - let bytes = compile_ok( - r#" -export fun echo(value: int & int): int & int { - value -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let instance = export_instance(metadata, "echo"); - let signature = &metadata.signatures[instance.signature.0 as usize]; - assert_eq!(signature.params[0].transport_class, mitki_abi::TransportClass::Immediate); - assert_eq!(signature.result.transport_class, mitki_abi::TransportClass::Immediate); -} - -#[test] -fn wasm_exports_reject_generic_functions() { - let diagnostics = compile_err( - r#" -export fun id[T](value: T): T { - value -} -"#, - ); - expect_diagnostic(&diagnostics, "Wasm imports and exports do not support generic functions"); -} - -#[test] -fn wasm_imports_require_explicit_generic_instances() { - let diagnostics = compile_err( - r#" -import "env" fun id[T](value: T): T; - -export fun main(value: int): int { - id(value) -} -"#, - ); - expect_diagnostic(&diagnostics, "`import instance` declaration is required"); -} - -#[test] -fn wasm_imports_with_bodies_fail_boundary_legality() { - let diagnostics = compile_err( - r#" -import "env" fun host(): int { - 42 -} -"#, - ); - expect_diagnostic(&diagnostics, "Imported Wasm functions cannot have a body"); -} - -#[test] -fn safe_wasm_exports_reject_pointer_types() { - let diagnostics = compile_err( - r#" -unsafe fun leak(): *mut u32 { - val out: *mut u32 = stack_alloc(1) - out -} - -export fun main(): *mut u32 { - unsafe { leak() } -} -"#, - ); - expect_diagnostic( - &diagnostics, - "typed Wasm imports/exports do not allow pointer types like `*mut u32`", - ); -} - -#[test] -fn raw_wasi_fd_read_helpers_can_build_iovecs_in_linear_memory() { - let bytes = compile_ok( - r#" -extern struct MutByteSlice { - ptr: *mut u8, - len: u32, -} - -extern struct WasiIovec { - buf: *mut u8, - buf_len: u32, -} - -import "wasi_snapshot_preview1" unsafe fun fd_read( - fd: u32, - iovs: *const WasiIovec, - iovs_len: u32, - nread: *mut u32, -): u16; - -unsafe fun read_once(fd: u32): u16 { - val chunk: [u8] = [0, 0, 0, 0] - val bytes: MutByteSlice = array_mut_bytes(chunk) - val iov_out: *mut WasiIovec = stack_alloc(1) - val nread_out: *mut u32 = stack_alloc(1) - - ptr_write(nread_out, 0); - ptr_write(iov_out, WasiIovec { buf: bytes.ptr, buf_len: bytes.len }); - fd_read(fd, iov_out, 1, nread_out) -} - -export fun main(): int { - unsafe { - if read_once(0) == 0 { - 1 - } else { - 0 - } - } -} -"#, - ); - - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, &bytes).expect("valid module"); - let import_ty = module - .imports() - .find(|import| import.module() == "wasi_snapshot_preview1" && import.name() == "fd_read") - .map(|import| import.ty()) - .expect("expected raw WASI fd_read import"); - assert_func_signature( - &import_ty, - &[ - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - ], - &[wasmtime::ValType::I32], - ); -} - -#[test] -fn unsafe_operations_require_unsafe_context() { - let diagnostics = compile_err( - r#" -import "wasi_snapshot_preview1" unsafe fun fd_close(fd: u32): u16; - -export fun main(): int { - fd_close(3) - 0 -} -"#, - ); - - expect_diagnostic(&diagnostics, "unsafe operation requires an `unsafe` block or `unsafe fun`"); -} diff --git a/crates/mitki-backend-wasm/tests/common/mod.rs b/crates/mitki-backend-wasm/tests/common/mod.rs deleted file mode 100644 index 8bb3754..0000000 --- a/crates/mitki-backend-wasm/tests/common/mod.rs +++ /dev/null @@ -1,331 +0,0 @@ -#![allow(dead_code, unused_imports)] - -use std::borrow::Cow; -use std::sync::{Arc, Mutex}; - -use mitki_abi::{ - AbiScalar, AbiValue, ArrayElements, CanonicalGraph, CanonicalNode, ExecutionDomain, - LinkageKind, METADATA_ENCODING_VERSION, REQUIRED_FEATURE_HANDLES, SemanticTypeGraph, ValueRef, - decode_semantic_type_graph, encode_semantic_type_graph, validate_wasm_module_contract, -}; -use mitki_backend_wasm::CompileOptions; -use mitki_comptime_wasm::{compile_file_to_wasm, compile_file_to_wasm_with_options}; -use mitki_db::RootDatabase; -use mitki_inputs::File; -use mitki_wasm_runtime::{ - RunConfig, WasmRuntime, collect_unsupported_imports_with_config, describe_module_abi, -}; - -pub(crate) fn compile_ok(fixture: &str) -> Vec { - let db = RootDatabase::default(); - let file = File::new(&db, "wasm_backend_ok.mitki".into(), fixture.to_owned()); - let bytes = compile_file_to_wasm(&db, file).unwrap_or_else(|diagnostics| { - let messages = diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>(); - panic!("expected success, got diagnostics: {messages:#?}"); - }); - wasmparser::Validator::new().validate_all(&bytes).expect("emitted Wasm should validate"); - bytes -} - -pub(crate) fn compile_ok_with_options(fixture: &str, options: CompileOptions) -> Vec { - let db = RootDatabase::default(); - let file = File::new(&db, "wasm_backend_ok_opts.mitki".into(), fixture.to_owned()); - let bytes = - compile_file_to_wasm_with_options(&db, file, options).unwrap_or_else(|diagnostics| { - let messages = - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>(); - panic!("expected success, got diagnostics: {messages:#?}"); - }); - wasmparser::Validator::new().validate_all(&bytes).expect("emitted Wasm should validate"); - bytes -} - -pub(crate) fn compile_err(fixture: &str) -> Vec { - let db = RootDatabase::default(); - let file = File::new(&db, "wasm_backend_err.mitki".into(), fixture.to_owned()); - let diagnostics = compile_file_to_wasm(&db, file).expect_err("expected compilation failure"); - diagnostics.iter().map(|diag| diag.message().to_owned()).collect() -} - -pub(crate) fn normalized_wat(bytes: &[u8]) -> String { - wasmprinter::print_bytes(bytes) - .expect("WAT should print") - .lines() - .map(str::trim) - .filter(|line| !line.is_empty()) - .collect::>() - .join("\n") -} - -pub(crate) fn bump_module_abi_version(bytes: &[u8], version: u32) -> Vec { - let mut module = wasm_encoder::Module::new(); - let mut rewritten = false; - - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload.expect("valid wasm payload"); - match payload { - wasmparser::Payload::CustomSection(reader) if reader.name() == "mitki.abi.v2" => { - let mut graph = - decode_semantic_type_graph(reader.data()).expect("valid ABI metadata"); - graph.encoding_version = - u16::try_from(version).expect("test metadata version should fit in u16"); - let encoded = encode_semantic_type_graph(&graph).expect("encoded ABI metadata"); - module.section(&wasm_encoder::CustomSection { - name: reader.name().into(), - data: Cow::Owned(encoded), - }); - rewritten = true; - } - other => { - if let Some((id, range)) = other.as_section() { - module.section(&wasm_encoder::RawSection { id, data: &bytes[range] }); - } - } - } - } - - assert!(rewritten, "expected ABI metadata section to exist"); - module.finish() -} - -pub(crate) fn string_value(graph: &SemanticTypeGraph, id: mitki_abi::StringId) -> &str { - graph.strings.get(id.0 as usize).map(String::as_str).expect("valid string id") -} - -pub(crate) fn symbol_name(graph: &SemanticTypeGraph, id: mitki_abi::SymbolId) -> &str { - let string_id = graph.nominal_symbols.get(id.0 as usize).copied().expect("valid symbol id"); - string_value(graph, string_id) -} - -pub(crate) fn export_instance<'a>( - graph: &'a SemanticTypeGraph, - logical_name: &str, -) -> &'a mitki_abi::FunctionInstance { - graph - .function_instances - .iter() - .find(|instance| { - instance.linkage == LinkageKind::WasmExport - && symbol_name(graph, instance.logical_symbol) == logical_name - }) - .expect("expected export instance") -} - -pub(crate) fn import_instance<'a>( - graph: &'a SemanticTypeGraph, - module_name: &str, - logical_name: &str, -) -> &'a mitki_abi::FunctionInstance { - graph - .function_instances - .iter() - .find(|instance| { - instance.linkage == LinkageKind::WasmImport - && instance - .wasm_module_name - .is_some_and(|id| string_value(graph, id) == module_name) - && symbol_name(graph, instance.logical_symbol) == logical_name - }) - .expect("expected import instance") -} - -pub(crate) fn expect_diagnostic(messages: &[String], needle: &str) { - assert!( - messages.iter().any(|message| message.contains(needle)), - "expected diagnostic containing `{needle}`, got {messages:#?}" - ); -} - -pub(crate) fn assert_func_signature( - extern_ty: &wasmtime::ExternType, - params: &[wasmtime::ValType], - results: &[wasmtime::ValType], -) { - let wasmtime::ExternType::Func(func_ty) = extern_ty else { - panic!("expected function extern type, got {extern_ty:?}"); - }; - let actual_params = func_ty.params().map(|ty| val_type_name(&ty)).collect::>(); - let expected_params = params.iter().map(val_type_name).collect::>(); - let actual_results = func_ty.results().map(|ty| val_type_name(&ty)).collect::>(); - let expected_results = results.iter().map(val_type_name).collect::>(); - assert_eq!(actual_params, expected_params); - assert_eq!(actual_results, expected_results); -} - -fn val_type_name(ty: &wasmtime::ValType) -> &'static str { - match ty { - wasmtime::ValType::I32 => "i32", - wasmtime::ValType::I64 => "i64", - wasmtime::ValType::F32 => "f32", - wasmtime::ValType::F64 => "f64", - wasmtime::ValType::V128 => "v128", - wasmtime::ValType::Ref(_) => "ref", - } -} - -pub(crate) fn expect_immediate_i32(value: &AbiValue, expected: i64) { - assert_eq!( - value, - &AbiValue::Immediate(AbiScalar::Int { signed: true, bits: 32, value: expected }) - ); -} - -pub(crate) fn decode_string_array(value: &AbiValue) -> Vec { - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical value, got {value:?}"); - }; - decode_string_array_ref(graph, &graph.root) -} - -pub(crate) fn decode_string(value: &AbiValue) -> String { - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical value, got {value:?}"); - }; - let ValueRef::NodeRef(root) = graph.root else { - panic!("expected node root, got {:?}", graph.root); - }; - let CanonicalNode::String { value, .. } = - graph.nodes.get(root.0 as usize).expect("string root node") - else { - panic!("expected canonical string"); - }; - value.clone() -} - -pub(crate) fn decode_string_array_ref(graph: &CanonicalGraph, root: &ValueRef) -> Vec { - assert!(graph.handles.is_empty(), "baseline canonical array should not carry handles"); - let ValueRef::NodeRef(root) = root else { - panic!("expected node root, got {root:?}"); - }; - let CanonicalNode::Array { elements: ArrayElements::Values(items), .. } = - graph.nodes.get(root.0 as usize).expect("array root node") - else { - panic!("expected canonical string array"); - }; - - items - .iter() - .map(|value_ref| { - let ValueRef::NodeRef(node_id) = value_ref else { - panic!("expected string node ref, got {value_ref:?}"); - }; - let CanonicalNode::String { value, .. } = - graph.nodes.get(node_id.0 as usize).expect("string node") - else { - panic!("expected canonical string node"); - }; - value.clone() - }) - .collect() -} - -pub(crate) fn decode_i32_array(value: &AbiValue) -> Vec { - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical value, got {value:?}"); - }; - assert!(graph.handles.is_empty(), "baseline canonical array should not carry handles"); - let ValueRef::NodeRef(root) = graph.root else { - panic!("expected node root, got {:?}", graph.root); - }; - let CanonicalNode::Array { elements, .. } = - graph.nodes.get(root.0 as usize).expect("array root node") - else { - panic!("expected canonical array"); - }; - match elements { - ArrayElements::PackedScalars { bytes, len, .. } => { - let expected_len = usize::try_from(*len).expect("len fits usize"); - bytes - .chunks_exact(4) - .map(|chunk| i32::from_le_bytes(chunk.try_into().expect("packed i32 chunk"))) - .take(expected_len) - .collect() - } - ArrayElements::Values(values) => values - .iter() - .map(|value| match value { - ValueRef::InlineScalar(AbiScalar::Int { value, .. }) => *value as i32, - other => panic!("expected inline i32 scalar, got {other:?}"), - }) - .collect(), - } -} - -pub(crate) fn decode_i32_pair(value: &AbiValue) -> [i32; 2] { - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical value, got {value:?}"); - }; - let ValueRef::NodeRef(root) = graph.root else { - panic!("expected node root, got {:?}", graph.root); - }; - let CanonicalNode::Tuple { fields, .. } = - graph.nodes.get(root.0 as usize).expect("tuple root node") - else { - panic!("expected canonical tuple"); - }; - assert_eq!(fields.len(), 2, "expected 2-tuple"); - let mut values = [0; 2]; - for (slot, field) in values.iter_mut().zip(fields.iter()) { - *slot = match field { - ValueRef::InlineScalar(AbiScalar::Int { value, .. }) => *value as i32, - other => panic!("expected inline i32 scalar, got {other:?}"), - }; - } - values -} - -pub(crate) fn decode_some_string_array(value: &AbiValue) -> Vec { - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical value, got {value:?}"); - }; - let ValueRef::NodeRef(root) = graph.root else { - panic!("expected node root, got {:?}", graph.root); - }; - let CanonicalNode::Enum { variant_index, fields, .. } = - graph.nodes.get(root.0 as usize).expect("enum root node") - else { - panic!("expected canonical enum"); - }; - assert_eq!(*variant_index, 0, "expected first enum variant"); - assert_eq!(fields.len(), 1, "expected single enum payload"); - decode_string_array_ref(graph, &fields[0]) -} - -pub(crate) fn compile_runtime_with_host_callback( - bytes: &[u8], - seen: &Arc>>, -) -> WasmRuntime { - let seen_clone = Arc::clone(seen); - WasmRuntime::builder() - .host_function("env", "round_trip", move |args| { - assert_eq!(args.len(), 1, "expected one canonical array argument"); - *seen_clone.lock().expect("host arg lock") = Some(args[0].clone()); - Ok(Some(args[0].clone())) - }) - .instantiate(bytes) - .expect("runtime should instantiate with typed host callback") -} - -pub(crate) fn assert_shared_abi_contract(bytes: &[u8]) { - let abi = describe_module_abi(bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - validate_wasm_module_contract(bytes, metadata) - .expect("emitted Wasm should agree with the shared ABI contract"); -} - -pub(crate) fn assert_metadata_requires_handles(bytes: &[u8]) { - let abi = describe_module_abi(bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - assert_ne!(metadata.required_features & REQUIRED_FEATURE_HANDLES, 0); -} - -pub(crate) fn assert_metadata_version_bump_rejected(bytes: &[u8]) { - let bumped = bump_module_abi_version(bytes, u32::from(METADATA_ENCODING_VERSION) + 1); - let error = describe_module_abi(&bumped).expect_err("newer metadata encoding should reject"); - assert!(error.to_string().contains("metadata encoding version")); -} - -pub(crate) fn unsupported_imports(bytes: &[u8]) -> Vec { - collect_unsupported_imports_with_config(bytes, RunConfig::without_wasi()) - .expect("unsupported import collection should succeed") -} diff --git a/crates/mitki-backend-wasm/tests/comptime_behavior.rs b/crates/mitki-backend-wasm/tests/comptime_behavior.rs deleted file mode 100644 index d9584b6..0000000 --- a/crates/mitki-backend-wasm/tests/comptime_behavior.rs +++ /dev/null @@ -1,86 +0,0 @@ -mod common; - -use common::{compile_err, compile_ok, expect_diagnostic, expect_immediate_i32}; -use mitki_wasm_runtime::invoke_export; - -#[test] -fn comptime_evaluates_direct_values_into_runtime_exports() { - let bytes = compile_ok( - r#" -comptime fun build(): int { - 42 -} - -export fun main(): int { - comptime(build()) -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn comptime_supports_nested_evaluation() { - let bytes = compile_ok( - r#" -comptime fun base(): int { - 40 -} - -comptime fun build(): int { - comptime(base()) + 2 -} - -export fun main(): int { - comptime(build()) -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn comptime_rejects_non_lowerable_results() { - let diagnostics = compile_err( - r#" -fun add_one(x: int): int { - x + 1 -} - -comptime fun build(): fun(int) -> int { - add_one -} - -export fun main(): int { - comptime(build()) - 42 -} -"#, - ); - - expect_diagnostic( - &diagnostics, - "comptime requires the target function to return a runtime-lowerable value", - ); -} - -#[test] -fn comptime_rejects_runtime_imports_in_stage_code() { - let diagnostics = compile_err( - r#" -comptime fun build() { - std::io::print_int(7) -} - -export fun main() { - comptime(build()) -} -"#, - ); - - expect_diagnostic(&diagnostics, "comptime functions cannot call imported Wasm functions"); -} diff --git a/crates/mitki-backend-wasm/tests/normalized_wat.rs b/crates/mitki-backend-wasm/tests/normalized_wat.rs deleted file mode 100644 index 879a7fb..0000000 --- a/crates/mitki-backend-wasm/tests/normalized_wat.rs +++ /dev/null @@ -1,184 +0,0 @@ -mod common; - -use common::{ - assert_func_signature, compile_err, compile_ok, compile_ok_with_options, expect_diagnostic, - normalized_wat, -}; -use mitki_backend_wasm::CompileOptions; - -#[test] -fn wasm_codegen_is_deterministic_across_repeated_compilation() { - let fixture = r#" -unsafe fun scalar_round_trip(): int { - val out: *mut u32 = stack_alloc(1) - ptr_write(out, 42); - if ptr_read(out) == 42 { - 1 - } else { - 0 - } -} - -export fun main(): int { - unsafe { - scalar_round_trip() - } -} -"#; - - let first = compile_ok(fixture); - let second = compile_ok(fixture); - assert_eq!(first, second, "expected emitted module bytes to stay deterministic"); -} - -#[test] -fn default_backend_matches_explicit_canonical_mir_pipeline() { - let fixture = r#" -fun choose(flag: bool): int { - if flag { 42 } else { 0 } -} - -export fun main(): int { - choose(true) -} -"#; - - let default_bytes = compile_ok(fixture); - let explicit_bytes = compile_ok_with_options(fixture, CompileOptions); - assert_eq!(default_bytes, explicit_bytes); -} - -#[test] -fn canonical_pipeline_emits_structured_loops() { - let bytes = compile_ok( - r#" -export fun main() { - loop { - () - } -} -"#, - ); - let wat = normalized_wat(&bytes); - assert!(wat.contains("loop"), "WAT missing expected loop construct:\n{wat}"); -} - -#[test] -fn canonical_pipeline_handles_closure_bodies_and_indirect_calls() { - let bytes = compile_ok( - r#" -fun apply(f: fun(int) -> int, x: int): int { - f(x) -} - -export fun main(): int { - val offset: int = 20 - val add: fun(int) -> int = { value in value + offset } - apply(add, 22) -} -"#, - ); - let wat = normalized_wat(&bytes); - assert!(wat.contains("call_indirect"), "WAT missing indirect call:\n{wat}"); -} - -#[test] -fn canonical_pipeline_handles_unsafe_pointer_ops() { - let bytes = compile_ok( - r#" -unsafe fun scalar_round_trip(): int { - val out: *mut u32 = stack_alloc(1) - ptr_write(out, 42); - if ptr_read(out) == 42 { - 1 - } else { - 0 - } -} - -export fun main(): int { - unsafe { - scalar_round_trip() - } -} -"#, - ); - let wat = normalized_wat(&bytes); - assert!(wat.contains("i32.store"), "WAT missing expected store:\n{wat}"); - assert!(wat.contains("i32.load"), "WAT missing expected load:\n{wat}"); -} - -#[test] -fn raw_wasi_imports_lower_pointers_to_i32_and_rights_to_i64() { - let bytes = compile_ok( - r#" -extern struct ByteSlice { - ptr: *const u8, - len: u32, -} - -import "wasi_snapshot_preview1" unsafe fun path_open( - dirfd: u32, - dirflags: u32, - path_ptr: *const u8, - path_len: u32, - oflags: u16, - rights_base: u64, - rights_inheriting: u64, - fdflags: u16, - opened_fd: *mut u32, -): u16; - -unsafe fun open_readme_errno(): u16 { - val path: ByteSlice = str_bytes("README.md") - val out: *mut u32 = stack_alloc(1) - path_open(3, 0, path.ptr, path.len, 0, 2, 0, 0, out) -} - -export fun main(): int { - unsafe { - if open_readme_errno() == 0 { - 1 - } else { - 0 - } - } -} -"#, - ); - - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, &bytes).expect("valid module"); - let import_ty = module - .imports() - .find(|import| import.module() == "wasi_snapshot_preview1" && import.name() == "path_open") - .map(|import| import.ty()) - .expect("expected raw WASI import"); - assert_func_signature( - &import_ty, - &[ - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - wasmtime::ValType::I64, - wasmtime::ValType::I64, - wasmtime::ValType::I32, - wasmtime::ValType::I32, - ], - &[wasmtime::ValType::I32], - ); -} - -#[test] -fn canonical_pipeline_preserves_compile_failure_diagnostics() { - let errors = compile_err( - r#" -export fun id[T](value: T): T { - value -} -"#, - ); - expect_diagnostic(&errors, "Wasm imports and exports do not support generic functions"); -} diff --git a/crates/mitki-backend-wasm/tests/runtime_behavior.rs b/crates/mitki-backend-wasm/tests/runtime_behavior.rs deleted file mode 100644 index 08d8191..0000000 --- a/crates/mitki-backend-wasm/tests/runtime_behavior.rs +++ /dev/null @@ -1,694 +0,0 @@ -mod common; - -use std::sync::{Arc, Mutex}; - -use common::{ - compile_ok, compile_runtime_with_host_callback, decode_i32_array, decode_i32_pair, - decode_some_string_array, decode_string, decode_string_array, expect_immediate_i32, - import_instance, symbol_name, -}; -use mitki_abi::{AbiScalar, AbiTypeKind, AbiValue, LinkageKind}; -use mitki_wasm_runtime::{WasmRuntime, describe_module_abi, invoke_export, invoke_export_instance}; - -#[test] -fn invoke_export_resolves_logical_names_through_metadata() { - let bytes = compile_ok( - r#" -export fun answer(): int { - 42 -} -"#, - ); - - let output = invoke_export(&bytes, "answer", &[]).expect("logical export should execute"); - assert!(output.stdout.is_empty(), "expected no runtime stdout"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let typed_export = metadata - .function_instances - .iter() - .find(|instance| { - instance.linkage == LinkageKind::WasmExport - && symbol_name(metadata, instance.logical_symbol) == "answer" - }) - .and_then(|instance| instance.wasm_field_name) - .map(|id| common::string_value(metadata, id).to_owned()) - .expect("expected generated typed export name"); - - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, &bytes).expect("valid module"); - let mut store = wasmtime::Store::new(&engine, ()); - let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("instance"); - assert!(instance.get_func(&mut store, "answer").is_none()); - assert!(instance.get_func(&mut store, typed_export.as_str()).is_some()); -} - -#[test] -fn invoke_export_returns_canonical_structured_values() { - let bytes = compile_ok( - r#" -export fun words(): [str] { - ["a", "b"] -} -"#, - ); - - let output = invoke_export(&bytes, "words", &[]).expect("export should execute"); - assert!(output.stdout.is_empty(), "expected no runtime stdout"); - let result = output.result.expect("expected canonical array result"); - assert_eq!(decode_string_array(&result), ["a", "b"]); -} - -#[test] -fn invoke_export_returns_owned_string_from_raw_bytes() { - let bytes = compile_ok( - r#" -use std::str as strings; - -export fun main(): str { - val bytes = str_bytes("hello") - unsafe { - strings::from_raw_parts_unchecked(bytes.ptr, bytes.len) - } -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - let result = output.result.expect("expected string result"); - assert_eq!(decode_string(&result), "hello"); -} - -#[test] -fn invoke_export_lowers_if_block_branches() { - let bytes = compile_ok( - r#" -export fun main(): str { - val found = true - if found { "yes" } else { "no" } -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - let result = output.result.expect("expected string result"); - assert_eq!(decode_string(&result), "yes"); -} - -#[test] -fn invoke_export_reads_wasi_environment_variable_via_std_env_var() { - let expected = std::env::var("PATH").expect("PATH should exist for inherited WASI env"); - let bytes = compile_ok( - r#" -use std::env; - -export fun main(): str { - match env::var("PATH") { - (found, value) => if found { value } else { "" }, - } -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - let result = output.result.expect("expected string result"); - assert_eq!(decode_string(&result), expected); -} - -#[test] -fn std_io_print_str_uses_wasi_fd_write_instead_of_mitki_print_str_import() { - let bytes = compile_ok( - r#" -export fun main() { - std::io::print_str("hello"); -} -"#, - ); - - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, &bytes).expect("valid module"); - let imports = module - .imports() - .map(|import| (import.module().to_owned(), import.name().to_owned())) - .collect::>(); - - assert!( - imports - .iter() - .any(|(module, name)| module == "wasi_snapshot_preview1" && name == "fd_write"), - "expected wasi fd_write import, got {imports:?}" - ); - assert!( - !imports.iter().any(|(module, name)| module == "mitki" && name == "print_str"), - "std::io::print_str should not use mitki print_str import, got {imports:?}" - ); -} - -#[test] -fn invoke_export_returns_canonical_nested_enum_values() { - let bytes = compile_ok( - r#" -enum Words { - Some([str]), - Empty, -} - -export fun words(): Words { - Words.Some(["a", "b"]) -} -"#, - ); - - let output = invoke_export(&bytes, "words", &[]).expect("export should execute"); - let result = output.result.expect("expected canonical enum result"); - assert_eq!(decode_some_string_array(&result), ["a", "b"]); -} - -#[test] -fn host_function_callbacks_use_abivalue_for_canonical_round_trips() { - let bytes = compile_ok( - r#" -import "env" fun round_trip(xs: [int]): [int]; - -export fun main(): [int] { - round_trip([20, 22]) -} -"#, - ); - - let seen = Arc::new(Mutex::new(None)); - let mut runtime = compile_runtime_with_host_callback(&bytes, &seen); - - let output = runtime.invoke_export("main", &[]).expect("main should execute"); - assert!(output.stdout.is_empty(), "expected no runtime stdout"); - - let received = - seen.lock().expect("host arg lock").clone().expect("host arg should be captured"); - assert_eq!(decode_i32_array(&received), [20, 22]); - assert_eq!(decode_i32_array(output.result.as_ref().expect("expected result")), [20, 22]); -} - -#[test] -fn invoke_export_preserves_tuple_fields() { - let bytes = compile_ok( - r#" -export fun make_pair(): (int, int) { - (20, 22) -} -"#, - ); - - let output = invoke_export(&bytes, "make_pair", &[]).expect("export should execute"); - assert_eq!(decode_i32_pair(output.result.as_ref().expect("expected tuple result")), [20, 22]); -} - -#[test] -fn wasm_runtime_supports_internal_union_values() { - let bytes = compile_ok( - r#" -export fun main() { - val id = if true { 42 } else { true }; -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - assert!(output.stdout.is_empty(), "expected no runtime stdout"); - assert!(output.result.is_none(), "unit export should not produce a result"); -} - -#[test] -fn invoke_export_instance_disambiguates_monomorphized_exports() { - let bytes = compile_ok( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[bool]; -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let export_instances = metadata - .function_instances - .iter() - .filter(|instance| { - instance.linkage == LinkageKind::WasmExport - && symbol_name(metadata, instance.logical_symbol) == "id" - }) - .collect::>(); - assert_eq!(export_instances.len(), 2, "expected two export instances"); - - let int_instance = export_instances - .iter() - .find(|instance| { - matches!(metadata.types[instance.type_args[0].0 as usize].kind, AbiTypeKind::Int { .. }) - }) - .expect("expected int export instance"); - let bool_instance = export_instances - .iter() - .find(|instance| { - matches!(metadata.types[instance.type_args[0].0 as usize].kind, AbiTypeKind::Bool) - }) - .expect("expected bool export instance"); - - let int_output = invoke_export_instance( - &bytes, - int_instance.id, - &[AbiValue::Immediate(AbiScalar::Int { signed: true, bits: 32, value: 42 })], - ) - .expect("int instance should execute"); - expect_immediate_i32(int_output.result.as_ref().expect("expected int result"), 42); - - let bool_output = invoke_export_instance( - &bytes, - bool_instance.id, - &[AbiValue::Immediate(AbiScalar::Bool(true))], - ) - .expect("bool instance should execute"); - assert_eq!(bool_output.result, Some(AbiValue::Immediate(AbiScalar::Bool(true)))); - - let ambiguous = invoke_export(&bytes, "id", &[AbiValue::Immediate(AbiScalar::Bool(true))]) - .expect_err("logical-name invocation should reject ambiguous generic export instances"); - assert!(ambiguous.to_string().contains("multiple exports named `id`")); -} - -#[test] -fn compile_supports_inherent_style_method_calls() { - let bytes = compile_ok( - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun bump(counter: Counter, delta: int): Counter { - Counter { value: counter.value + delta } -} - -fun get(counter: Counter): int { - counter.value -} - -export fun main(): int { - new(40).bump(2).get() -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn compile_supports_in_place_method_updates_on_var_locals() { - let bytes = compile_ok( - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun bump(var counter: Counter, delta: int) { - counter.value = counter.value + delta -} - -fun get(counter: Counter): int { - counter.value -} - -export fun main(): int { - var counter: Counter = new(40) - counter.bump(2); - counter.get() -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn compile_preserves_mutable_aggregate_writes_after_statement_if() { - let bytes = compile_ok( - r#" -use std::alloc::int as alloc; - -struct Bag { - ptr: *mut int, - len: u32, - cap: u32, -} - -fun new(): Bag { - unsafe { - Bag { ptr: alloc::alloc_items(1), len: 0, cap: 1 } - } -} - -fun free(var bag: Bag) { - unsafe { - alloc::dealloc_items(bag.ptr, bag.cap); - } -} - -fun push(var bag: Bag, value: int) { - val index: u32 = bag.len - if index == 99 { - val then_noop: u32 = index - } else { - val else_noop: u32 = index - } - unsafe { - val ptr: *mut int = bag.ptr - ptr_write(ptr_add(ptr, index), value); - } - bag.len = bag.len + 1 -} - -export fun main(): int { - var bag: Bag = new() - bag.push(42); - val written: int = unsafe { ptr_read(bag.ptr) } - bag.free(); - written -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn compile_supports_direct_field_assignment_on_var_locals() { - let bytes = compile_ok( - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -export fun main(): int { - var counter: Counter = new(40) - counter.value = counter.value + 2 - counter.value -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn compile_supports_std_vec_int_mutation() { - let bytes = compile_ok( - r#" -use std::vec::int as vec; - -export fun main(): int { - var xs: vec::Vec = vec::new() - xs.push(20); - xs.push(22); - xs.reserve(8); - xs.set(1, 23); - - val first: int = match xs.get(0) { - .Some(value) => value, - .None => 0, - } - val second: int = match xs.get(1) { - .Some(value) => value, - .None => 0, - } - - xs.clear(); - val empty_bonus: int = if xs.is_empty() { 1 } else { 0 } - xs.free(); - - first + second + empty_bonus -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 44); -} - -#[test] -fn compile_supports_std_vec_int_push_reallocation_write() { - let bytes = compile_ok( - r#" -use std::vec::int as vec; - -export fun main(): int { - var xs: vec::Vec = vec::with_capacity(1) - xs.push(1); - xs.push(2); - xs.push(3); - xs.push(4); - xs.push(5); - - val last: int = match xs.get(4) { - .Some(value) => value, - .None => 0, - } - xs.free(); - last -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 5); -} - -#[test] -fn compile_supports_std_vec_int_bounds_checked_get_and_set() { - let bytes = compile_ok( - r#" -use std::vec::int as vec; - -export fun main(): int { - var xs: vec::Vec = vec::new() - xs.push(20); - xs.push(22); - xs.set(5, 99); - - val second: int = match xs.get(1) { - .Some(value) => value, - .None => 0, - } - val missing_bonus: int = match xs.get(5) { - .Some(_) => 1000, - .None => 1, - } - - xs.free(); - second + missing_bonus -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 23); -} - -#[test] -fn enum_constructors_still_work_alongside_method_syntax() { - let bytes = compile_ok( - r#" -enum Answer { - Some(int), - None, -} - -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun bump(counter: Counter, delta: int): Counter { - Counter { value: counter.value + delta } -} - -fun get(counter: Counter): int { - counter.value -} - -export fun main(): int { - val answer: Answer = Answer.Some(new(40).bump(2).get()) - match answer { - .Some(value) => value, - .None => 0, - } -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("export should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn host_function_instance_binds_one_generic_import_instance() { - let bytes = compile_ok( - r#" -import "env" fun id[T](value: T): T; -import instance id[int]; - -export fun main(): int { - id(41) -} -"#, - ); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI v2 metadata"); - let import = import_instance(metadata, "env", "id"); - assert_eq!(import.type_args.len(), 1, "expected one type argument"); - assert!(matches!(metadata.types[import.type_args[0].0 as usize].kind, AbiTypeKind::Int { .. })); - - let mut runtime = WasmRuntime::builder() - .host_function_instance(import.id, |args| { - assert_eq!(args.len(), 1, "expected one argument"); - let AbiValue::Immediate(AbiScalar::Int { value, .. }) = args[0] else { - panic!("expected i32 argument, got {:?}", args[0]); - }; - Ok(Some(AbiValue::Immediate(AbiScalar::Int { - signed: true, - bits: 32, - value: value + 1, - }))) - }) - .instantiate(&bytes) - .expect("runtime should instantiate with concrete import instance handler"); - - let output = runtime.invoke_export("main", &[]).expect("main should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn unsafe_pointer_intrinsics_round_trip_scalars() { - let bytes = compile_ok( - r#" -unsafe fun scalar_round_trip(): int { - val out: *mut u32 = stack_alloc(1) - ptr_write(out, 42); - if ptr_read(out) == 42 { - 1 - } else { - 0 - } -} - -export fun main(): int { - unsafe { - scalar_round_trip() - } -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("unsafe pointer round-trip should run"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 1); -} - -#[test] -fn canonical_pipeline_handles_nested_if_and_match_values() { - let bytes = compile_ok( - r#" -enum Score { - Good(int), - Bad(int), -} - -fun choose(flag: bool, value: Score): int { - if flag { - match value { - .Good(v) => v, - .Bad(v) => if v == 41 { 42 } else { v }, - } - } else { - 0 - } -} - -export fun main(): int { - choose(true, Score.Bad(41)) -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("main should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn canonical_pipeline_handles_aggregate_joins_and_field_access() { - let bytes = compile_ok( - r#" -struct Pair { - left: int, - right: int, -} - -fun choose(flag: bool): Pair { - if flag { - Pair { left: 20, right: 22 } - } else { - Pair { left: 1, right: 2 } - } -} - -export fun main(): int { - val pair: Pair = choose(true) - pair.left + pair.right -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("main should execute"); - expect_immediate_i32(output.result.as_ref().expect("expected result"), 42); -} - -#[test] -fn canonical_pipeline_handles_branch_owned_arrays() { - let bytes = compile_ok( - r#" -fun choose(flag: bool): [str] { - if flag { - ["a", "b"] - } else { - ["c"] - } -} - -export fun main(): [str] { - choose(true) -} -"#, - ); - - let output = invoke_export(&bytes, "main", &[]).expect("main should execute"); - assert_eq!(decode_string_array(output.result.as_ref().expect("expected result")), ["a", "b"]); -} diff --git a/crates/mitki-benchmark/Cargo.toml b/crates/mitki-benchmark/Cargo.toml deleted file mode 100644 index f5251d7..0000000 --- a/crates/mitki-benchmark/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "mitki-benchmark" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[[bench]] -name = "goto_definition" -harness = false - -[[bench]] -name = "parser" -harness = false - -[[bench]] -name = "tokenizer" -harness = false - -[dev-dependencies] -codspeed-criterion-compat = { version = "4.3", default-features = false } -mitki-analysis.workspace = true -mitki-ide.workspace = true -mitki-inputs.workspace = true -mitki-parse.workspace = true -mitki-tokenizer.workspace = true -mitki-yellow.workspace = true -salsa.workspace = true -text-size.workspace = true diff --git a/crates/mitki-benchmark/benches/goto_definition.rs b/crates/mitki-benchmark/benches/goto_definition.rs deleted file mode 100644 index ec9c809..0000000 --- a/crates/mitki-benchmark/benches/goto_definition.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::hint::black_box; - -use codspeed_criterion_compat::{Criterion, criterion_group, criterion_main}; -use mitki_analysis::{ResolveIntent, Semantics}; -use mitki_ide::{Analysis, FilePosition, extract_cursor_offset}; -use mitki_inputs::File; -use mitki_parse::FileParse as _; -use mitki_yellow::SyntaxKind; - -fn benchmark_goto_definition(c: &mut Criterion) { - let analysis = Analysis::default(); - - let fixture = r#" - fun init() { - - } - - fun helper_one(x: i32): i32 { - return x + 1; - } - - fun helper_two(x: i32): i32 { - return x * 2; - } - - fun noise_a() { } - fun noise_b() { } - fun noise_c() { } - fun noise_d() { } - fun noise_e() { } - fun noise_f() { } - fun noise_g() { } - fun noise_h() { } - fun noise_i() { } - fun noise_j() { } - - fun compute_complex_value(y: i32) -> i32 { - let a = helper_one(y); - let b = helper_two(y); - let result = a + b; - return result; - } - - fun extra1() { } - fun extra2() { } - fun extra3() { } - fun extra4() { } - fun extra5() { } - fun extra6() { } - fun extra7() { } - fun extra8() { } - fun extra9() { } - fun extra10() { } - - fun main() { - init(); - let value = compute_complex_value$0(42); - println!("Computed: {}", value); - } - "#; - let (offset, fixture_text) = extract_cursor_offset(fixture); - - let file = File::new(analysis.db(), "goto_complex_test".into(), fixture_text.clone()); - let file_position = FilePosition { file, offset }; - - c.bench_function("goto_definition_complex", |b| { - b.iter(|| { - if let Some((_def, focus)) = analysis.goto_definition(file_position) { - black_box(focus); - } else { - panic!("goto_definition returned an error"); - } - }) - }); -} - -fn benchmark_binding_at(c: &mut Criterion) { - let analysis = Analysis::default(); - let fixture = r#" - fun helper(x: i32): i32 { - return x + 1; - } - - fun compute(y: i32): i32 { - let alpha = helper(y); - let beta = alpha * 2; - let gamma = beta + alpha; - let delta = gamma + beta; - let epsilon = delta + gamma; - let zeta = epsilon + delta; - let eta = zeta + epsilon; - let theta = eta + zeta; - let result = theta + e$0ta; - return result; - } - "#; - let (offset, fixture_text) = extract_cursor_offset(fixture); - let file = File::new(analysis.db(), "binding_at_test".into(), fixture_text); - let semantics = Semantics::new(analysis.db(), file); - let node = name_node_at(file, analysis.db(), offset); - - c.bench_function("binding_at_large_body", |b| { - b.iter(|| { - let binding = semantics.binding_at(analysis.db(), &node, ResolveIntent::Any); - black_box(binding); - }) - }); -} - -fn benchmark_enum_variant_lookup(c: &mut Criterion) { - let analysis = Analysis::default(); - let fixture = r#" - enum Color { - Red, - Green, - Blue, - } - - enum Shape { - Red, - } - - fun main() { - Color.$0Red - } - "#; - let (offset, fixture_text) = extract_cursor_offset(fixture); - let file = File::new(analysis.db(), "enum_variant_test".into(), fixture_text); - let semantics = Semantics::new(analysis.db(), file); - let node = name_node_at(file, analysis.db(), offset); - - c.bench_function("binding_at_enum_variant", |b| { - b.iter(|| { - let binding = semantics.binding_at(analysis.db(), &node, ResolveIntent::Any); - black_box(binding); - }) - }); -} - -fn name_node_at<'db>( - file: File, - db: &'db dyn salsa::Database, - offset: text_size::TextSize, -) -> mitki_yellow::SyntaxNode<'db> { - let root = file.parse(db).syntax_node(); - let token = root - .token_at_offset(offset) - .filter(|token| !token.is_trivia()) - .max_by_key(|token| usize::from(token.kind() == SyntaxKind::NAME)) - .expect("name token"); - token.parent() -} - -criterion_group!( - benches, - benchmark_goto_definition, - benchmark_binding_at, - benchmark_enum_variant_lookup -); -criterion_main!(benches); diff --git a/crates/mitki-benchmark/benches/parser.rs b/crates/mitki-benchmark/benches/parser.rs deleted file mode 100644 index eb7015a..0000000 --- a/crates/mitki-benchmark/benches/parser.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::hint::black_box; - -use codspeed_criterion_compat::{ - BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, -}; -use mitki_inputs::File; -use mitki_parse::FileParse as _; - -fn benchmark_parser(c: &mut Criterion) { - let db = salsa::DatabaseImpl::new(); - let files = vec![ - File::new( - &db, - "Simple".into(), - r#" - fun foo() { - 42 - } - "# - .to_string(), - ), - File::new( - &db, - "Medium".into(), - r#" - fun foo() { - if true {} - if true {} else {} - if true {} else if false {} else {} - } - - fun bar() { - loop {} - } - "# - .to_string(), - ), - ]; - - let mut group = c.benchmark_group("Parser Benchmark"); - - for file in files { - let code_length = file.text(&db).len() as u64; - group.throughput(Throughput::Bytes(code_length)); - group.bench_with_input( - BenchmarkId::new("parse_code", file.path(&db)), - &file, - |b, &file| { - b.iter(|| { - let module = file.parse(&db); - black_box(module); - }); - }, - ); - } - - group.finish(); -} - -criterion_group!(benches, benchmark_parser); -criterion_main!(benches); diff --git a/crates/mitki-benchmark/benches/tokenizer.rs b/crates/mitki-benchmark/benches/tokenizer.rs deleted file mode 100644 index 7db1e20..0000000 --- a/crates/mitki-benchmark/benches/tokenizer.rs +++ /dev/null @@ -1,69 +0,0 @@ -use codspeed_criterion_compat::{ - Criterion, Throughput, black_box, criterion_group, criterion_main, -}; - -static SOURCE: &str = " -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -foobar(val fun loop while val loop while val) { + ++ = == === => } -"; - -static IDENTIFIERS: &str = - "It was the year when they finally immanentized the Eschaton It was the year when they \ - finally immanentized the Eschaton It was the year when they finally immanentized the \ - Eschaton It was the year when they finally immanentized the Eschaton It was the year when \ - they finally immanentized the Eschaton It was the year when they finally immanentized the \ - Eschaton It was the year when they finally immanentized the Eschaton It was the year when \ - they finally immanentized the Eschaton It was the year when they finally immanentized the \ - Eschaton It was the year when they finally immanentized the Eschaton It was the year when \ - they finally immanentized the Eschaton It was the year when they finally immanentized the \ - Eschaton It was the year when they finally immanentized the Eschaton"; - -static CANDIDATES: [(&str, &str); 2] = - [("identifiers", IDENTIFIERS), ("keywords_operators_and_punctators", SOURCE)]; - -fn iterate(s: &str) { - use mitki_tokenizer::Tokenizer; - - let tokenizer = Tokenizer::new(s); - black_box(tokenizer); -} - -fn bench_iterate(c: &mut Criterion) { - let mut group = c.benchmark_group("iterate"); - - for (name, source) in CANDIDATES { - group.throughput(Throughput::Bytes(source.len() as u64)); - group.bench_with_input(name, &source, |b, &s| b.iter(|| iterate(s))); - } -} - -criterion_group!(benches, bench_iterate); -criterion_main!(benches); diff --git a/crates/mitki-benchmark/src/lib.rs b/crates/mitki-benchmark/src/lib.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/mitki-benchmark/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/mitki-codegen-core/Cargo.toml b/crates/mitki-codegen-core/Cargo.toml deleted file mode 100644 index bebb4f2..0000000 --- a/crates/mitki-codegen-core/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "mitki-codegen-core" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-hir.workspace = true -mitki-lower.workspace = true -mitki-resolve.workspace = true -mitki-span.workspace = true -rustc-hash.workspace = true -salsa.workspace = true diff --git a/crates/mitki-codegen-core/src/capability.rs b/crates/mitki-codegen-core/src/capability.rs deleted file mode 100644 index fb84471..0000000 --- a/crates/mitki-codegen-core/src/capability.rs +++ /dev/null @@ -1,165 +0,0 @@ -use mitki_hir::ty::{Ty, TyKind}; - -use crate::classify::{AbiTy, FunctionSignature}; -use crate::descriptor::{ - TypeRuntimeDescriptor, - supported_type_runtime_descriptor as raw_supported_type_runtime_descriptor, -}; -use crate::layout::{AggregateLayout, ArrayRuntimeLayout}; -use crate::runtime_rep::runtime_rep_descriptor; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ValueSupportFailure<'db> { - Unsupported(Ty<'db>), - Recursive(Ty<'db>), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BoundaryCapabilityFailure<'db> { - Type(ValueSupportFailure<'db>), -} - -pub fn supported_value_abi(db: &dyn salsa::Database, ty: Ty<'_>) -> Option { - runtime_rep_descriptor(db, ty).ok().map(|descriptor| descriptor.runtime_abi()) -} - -pub fn supported_value_abi_or_message( - db: &dyn salsa::Database, - ty: Ty<'_>, - context: &str, -) -> Result { - supported_value_abi(db, ty).ok_or_else(|| { - value_support_message(db, ensure_value_support(db, ty).unwrap_err(), context) - }) -} - -pub fn supports_boundary(db: &dyn salsa::Database, ty: Ty<'_>) -> bool { - ensure_boundary_capability(db, ty).is_ok() -} - -pub fn supported_function_signature( - db: &dyn salsa::Database, - ty: Ty<'_>, -) -> Option { - runtime_rep_descriptor(db, ty).ok()?.function_signature() -} - -pub fn supported_function_signature_or_message( - db: &dyn salsa::Database, - ty: Ty<'_>, - context: &str, -) -> Result { - supported_function_signature(db, ty) - .ok_or_else(|| unsupported_shape_message(db, ty, context, "a function value type")) -} - -pub fn supported_array_runtime_layout( - db: &dyn salsa::Database, - ty: Ty<'_>, -) -> Option { - runtime_rep_descriptor(db, ty).ok()?.array_layout() -} - -pub fn supported_array_runtime_layout_or_message( - db: &dyn salsa::Database, - ty: Ty<'_>, - context: &str, -) -> Result { - supported_array_runtime_layout(db, ty) - .ok_or_else(|| unsupported_shape_message(db, ty, context, "an array value type")) -} - -pub fn supported_internal_nominal_payload_layout( - db: &dyn salsa::Database, - ty: Ty<'_>, -) -> Option { - runtime_rep_descriptor(db, ty).ok()?.runtime_nominal_payload_layout() -} - -pub fn supported_internal_nominal_payload_layout_or_message( - db: &dyn salsa::Database, - ty: Ty<'_>, - context: &str, -) -> Result { - supported_internal_nominal_payload_layout(db, ty) - .ok_or_else(|| unsupported_shape_message(db, ty, context, "a nominal value type")) -} - -fn ensure_value_support<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Result<(), ValueSupportFailure<'db>> { - runtime_rep_descriptor(db, ty).map(|_shape| ()).map_err(map_runtime_rep_failure) -} - -pub fn ensure_boundary_capability<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Result<(), BoundaryCapabilityFailure<'db>> { - ensure_value_support(db, ty).map_err(BoundaryCapabilityFailure::Type) -} - -pub fn supported_type_runtime_descriptor<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Option> { - raw_supported_type_runtime_descriptor(db, ty) -} - -fn map_runtime_rep_failure<'db>( - failure: crate::runtime_rep::RuntimeRepFailure<'db>, -) -> ValueSupportFailure<'db> { - match failure { - crate::runtime_rep::RuntimeRepFailure::Unsupported(ty) => { - ValueSupportFailure::Unsupported(ty) - } - crate::runtime_rep::RuntimeRepFailure::Recursive(ty) => ValueSupportFailure::Recursive(ty), - } -} - -fn value_support_message( - db: &dyn salsa::Database, - failure: ValueSupportFailure<'_>, - context: &str, -) -> String { - format!("{context}; {}", value_support_detail(db, failure)) -} - -fn value_support_detail(db: &dyn salsa::Database, failure: ValueSupportFailure<'_>) -> String { - let ty = match failure { - ValueSupportFailure::Unsupported(ty) | ValueSupportFailure::Recursive(ty) => ty, - }; - - match failure { - ValueSupportFailure::Recursive(_) => { - format!("recursive types are not supported yet; found `{}`", ty.display(db)) - } - ValueSupportFailure::Unsupported(_) => match ty.kind(db) { - TyKind::Inter(_) => { - format!("intersection types are not supported yet; found `{}`", ty.display(db)) - } - TyKind::Rec(_, _) => { - format!("recursive types are not supported yet; found `{}`", ty.display(db)) - } - TyKind::Pointer { .. } => { - format!("raw pointer type `{}` is not supported in this context", ty.display(db)) - } - TyKind::ExternStruct(_) => { - format!("extern struct type `{}` is not supported in this context", ty.display(db)) - } - _ => format!("found `{}`", ty.display(db)), - }, - } -} - -fn unsupported_shape_message( - db: &dyn salsa::Database, - ty: Ty<'_>, - context: &str, - expected: &str, -) -> String { - match ensure_value_support(db, ty) { - Err(failure) => value_support_message(db, failure, context), - Ok(()) => format!("{context}; expected {expected}, found `{}`", ty.display(db)), - } -} diff --git a/crates/mitki-codegen-core/src/classify.rs b/crates/mitki-codegen-core/src/classify.rs deleted file mode 100644 index c826c12..0000000 --- a/crates/mitki-codegen-core/src/classify.rs +++ /dev/null @@ -1,600 +0,0 @@ -use mitki_hir::ty::{ExactInt, Ty, TyKind}; -use mitki_lower::item::scope::{enum_variants, struct_fields}; -use mitki_resolve::{RuntimeFunction, RuntimeTy}; -use mitki_span::Symbol; -use rustc_hash::{FxHashMap, FxHashSet}; -use salsa::plumbing::AsId as _; - -use crate::layout::{ - ARC_HEADER_SIZE, ARRAY_HEADER_SIZE, AggregateKind, AggregateLayout, ArrayRuntimeLayout, - EnumLayout, VariantLayout, abi_layout, align_to, layout_fields, symbol_bits, -}; -use crate::stage_intrinsic::StageIntrinsic; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum WasmValType { - I32, - I64, - F32, - F64, - V128, - Ref, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum WasmBlockType { - Empty, - Result(WasmValType), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RefKind { - String, - Opaque, - Array(u32), - Nominal(u32), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum BackendTy { - Int, - I64, - Bool, - Float, - Char, - Ref(RefKind), - Unit, -} - -impl BackendTy { - pub fn value_type(self) -> Option { - match self { - BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_) => { - Some(WasmValType::I32) - } - BackendTy::I64 => Some(WasmValType::I64), - BackendTy::Float => Some(WasmValType::F64), - BackendTy::Unit => None, - } - } - - pub fn block_type(self) -> WasmBlockType { - self.value_type().map_or(WasmBlockType::Empty, WasmBlockType::Result) - } - - pub fn is_heap_ref(self) -> bool { - matches!(self, Self::Ref(_)) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct FunctionSignature { - pub params: Vec, - pub result: AbiTy, -} - -impl FunctionSignature { - #[allow(dead_code)] - pub fn wasm_results(&self) -> Vec { - match self.result { - AbiTy::Scalar(result) => result.value_type().into_iter().collect(), - AbiTy::Aggregate(_) => Vec::new(), - } - } - - #[allow(dead_code)] - pub fn wasm_params_with_env(&self) -> Vec { - let mut params = - Vec::with_capacity(self.params.len() + usize::from(self.result.is_aggregate()) + 1); - params.push(WasmValType::I32); - if self.result.is_aggregate() { - params.push(WasmValType::I32); - } - - for ty in &self.params { - match ty { - AbiTy::Scalar(ty) => { - if let Some(value_type) = ty.value_type() { - params.push(value_type); - } - } - AbiTy::Aggregate(_) => params.push(WasmValType::I32), - } - } - - params - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum AbiTy { - Scalar(BackendTy), - Aggregate(Box), -} - -impl AbiTy { - pub fn is_aggregate(&self) -> bool { - matches!(self, Self::Aggregate(_)) - } - - pub fn aggregate(&self) -> Option<&AggregateLayout> { - let Self::Aggregate(layout) = self else { - return None; - }; - Some(layout) - } - - pub fn contains_heap_refs(&self) -> bool { - match self { - Self::Scalar(ty) => ty.is_heap_ref(), - Self::Aggregate(layout) => layout.contains_heap_refs(), - } - } -} - -pub fn exact_int_backend_ty(int_ty: ExactInt) -> BackendTy { - if int_ty.bits() <= 32 { BackendTy::Int } else { BackendTy::I64 } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ValueShapeFailure<'db> { - Unsupported(Ty<'db>), - Recursive(Ty<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ValueShapeField<'db> { - pub name: Option>, - pub ty: Ty<'db>, - pub shape: Box>, - pub runtime_offset: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ValueShapeVariant<'db> { - pub name: Symbol<'db>, - pub tag: i32, - pub fields: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FieldsShape<'db> { - pub fields: Vec>, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EnumShape<'db> { - pub variants: Vec>, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct UnionShape<'db> { - pub members: Vec>, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ArrayShape<'db> { - pub item_ty: Ty<'db>, - pub item: Box>, - pub layout: ArrayRuntimeLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FunctionValueShape<'db> { - pub params: Vec>, - pub result: Box>, - pub signature: FunctionSignature, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ValueShapeKind<'db> { - Unit, - Int, - Bool, - Float, - Char, - String, - Array(ArrayShape<'db>), - Tuple(FieldsShape<'db>), - Record(FieldsShape<'db>), - Struct(FieldsShape<'db>), - Enum(EnumShape<'db>), - Union(UnionShape<'db>), - Function(FunctionValueShape<'db>), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ValueShape<'db> { - pub ty: Ty<'db>, - pub kind: ValueShapeKind<'db>, - pub runtime_abi: AbiTy, -} - -pub fn stage_intrinsic_signature(intrinsic: StageIntrinsic) -> FunctionSignature { - match intrinsic { - StageIntrinsic::TypeName => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Int)], - result: AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - }, - StageIntrinsic::FieldCount - | StageIntrinsic::VariantCount - | StageIntrinsic::FunctionParamCount => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Int)], - result: AbiTy::Scalar(BackendTy::Int), - }, - StageIntrinsic::FieldName - | StageIntrinsic::VariantName - | StageIntrinsic::FunctionParamTypeName => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Int), AbiTy::Scalar(BackendTy::Int)], - result: AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - }, - StageIntrinsic::FunctionReturnTypeName => FunctionSignature { - params: vec![AbiTy::Scalar(BackendTy::Int)], - result: AbiTy::Scalar(BackendTy::Ref(RefKind::String)), - }, - } -} - -pub fn runtime_function_signature(function: RuntimeFunction) -> FunctionSignature { - let params = - function.params().iter().map(|ty| AbiTy::Scalar(runtime_ty_to_backend_ty(*ty))).collect(); - let result = AbiTy::Scalar(runtime_ty_to_backend_ty(function.result())); - FunctionSignature { params, result } -} - -pub fn runtime_ty_to_backend_ty(ty: RuntimeTy) -> BackendTy { - match ty { - RuntimeTy::Int => BackendTy::Int, - RuntimeTy::Str => BackendTy::Ref(RefKind::String), - RuntimeTy::Unit => BackendTy::Unit, - } -} - -#[allow(dead_code)] -pub fn classify_ty(db: &dyn salsa::Database, ty: Ty<'_>) -> Option { - match abi_ty(db, ty)? { - AbiTy::Scalar(ty) => Some(ty), - AbiTy::Aggregate(_) => None, - } -} - -pub fn ty_bits(ty: Ty<'_>) -> u32 { - u32::try_from(ty.as_id().as_bits()).expect("type id should fit into u32") -} - -pub fn nominal_ty_bits(ty: Ty<'_>) -> u32 { - ty_bits(ty) -} - -pub fn array_ty_bits(ty: Ty<'_>) -> u32 { - ty_bits(ty) -} - -pub fn value_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Result, ValueShapeFailure<'db>> { - build_value_shape(db, ty, &mut FxHashMap::default(), &mut FxHashSet::default()) -} - -pub fn supported_value_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Option> { - value_shape(db, ty).ok() -} - -pub fn abi_ty(db: &dyn salsa::Database, ty: Ty<'_>) -> Option { - Some(supported_value_shape(db, ty)?.runtime_abi) -} - -pub fn function_value_abi_ty() -> AbiTy { - AbiTy::Aggregate(Box::new(function_value_layout())) -} - -pub fn function_value_layout() -> AggregateLayout { - AggregateLayout { size: 8, align: 4, kind: AggregateKind::FunctionValue } -} - -fn build_value_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - cache: &mut FxHashMap>, - active: &mut FxHashSet, -) -> Result, ValueShapeFailure<'db>> { - let bits = ty.as_id().as_bits(); - if let Some(shape) = cache.get(&bits) { - return Ok(shape.clone()); - } - if !active.insert(bits) { - return Err(ValueShapeFailure::Recursive(ty)); - } - - let shape = match ty.kind(db) { - TyKind::Tuple(items) if items.is_empty() => { - scalar_shape(ty, ValueShapeKind::Unit, BackendTy::Unit) - } - TyKind::Int => scalar_shape(ty, ValueShapeKind::Int, BackendTy::Int), - TyKind::ExactInt(int_ty) => { - scalar_shape(ty, ValueShapeKind::Int, exact_int_backend_ty(*int_ty)) - } - TyKind::Bool => scalar_shape(ty, ValueShapeKind::Bool, BackendTy::Bool), - TyKind::Float => scalar_shape(ty, ValueShapeKind::Float, BackendTy::Float), - TyKind::Char => scalar_shape(ty, ValueShapeKind::Char, BackendTy::Char), - TyKind::String => scalar_shape(ty, ValueShapeKind::String, BackendTy::Ref(RefKind::String)), - TyKind::Pointer { .. } => scalar_shape(ty, ValueShapeKind::Int, BackendTy::Int), - TyKind::Array(item_ty) => { - let item = build_value_shape(db, *item_ty, cache, active)?; - let layout = array_runtime_layout_from_item_abi(ty, item.runtime_abi.clone()) - .ok_or(ValueShapeFailure::Unsupported(ty))?; - ValueShape { - ty, - runtime_abi: AbiTy::Scalar(BackendTy::Ref(RefKind::Array(array_ty_bits(ty)))), - kind: ValueShapeKind::Array(ArrayShape { - item_ty: *item_ty, - item: Box::new(item), - layout, - }), - } - } - TyKind::Tuple(items) => { - let fields = items.iter().map(|&item_ty| (None, item_ty)).collect::>(); - let shape = build_fields_shape(db, ty, fields, cache, active)?; - let runtime_abi = AbiTy::Aggregate(Box::new(shape.runtime_layout.clone())); - ValueShape { ty, kind: ValueShapeKind::Tuple(shape), runtime_abi } - } - TyKind::Record(fields) => { - let mut ordered = fields.clone(); - ordered.sort_by_key(|(name, _)| name.text(db).to_owned()); - let fields = - ordered.into_iter().map(|(name, field_ty)| (Some(name), field_ty)).collect(); - let shape = build_fields_shape(db, ty, fields, cache, active)?; - let runtime_abi = AbiTy::Aggregate(Box::new(shape.runtime_layout.clone())); - ValueShape { ty, kind: ValueShapeKind::Record(shape), runtime_abi } - } - TyKind::ExternStruct(struct_ty) => { - let fields = struct_fields(db, *struct_ty) - .iter() - .map(|(name, field_ty)| (Some(*name), *field_ty)) - .collect::>(); - let shape = build_fields_shape(db, ty, fields, cache, active)?; - let runtime_abi = AbiTy::Aggregate(Box::new(shape.runtime_layout.clone())); - ValueShape { ty, kind: ValueShapeKind::Record(shape), runtime_abi } - } - TyKind::Struct(struct_ty) => { - let fields = struct_fields(db, *struct_ty) - .iter() - .map(|(name, field_ty)| (Some(*name), *field_ty)) - .collect::>(); - let shape = build_fields_shape(db, ty, fields, cache, active)?; - ValueShape { - ty, - runtime_abi: AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(nominal_ty_bits(ty)))), - kind: ValueShapeKind::Struct(shape), - } - } - TyKind::Enum(enum_ty) => { - let variants = enum_variants(db, *enum_ty) - .iter() - .enumerate() - .map(|(tag, (name, fields))| (*name, tag as i32, fields.clone())) - .collect::>(); - let shape = build_enum_shape(db, ty, variants, cache, active)?; - ValueShape { - ty, - runtime_abi: AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(nominal_ty_bits(ty)))), - kind: ValueShapeKind::Enum(shape), - } - } - TyKind::Union(items) => { - let shape = build_union_shape(db, ty, items.clone(), cache, active)?; - let runtime_abi = AbiTy::Aggregate(Box::new(shape.runtime_layout.clone())); - ValueShape { ty, kind: ValueShapeKind::Union(shape), runtime_abi } - } - TyKind::Function { inputs, output } => { - let params = inputs - .iter() - .map(|&input| build_value_shape(db, input, cache, active)) - .collect::, _>>()?; - let result = Box::new(build_value_shape(db, *output, cache, active)?); - let signature = FunctionSignature { - params: params.iter().map(|shape| shape.runtime_abi.clone()).collect(), - result: result.runtime_abi.clone(), - }; - ValueShape { - ty, - runtime_abi: function_value_abi_ty(), - kind: ValueShapeKind::Function(FunctionValueShape { params, result, signature }), - } - } - TyKind::Inter(_) | TyKind::Unknown | TyKind::Var(_) => { - return Err(ValueShapeFailure::Unsupported(ty)); - } - TyKind::Rec(_, _) => return Err(ValueShapeFailure::Recursive(ty)), - }; - - active.remove(&bits); - cache.insert(bits, shape.clone()); - Ok(shape) -} - -fn scalar_shape<'db>( - ty: Ty<'db>, - kind: ValueShapeKind<'db>, - backend_ty: BackendTy, -) -> ValueShape<'db> { - let abi = AbiTy::Scalar(backend_ty); - ValueShape { ty, kind, runtime_abi: abi } -} - -fn build_fields_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - declared_fields: Vec<(Option>, Ty<'db>)>, - cache: &mut FxHashMap>, - active: &mut FxHashSet, -) -> Result, ValueShapeFailure<'db>> { - let built = declared_fields - .into_iter() - .map(|(name, field_ty)| { - build_value_shape(db, field_ty, cache, active).map(|shape| (name, field_ty, shape)) - }) - .collect::, _>>()?; - let runtime_layout = aggregate_layout_from_fields( - built.iter().map(|(name, _, shape)| (name.map(symbol_bits), shape.runtime_abi.clone())), - ) - .ok_or(ValueShapeFailure::Unsupported(ty))?; - let runtime_fields = runtime_layout.fields().unwrap_or(&[]); - let fields = built - .into_iter() - .zip(runtime_fields.iter()) - .map(|((name, field_ty, shape), runtime_field)| ValueShapeField { - name, - ty: field_ty, - shape: Box::new(shape), - runtime_offset: runtime_field.offset, - }) - .collect(); - Ok(FieldsShape { fields, runtime_layout }) -} - -fn build_enum_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - declared_variants: Vec<(Symbol<'db>, i32, Vec>)>, - cache: &mut FxHashMap>, - active: &mut FxHashSet, -) -> Result, ValueShapeFailure<'db>> { - let built_variants = declared_variants - .into_iter() - .map(|(name, tag, field_tys)| { - let fields = field_tys - .into_iter() - .map(|field_ty| { - build_value_shape(db, field_ty, cache, active).map(|shape| (field_ty, shape)) - }) - .collect::, _>>()?; - Ok::<_, ValueShapeFailure<'db>>((name, tag, fields)) - }) - .collect::, _>>()?; - - let runtime_layout = - enum_layout_from_variants(built_variants.iter().map(|(name, tag, fields)| { - ( - symbol_bits(*name), - *tag, - fields.iter().map(|(_, shape)| shape.runtime_abi.clone()).collect::>(), - ) - })) - .ok_or(ValueShapeFailure::Unsupported(ty))?; - let AggregateKind::Enum(runtime_enum_layout) = &runtime_layout.kind else { - return Err(ValueShapeFailure::Unsupported(ty)); - }; - let variants = built_variants - .into_iter() - .zip(runtime_enum_layout.variants.iter()) - .map(|((name, tag, fields), runtime_variant)| ValueShapeVariant { - name, - tag, - fields: fields - .into_iter() - .zip(runtime_variant.fields.iter()) - .map(|((field_ty, shape), runtime_field)| ValueShapeField { - name: None, - ty: field_ty, - shape: Box::new(shape), - runtime_offset: runtime_field.offset, - }) - .collect(), - }) - .collect(); - - Ok(EnumShape { variants, runtime_layout }) -} - -fn build_union_shape<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - members: Vec>, - cache: &mut FxHashMap>, - active: &mut FxHashSet, -) -> Result, ValueShapeFailure<'db>> { - let members = members - .into_iter() - .map(|member_ty| build_value_shape(db, member_ty, cache, active)) - .collect::, _>>()?; - let runtime_layout = - enum_layout_from_variants(members.iter().enumerate().map(|(index, shape)| { - ((index as u64) + 1, index as i32, vec![shape.runtime_abi.clone()]) - })) - .ok_or(ValueShapeFailure::Unsupported(ty))?; - Ok(UnionShape { members, runtime_layout }) -} - -fn aggregate_layout_from_fields( - fields: impl IntoIterator, AbiTy)>, -) -> Option { - let layout = layout_fields(fields.into_iter().map(|(name_bits, abi)| (name_bits, Some(abi))))?; - Some(AggregateLayout { - size: layout.size, - align: layout.align, - kind: AggregateKind::Fields(layout.fields), - }) -} - -fn enum_layout_from_variants( - variants: impl IntoIterator)>, -) -> Option { - let mut payload_size = 0u32; - let mut payload_align = 1u32; - let mut variant_layouts = Vec::new(); - - for (name_bits, tag, fields) in variants { - let payload = layout_fields(fields.into_iter().map(|ty| (None, Some(ty))))?; - payload_size = payload_size.max(payload.size); - payload_align = payload_align.max(payload.align); - variant_layouts.push((name_bits, tag, payload)); - } - - let payload_offset = align_to(4, payload_align); - let align = 4u32.max(payload_align); - let variants = variant_layouts - .into_iter() - .map(|(name_bits, tag, payload)| VariantLayout { - name_bits, - tag, - fields: payload - .fields - .into_iter() - .map(|mut field| { - field.offset += payload_offset; - field - }) - .collect(), - }) - .collect(); - Some(AggregateLayout { - size: align_to(payload_offset + payload_size, align), - align, - kind: AggregateKind::Enum(EnumLayout { payload_offset, variants }), - }) -} - -fn array_runtime_layout_from_item_abi(ty: Ty<'_>, item_abi: AbiTy) -> Option { - let item_layout = abi_layout(&item_abi)?; - let item_align = item_layout.align.max(1); - let item_size = item_layout.size; - let item_stride = align_to(item_size, item_align); - let data_offset = align_to(ARC_HEADER_SIZE + ARRAY_HEADER_SIZE, item_align) - ARC_HEADER_SIZE; - Some(ArrayRuntimeLayout { - type_bits: array_ty_bits(ty), - item_abi, - item_size, - item_align, - item_stride, - data_offset, - }) -} diff --git a/crates/mitki-codegen-core/src/descriptor.rs b/crates/mitki-codegen-core/src/descriptor.rs deleted file mode 100644 index 3dafbb3..0000000 --- a/crates/mitki-codegen-core/src/descriptor.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::ops::Deref; - -use mitki_hir::ty::Ty; - -use crate::classify::{self, ValueShape, ValueShapeFailure, ty_bits}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct TypeGraphNodeId(pub u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct RecursiveTypeGroupId(pub u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TypeRuntimeDescriptor<'db> { - shape: ValueShape<'db>, - graph_node: TypeGraphNodeId, - recursive_group: Option, -} - -pub type TypeRuntimeDescriptorFailure<'db> = ValueShapeFailure<'db>; - -impl<'db> TypeRuntimeDescriptor<'db> { - pub fn new(shape: ValueShape<'db>) -> Self { - Self { graph_node: TypeGraphNodeId(ty_bits(shape.ty)), recursive_group: None, shape } - } - - pub fn graph_node(&self) -> TypeGraphNodeId { - self.graph_node - } -} - -impl<'db> Deref for TypeRuntimeDescriptor<'db> { - type Target = ValueShape<'db>; - - fn deref(&self) -> &Self::Target { - &self.shape - } -} - -pub fn type_runtime_descriptor<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Result, TypeRuntimeDescriptorFailure<'db>> { - classify::value_shape(db, ty).map(TypeRuntimeDescriptor::new) -} - -pub fn supported_type_runtime_descriptor<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Option> { - type_runtime_descriptor(db, ty).ok() -} diff --git a/crates/mitki-codegen-core/src/layout.rs b/crates/mitki-codegen-core/src/layout.rs deleted file mode 100644 index 18478c4..0000000 --- a/crates/mitki-codegen-core/src/layout.rs +++ /dev/null @@ -1,180 +0,0 @@ -use mitki_span::Symbol; -use salsa::plumbing::AsId as _; - -use crate::classify::{AbiTy, BackendTy}; - -pub const ARC_HEADER_SIZE: u32 = 8; -pub const ARC_ALIGN: u32 = 8; -pub const ARC_IMMORTAL_REFCNT: i32 = -1; -pub const ARRAY_HEADER_SIZE: u32 = 8; -pub const ARRAY_LEN_OFFSET: u32 = 0; -pub const ARRAY_CAPACITY_OFFSET: u32 = 4; - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct AggregateLayout { - pub size: u32, - pub align: u32, - pub kind: AggregateKind, -} - -impl AggregateLayout { - pub fn field_named(&self, name_bits: u64) -> Option<&FieldLayout> { - let AggregateKind::Fields(fields) = &self.kind else { - return None; - }; - fields.iter().find(|field| field.name_bits == Some(name_bits)) - } - - pub fn fields(&self) -> Option<&[FieldLayout]> { - let AggregateKind::Fields(fields) = &self.kind else { - return None; - }; - Some(fields) - } - - pub fn variant(&self, name_bits: u64) -> Option<&VariantLayout> { - let AggregateKind::Enum(layout) = &self.kind else { - return None; - }; - layout.variants.iter().find(|variant| variant.name_bits == name_bits) - } - - pub fn contains_heap_refs(&self) -> bool { - match &self.kind { - AggregateKind::Fields(fields) => fields.iter().any(FieldLayout::contains_heap_refs), - AggregateKind::Enum(layout) => layout - .variants - .iter() - .any(|variant| variant.fields.iter().any(FieldLayout::contains_heap_refs)), - AggregateKind::FunctionValue => true, - } - } - - pub fn is_function_value(&self) -> bool { - matches!(self.kind, AggregateKind::FunctionValue) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct ArrayRuntimeLayout { - pub type_bits: u32, - pub item_abi: AbiTy, - pub item_size: u32, - pub item_align: u32, - pub item_stride: u32, - pub data_offset: u32, -} - -impl ArrayRuntimeLayout { - pub fn object_align(&self) -> u32 { - ARC_ALIGN.max(self.item_align) - } - - pub fn total_size_for_len(&self, len: u32) -> Option { - self.item_stride - .checked_mul(len)? - .checked_add(ARC_HEADER_SIZE)? - .checked_add(self.data_offset) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum AggregateKind { - Fields(Vec), - Enum(EnumLayout), - FunctionValue, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct FieldLayout { - pub name_bits: Option, - pub offset: u32, - pub ty: AbiTy, -} - -impl FieldLayout { - pub fn contains_heap_refs(&self) -> bool { - self.ty.contains_heap_refs() - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct EnumLayout { - pub payload_offset: u32, - pub variants: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct VariantLayout { - pub name_bits: u64, - pub tag: i32, - pub fields: Vec, -} - -#[derive(Clone, Debug)] -pub struct FieldsLayout { - pub fields: Vec, - pub size: u32, - pub align: u32, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct MemoryArg { - pub offset: u64, - pub align: u32, - pub memory_index: u32, -} - -pub fn layout_fields( - fields: impl IntoIterator, Option)>, -) -> Option { - let mut field_layouts = Vec::new(); - let mut size = 0; - let mut align = 1; - - for (name_bits, field) in fields { - let field = field?; - let layout = abi_layout(&field)?; - let offset = align_to(size, layout.align); - size = offset + layout.size; - align = align.max(layout.align); - field_layouts.push(FieldLayout { name_bits, offset, ty: field }); - } - - Some(FieldsLayout { fields: field_layouts, size: align_to(size, align), align }) -} - -pub fn abi_layout(ty: &AbiTy) -> Option { - match ty { - AbiTy::Scalar(BackendTy::Int | BackendTy::Bool | BackendTy::Char | BackendTy::Ref(_)) => { - Some(AggregateLayout { size: 4, align: 4, kind: AggregateKind::Fields(Vec::new()) }) - } - AbiTy::Scalar(BackendTy::I64) => { - Some(AggregateLayout { size: 8, align: 8, kind: AggregateKind::Fields(Vec::new()) }) - } - AbiTy::Scalar(BackendTy::Float) => { - Some(AggregateLayout { size: 8, align: 8, kind: AggregateKind::Fields(Vec::new()) }) - } - AbiTy::Scalar(BackendTy::Unit) => { - Some(AggregateLayout { size: 0, align: 1, kind: AggregateKind::Fields(Vec::new()) }) - } - AbiTy::Aggregate(layout) => Some((**layout).clone()), - } -} - -pub fn align_to(offset: u32, align: u32) -> u32 { - if align <= 1 { - offset - } else { - let mask = align - 1; - (offset + mask) & !mask - } -} - -pub fn memarg(offset: u32, align: u32) -> MemoryArg { - MemoryArg { offset: offset.into(), align, memory_index: 0 } -} - -pub fn symbol_bits(symbol: Symbol<'_>) -> u64 { - symbol.as_id().as_bits() -} diff --git a/crates/mitki-codegen-core/src/lib.rs b/crates/mitki-codegen-core/src/lib.rs deleted file mode 100644 index 7316a56..0000000 --- a/crates/mitki-codegen-core/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod capability; -pub mod classify; -pub mod descriptor; -pub mod layout; -pub mod runtime_rep; -pub mod stage_intrinsic; - -pub use stage_intrinsic::StageIntrinsic; diff --git a/crates/mitki-codegen-core/src/runtime_rep.rs b/crates/mitki-codegen-core/src/runtime_rep.rs deleted file mode 100644 index 45e45e2..0000000 --- a/crates/mitki-codegen-core/src/runtime_rep.rs +++ /dev/null @@ -1,810 +0,0 @@ -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::item::scope::{enum_variants, struct_fields}; -use mitki_span::Symbol; -use rustc_hash::{FxHashMap, FxHashSet}; -use salsa::plumbing::AsId as _; - -use crate::classify::{ - AbiTy, BackendTy, FunctionSignature, RefKind, array_ty_bits, exact_int_backend_ty, - function_value_abi_ty, nominal_ty_bits, ty_bits, -}; -use crate::layout::{ - ARC_HEADER_SIZE, ARRAY_HEADER_SIZE, AggregateKind, AggregateLayout, ArrayRuntimeLayout, - EnumLayout, VariantLayout, abi_layout, align_to, layout_fields, symbol_bits, -}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct RuntimeRepId(pub u32); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct RecursiveRuntimeRepGroupId(pub u32); - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeRepField<'db> { - pub name: Option>, - pub ty: Ty<'db>, - pub rep: RuntimeRepId, - pub runtime_offset: u32, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeRepVariant<'db> { - pub name: Symbol<'db>, - pub tag: i32, - pub fields: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeFieldsRep<'db> { - pub fields: Vec>, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeEnumRep<'db> { - pub variants: Vec>, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeUnionRep { - pub members: Vec, - pub runtime_layout: AggregateLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeIntersectionRep { - pub carrier: RuntimeRepId, - pub members: Vec, - pub runtime_layout: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeArrayRep<'db> { - pub item_ty: Ty<'db>, - pub item: RuntimeRepId, - pub layout: ArrayRuntimeLayout, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeFunctionRep { - pub params: Vec, - pub result: RuntimeRepId, - pub signature: FunctionSignature, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum RuntimeRepKind<'db> { - Pending, - Unit, - Int, - Bool, - Float, - Char, - String, - Array(RuntimeArrayRep<'db>), - Tuple(RuntimeFieldsRep<'db>), - Record(RuntimeFieldsRep<'db>), - ExternStruct(RuntimeFieldsRep<'db>), - Struct(RuntimeFieldsRep<'db>), - Enum(RuntimeEnumRep<'db>), - Union(RuntimeUnionRep), - Intersection(RuntimeIntersectionRep), - Function(RuntimeFunctionRep), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeRepNode<'db> { - pub id: RuntimeRepId, - pub ty: Ty<'db>, - pub kind: RuntimeRepKind<'db>, - pub runtime_abi: AbiTy, - pub recursive_group: Option, - pub contains_function: bool, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeRepGraph<'db> { - pub nodes: Vec>, - pub recursive_groups: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeRepDescriptor<'db> { - graph: RuntimeRepGraph<'db>, - root: RuntimeRepId, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RuntimeRepFailure<'db> { - Unsupported(Ty<'db>), - Recursive(Ty<'db>), -} - -impl<'db> RuntimeRepDescriptor<'db> { - pub fn build( - db: &'db dyn salsa::Database, - ty: Ty<'db>, - ) -> Result> { - let mut builder = RuntimeRepBuilder::new(db); - let root = builder.intern_type(ty, &mut FxHashMap::default())?; - builder.populate_recursive_groups(); - builder.populate_contains_function()?; - Ok(Self { graph: builder.graph, root }) - } - - pub fn root_node(&self) -> &RuntimeRepNode<'db> { - self.node(self.root) - } - - pub fn node(&self, id: RuntimeRepId) -> &RuntimeRepNode<'db> { - &self.graph.nodes[id.0 as usize] - } - - pub fn runtime_abi(&self) -> AbiTy { - self.root_node().runtime_abi.clone() - } - - pub fn array_layout(&self) -> Option { - let RuntimeRepKind::Array(array) = &self.root_node().kind else { - return None; - }; - Some(array.layout.clone()) - } - - pub fn function_signature(&self) -> Option { - let RuntimeRepKind::Function(function) = &self.root_node().kind else { - return None; - }; - Some(function.signature.clone()) - } - - pub fn runtime_nominal_payload_layout(&self) -> Option { - match &self.root_node().kind { - RuntimeRepKind::Struct(fields) => Some(fields.runtime_layout.clone()), - RuntimeRepKind::Enum(shape) => Some(shape.runtime_layout.clone()), - _ => None, - } - } -} - -pub fn runtime_rep_descriptor<'db>( - db: &'db dyn salsa::Database, - ty: Ty<'db>, -) -> Result, RuntimeRepFailure<'db>> { - RuntimeRepDescriptor::build(db, ty) -} - -struct RuntimeRepBuilder<'db> { - db: &'db dyn salsa::Database, - graph: RuntimeRepGraph<'db>, - cache: FxHashMap, - active: FxHashSet, -} - -impl<'db> RuntimeRepBuilder<'db> { - fn new(db: &'db dyn salsa::Database) -> Self { - Self { - db, - graph: RuntimeRepGraph { nodes: Vec::new(), recursive_groups: Vec::new() }, - cache: FxHashMap::default(), - active: FxHashSet::default(), - } - } - - fn intern_type( - &mut self, - ty: Ty<'db>, - recursive_bindings: &mut FxHashMap, - ) -> Result> { - if let TyKind::Var(id) = ty.kind(self.db) { - return recursive_bindings.get(id).copied().ok_or(RuntimeRepFailure::Unsupported(ty)); - } - - let bits = ty.as_id().as_bits(); - if let Some(&id) = self.cache.get(&bits) { - return Ok(id); - } - - if let TyKind::Rec(id, body) = ty.kind(self.db) { - let placeholder = - self.placeholder_runtime_abi(*body).ok_or(RuntimeRepFailure::Recursive(ty))?; - let rep_id = self.push_pending_node(ty, placeholder); - self.cache.insert(bits, rep_id); - let previous = recursive_bindings.insert(*id, rep_id); - let (kind, runtime_abi) = self.build_kind(*body, recursive_bindings)?; - if let Some(previous) = previous { - recursive_bindings.insert(*id, previous); - } else { - recursive_bindings.remove(id); - } - let node = &mut self.graph.nodes[rep_id.0 as usize]; - node.kind = kind; - node.runtime_abi = runtime_abi; - return Ok(rep_id); - } - - if self.active.contains(&bits) { - return Err(RuntimeRepFailure::Recursive(ty)); - } - - if let Some(placeholder) = self.placeholder_runtime_abi(ty) { - let rep_id = self.push_pending_node(ty, placeholder); - self.cache.insert(bits, rep_id); - self.active.insert(bits); - let build = self.build_kind(ty, recursive_bindings); - self.active.remove(&bits); - let (kind, runtime_abi) = build?; - let node = &mut self.graph.nodes[rep_id.0 as usize]; - node.kind = kind; - node.runtime_abi = runtime_abi; - return Ok(rep_id); - } - - self.active.insert(bits); - let build = self.build_kind(ty, recursive_bindings); - self.active.remove(&bits); - let (kind, runtime_abi) = build?; - let rep_id = RuntimeRepId(self.graph.nodes.len() as u32); - self.graph.nodes.push(RuntimeRepNode { - id: rep_id, - ty, - kind, - runtime_abi, - recursive_group: None, - contains_function: false, - }); - self.cache.insert(bits, rep_id); - Ok(rep_id) - } - - fn push_pending_node(&mut self, ty: Ty<'db>, runtime_abi: AbiTy) -> RuntimeRepId { - let rep_id = RuntimeRepId(self.graph.nodes.len() as u32); - self.graph.nodes.push(RuntimeRepNode { - id: rep_id, - ty, - kind: RuntimeRepKind::Pending, - runtime_abi, - recursive_group: None, - contains_function: false, - }); - rep_id - } - - fn build_kind( - &mut self, - ty: Ty<'db>, - recursive_bindings: &mut FxHashMap, - ) -> Result<(RuntimeRepKind<'db>, AbiTy), RuntimeRepFailure<'db>> { - Ok(match ty.kind(self.db) { - TyKind::Tuple(items) if items.is_empty() => { - (RuntimeRepKind::Unit, AbiTy::Scalar(BackendTy::Unit)) - } - TyKind::Int => (RuntimeRepKind::Int, AbiTy::Scalar(BackendTy::Int)), - TyKind::ExactInt(int_ty) => { - (RuntimeRepKind::Int, AbiTy::Scalar(exact_int_backend_ty(*int_ty))) - } - TyKind::Bool => (RuntimeRepKind::Bool, AbiTy::Scalar(BackendTy::Bool)), - TyKind::Float => (RuntimeRepKind::Float, AbiTy::Scalar(BackendTy::Float)), - TyKind::Char => (RuntimeRepKind::Char, AbiTy::Scalar(BackendTy::Char)), - TyKind::String => { - (RuntimeRepKind::String, AbiTy::Scalar(BackendTy::Ref(RefKind::String))) - } - TyKind::Pointer { .. } => (RuntimeRepKind::Int, AbiTy::Scalar(BackendTy::Int)), - TyKind::Array(item_ty) => { - let item = self.intern_type(*item_ty, recursive_bindings)?; - let item_abi = self.graph.nodes[item.0 as usize].runtime_abi.clone(); - let layout = array_runtime_layout_from_item_abi(ty, item_abi.clone()) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - ( - RuntimeRepKind::Array(RuntimeArrayRep { item_ty: *item_ty, item, layout }), - AbiTy::Scalar(BackendTy::Ref(RefKind::Array(array_ty_bits(ty)))), - ) - } - TyKind::Tuple(items) => { - let rep = self.build_fields_rep( - ty, - items.iter().copied().map(|field_ty| (None, field_ty)), - recursive_bindings, - )?; - let runtime_abi = AbiTy::Aggregate(Box::new(rep.runtime_layout.clone())); - (RuntimeRepKind::Tuple(rep), runtime_abi) - } - TyKind::Record(fields) => { - let mut ordered = fields.clone(); - ordered.sort_by_key(|(name, _)| name.text(self.db).to_owned()); - let rep = self.build_fields_rep( - ty, - ordered.into_iter().map(|(name, field_ty)| (Some(name), field_ty)), - recursive_bindings, - )?; - let runtime_abi = AbiTy::Aggregate(Box::new(rep.runtime_layout.clone())); - (RuntimeRepKind::Record(rep), runtime_abi) - } - TyKind::ExternStruct(struct_ty) => { - let rep = self.build_fields_rep( - ty, - struct_fields(self.db, *struct_ty) - .iter() - .map(|(name, field_ty)| (Some(*name), *field_ty)), - recursive_bindings, - )?; - let runtime_abi = AbiTy::Aggregate(Box::new(rep.runtime_layout.clone())); - (RuntimeRepKind::ExternStruct(rep), runtime_abi) - } - TyKind::Struct(struct_ty) => { - let rep = self.build_fields_rep( - ty, - struct_fields(self.db, *struct_ty) - .iter() - .map(|(name, field_ty)| (Some(*name), *field_ty)), - recursive_bindings, - )?; - ( - RuntimeRepKind::Struct(rep), - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(nominal_ty_bits(ty)))), - ) - } - TyKind::Enum(enum_ty) => { - let rep = self.build_enum_rep( - ty, - enum_variants(self.db, *enum_ty) - .iter() - .enumerate() - .map(|(tag, (name, fields))| (*name, tag as i32, fields.clone())), - recursive_bindings, - )?; - ( - RuntimeRepKind::Enum(rep), - AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(nominal_ty_bits(ty)))), - ) - } - TyKind::Union(items) => { - let members = items - .iter() - .map(|&member| self.intern_type(member, recursive_bindings)) - .collect::, _>>()?; - let runtime_layout = enum_layout_from_variants(members.iter().enumerate().map( - |(index, &member)| { - ( - (index as u64) + 1, - index as i32, - vec![self.graph.nodes[member.0 as usize].runtime_abi.clone()], - ) - }, - )) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - let runtime_abi = AbiTy::Aggregate(Box::new(runtime_layout.clone())); - (RuntimeRepKind::Union(RuntimeUnionRep { members, runtime_layout }), runtime_abi) - } - TyKind::Inter(items) => { - let members = items - .iter() - .map(|&member| self.intern_type(member, recursive_bindings)) - .collect::, _>>()?; - let carrier_index = choose_intersection_carrier_index(self.db, items.as_slice()) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - let carrier_rep = members[carrier_index]; - let carrier_ty = items[carrier_index]; - let carrier_bits = ty_bits(carrier_ty); - let mut seen = FxHashSet::default(); - let mut runtime_members = Vec::new(); - runtime_members.push(carrier_rep); - seen.insert(carrier_bits); - for (&member_ty, &member_rep) in items.iter().zip(members.iter()) { - let bits = ty_bits(member_ty); - if !seen.insert(bits) { - continue; - } - runtime_members.push(member_rep); - } - let (runtime_layout, runtime_abi) = if runtime_members.len() == 1 { - (None, self.graph.nodes[carrier_rep.0 as usize].runtime_abi.clone()) - } else { - let layout = - aggregate_layout_from_fields(runtime_members.iter().map(|&member| { - (None, self.graph.nodes[member.0 as usize].runtime_abi.clone()) - })) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - (Some(layout.clone()), AbiTy::Aggregate(Box::new(layout))) - }; - ( - RuntimeRepKind::Intersection(RuntimeIntersectionRep { - carrier: carrier_rep, - members: runtime_members, - runtime_layout, - }), - runtime_abi, - ) - } - TyKind::Function { inputs, output } => { - let params = inputs - .iter() - .map(|&input| self.intern_type(input, recursive_bindings)) - .collect::, _>>()?; - let result = self.intern_type(*output, recursive_bindings)?; - let signature = FunctionSignature { - params: params - .iter() - .map(|&rep| self.graph.nodes[rep.0 as usize].runtime_abi.clone()) - .collect(), - result: self.graph.nodes[result.0 as usize].runtime_abi.clone(), - }; - ( - RuntimeRepKind::Function(RuntimeFunctionRep { params, result, signature }), - function_value_abi_ty(), - ) - } - TyKind::Rec(_, _) => return Err(RuntimeRepFailure::Recursive(ty)), - TyKind::Unknown | TyKind::Var(_) => return Err(RuntimeRepFailure::Unsupported(ty)), - }) - } - - fn build_fields_rep( - &mut self, - ty: Ty<'db>, - declared_fields: impl IntoIterator>, Ty<'db>)>, - recursive_bindings: &mut FxHashMap, - ) -> Result, RuntimeRepFailure<'db>> { - let built = declared_fields - .into_iter() - .map(|(name, field_ty)| { - self.intern_type(field_ty, recursive_bindings).map(|rep| (name, field_ty, rep)) - }) - .collect::, _>>()?; - let runtime_layout = aggregate_layout_from_fields(built.iter().map(|(name, _, rep)| { - (name.map(symbol_bits), self.graph.nodes[rep.0 as usize].runtime_abi.clone()) - })) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - let runtime_fields = runtime_layout.fields().unwrap_or(&[]); - let fields = built - .into_iter() - .zip(runtime_fields.iter()) - .map(|((name, field_ty, rep), runtime_field)| RuntimeRepField { - name, - ty: field_ty, - rep, - runtime_offset: runtime_field.offset, - }) - .collect(); - Ok(RuntimeFieldsRep { fields, runtime_layout }) - } - - fn build_enum_rep( - &mut self, - ty: Ty<'db>, - declared_variants: impl IntoIterator, i32, Vec>)>, - recursive_bindings: &mut FxHashMap, - ) -> Result, RuntimeRepFailure<'db>> { - let built_variants = declared_variants - .into_iter() - .map(|(name, tag, field_tys)| { - let fields = field_tys - .into_iter() - .map(|field_ty| { - self.intern_type(field_ty, recursive_bindings).map(|rep| (field_ty, rep)) - }) - .collect::, _>>()?; - Ok::<_, RuntimeRepFailure<'db>>((name, tag, fields)) - }) - .collect::, _>>()?; - - let runtime_layout = - enum_layout_from_variants(built_variants.iter().map(|(name, tag, fields)| { - ( - symbol_bits(*name), - *tag, - fields - .iter() - .map(|(_, rep)| self.graph.nodes[rep.0 as usize].runtime_abi.clone()) - .collect::>(), - ) - })) - .ok_or(RuntimeRepFailure::Unsupported(ty))?; - let AggregateKind::Enum(runtime_enum_layout) = &runtime_layout.kind else { - return Err(RuntimeRepFailure::Unsupported(ty)); - }; - let variants = built_variants - .into_iter() - .zip(runtime_enum_layout.variants.iter()) - .map(|((name, tag, fields), runtime_variant)| RuntimeRepVariant { - name, - tag, - fields: fields - .into_iter() - .zip(runtime_variant.fields.iter()) - .map(|((field_ty, rep), runtime_field)| RuntimeRepField { - name: None, - ty: field_ty, - rep, - runtime_offset: runtime_field.offset, - }) - .collect(), - }) - .collect(); - - Ok(RuntimeEnumRep { variants, runtime_layout }) - } - - fn placeholder_runtime_abi(&self, ty: Ty<'db>) -> Option { - match ty.kind(self.db) { - TyKind::Tuple(items) if items.is_empty() => Some(AbiTy::Scalar(BackendTy::Unit)), - TyKind::Int => Some(AbiTy::Scalar(BackendTy::Int)), - TyKind::ExactInt(int_ty) => Some(AbiTy::Scalar(exact_int_backend_ty(*int_ty))), - TyKind::Bool => Some(AbiTy::Scalar(BackendTy::Bool)), - TyKind::Float => Some(AbiTy::Scalar(BackendTy::Float)), - TyKind::Char => Some(AbiTy::Scalar(BackendTy::Char)), - TyKind::String => Some(AbiTy::Scalar(BackendTy::Ref(RefKind::String))), - TyKind::Pointer { .. } => Some(AbiTy::Scalar(BackendTy::Int)), - TyKind::Array(_) => { - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Array(array_ty_bits(ty))))) - } - TyKind::Struct(_) | TyKind::Enum(_) => { - Some(AbiTy::Scalar(BackendTy::Ref(RefKind::Nominal(nominal_ty_bits(ty))))) - } - TyKind::Function { .. } => Some(function_value_abi_ty()), - TyKind::Rec(_, body) => self.placeholder_runtime_abi(*body), - TyKind::Tuple(_) - | TyKind::ExternStruct(_) - | TyKind::Record(_) - | TyKind::Union(_) - | TyKind::Inter(_) - | TyKind::Unknown - | TyKind::Var(_) => None, - } - } - - fn populate_recursive_groups(&mut self) { - #[derive(Default)] - struct TarjanState { - next_index: usize, - indices: FxHashMap, - lowlinks: FxHashMap, - stack: Vec, - on_stack: FxHashSet, - components: Vec>, - } - - fn visit<'db>(graph: &RuntimeRepGraph<'db>, index: usize, state: &mut TarjanState) { - state.indices.insert(index, state.next_index); - state.lowlinks.insert(index, state.next_index); - state.next_index += 1; - state.stack.push(index); - state.on_stack.insert(index); - - for edge in runtime_edges(graph, RuntimeRepId(index as u32)) { - let edge_index = edge.0 as usize; - if !state.indices.contains_key(&edge_index) { - visit(graph, edge_index, state); - let lowlink = state.lowlinks[&index].min(state.lowlinks[&edge_index]); - state.lowlinks.insert(index, lowlink); - } else if state.on_stack.contains(&edge_index) { - let lowlink = state.lowlinks[&index].min(state.indices[&edge_index]); - state.lowlinks.insert(index, lowlink); - } - } - - if state.lowlinks[&index] == state.indices[&index] { - let mut component = Vec::new(); - while let Some(member) = state.stack.pop() { - state.on_stack.remove(&member); - component.push(member); - if member == index { - break; - } - } - component.sort_unstable(); - state.components.push(component); - } - } - - let mut state = TarjanState::default(); - for index in 0..self.graph.nodes.len() { - if !state.indices.contains_key(&index) { - visit(&self.graph, index, &mut state); - } - } - - self.graph.recursive_groups.clear(); - for node in &mut self.graph.nodes { - node.recursive_group = None; - } - - for component in state.components { - let is_recursive = component.len() > 1 - || runtime_edges(&self.graph, RuntimeRepId(component[0] as u32)) - .contains(&RuntimeRepId(component[0] as u32)); - if !is_recursive { - continue; - } - let group_id = RecursiveRuntimeRepGroupId(self.graph.recursive_groups.len() as u32); - let members = - component.into_iter().map(|index| RuntimeRepId(index as u32)).collect::>(); - for member in &members { - self.graph.nodes[member.0 as usize].recursive_group = Some(group_id); - } - self.graph.recursive_groups.push(members); - } - } - - fn populate_contains_function(&mut self) -> Result<(), RuntimeRepFailure<'db>> { - fn visit<'db>( - graph: &RuntimeRepGraph<'db>, - id: RuntimeRepId, - memo: &mut [Option], - active: &mut FxHashSet, - ) -> Result> { - if let Some(value) = memo[id.0 as usize] { - return Ok(value); - } - if !active.insert(id) { - return Ok(false); - } - let contains = match &graph.nodes[id.0 as usize].kind { - RuntimeRepKind::Function(_) => true, - RuntimeRepKind::Array(array) => visit(graph, array.item, memo, active)?, - RuntimeRepKind::Tuple(fields) - | RuntimeRepKind::Record(fields) - | RuntimeRepKind::ExternStruct(fields) - | RuntimeRepKind::Struct(fields) => fields - .fields - .iter() - .any(|field| visit(graph, field.rep, memo, active).unwrap_or(false)), - RuntimeRepKind::Enum(shape) => shape.variants.iter().any(|variant| { - variant - .fields - .iter() - .any(|field| visit(graph, field.rep, memo, active).unwrap_or(false)) - }), - RuntimeRepKind::Union(shape) => shape - .members - .iter() - .any(|&member| visit(graph, member, memo, active).unwrap_or(false)), - RuntimeRepKind::Intersection(shape) => shape - .members - .iter() - .any(|&member| visit(graph, member, memo, active).unwrap_or(false)), - RuntimeRepKind::Pending - | RuntimeRepKind::Unit - | RuntimeRepKind::Int - | RuntimeRepKind::Bool - | RuntimeRepKind::Float - | RuntimeRepKind::Char - | RuntimeRepKind::String => false, - }; - active.remove(&id); - memo[id.0 as usize] = Some(contains); - Ok(contains) - } - - let mut memo = vec![None; self.graph.nodes.len()]; - let mut active = FxHashSet::default(); - for index in 0..self.graph.nodes.len() { - let contains = visit(&self.graph, RuntimeRepId(index as u32), &mut memo, &mut active)?; - self.graph.nodes[index].contains_function = contains; - } - Ok(()) - } -} - -fn runtime_edges<'db>(graph: &RuntimeRepGraph<'db>, id: RuntimeRepId) -> Vec { - match &graph.nodes[id.0 as usize].kind { - RuntimeRepKind::Array(array) => vec![array.item], - RuntimeRepKind::Tuple(fields) - | RuntimeRepKind::Record(fields) - | RuntimeRepKind::ExternStruct(fields) - | RuntimeRepKind::Struct(fields) => fields.fields.iter().map(|field| field.rep).collect(), - RuntimeRepKind::Enum(shape) => shape - .variants - .iter() - .flat_map(|variant| variant.fields.iter().map(|field| field.rep)) - .collect(), - RuntimeRepKind::Union(shape) => shape.members.clone(), - RuntimeRepKind::Intersection(shape) => shape.members.clone(), - RuntimeRepKind::Function(function) => { - let mut edges = function.params.clone(); - edges.push(function.result); - edges - } - RuntimeRepKind::Pending - | RuntimeRepKind::Unit - | RuntimeRepKind::Int - | RuntimeRepKind::Bool - | RuntimeRepKind::Float - | RuntimeRepKind::Char - | RuntimeRepKind::String => Vec::new(), - } -} - -fn aggregate_layout_from_fields( - fields: impl IntoIterator, AbiTy)>, -) -> Option { - let layout = layout_fields(fields.into_iter().map(|(name_bits, abi)| (name_bits, Some(abi))))?; - Some(AggregateLayout { - size: layout.size, - align: layout.align, - kind: AggregateKind::Fields(layout.fields), - }) -} - -fn choose_intersection_carrier_index( - db: &dyn salsa::Database, - members: &[Ty<'_>], -) -> Option { - members - .iter() - .enumerate() - .min_by_key(|(_, ty)| (intersection_carrier_priority(db, **ty), ty_bits(**ty))) - .map(|(index, _)| index) -} - -fn intersection_carrier_priority(db: &dyn salsa::Database, ty: Ty<'_>) -> u8 { - match ty.kind(db) { - TyKind::Record(_) => 0, - TyKind::ExternStruct(_) => 1, - TyKind::Struct(_) => 2, - TyKind::Tuple(_) => 3, - TyKind::Enum(_) => 4, - TyKind::Union(_) => 5, - TyKind::Array(_) => 6, - TyKind::String => 7, - TyKind::Bool | TyKind::Float | TyKind::Int | TyKind::ExactInt(_) | TyKind::Char => 8, - TyKind::Pointer { .. } => 9, - TyKind::Function { .. } => 10, - TyKind::Unknown | TyKind::Var(_) | TyKind::Rec(_, _) | TyKind::Inter(_) => 11, - } -} - -fn enum_layout_from_variants( - variants: impl IntoIterator)>, -) -> Option { - let mut payload_size = 0u32; - let mut payload_align = 1u32; - let mut variant_layouts = Vec::new(); - - for (name_bits, tag, fields) in variants { - let payload = layout_fields(fields.into_iter().map(|ty| (None, Some(ty))))?; - payload_size = payload_size.max(payload.size); - payload_align = payload_align.max(payload.align); - variant_layouts.push((name_bits, tag, payload)); - } - - let payload_offset = align_to(4, payload_align); - let align = 4u32.max(payload_align); - let variants = variant_layouts - .into_iter() - .map(|(name_bits, tag, payload)| VariantLayout { - name_bits, - tag, - fields: payload - .fields - .into_iter() - .map(|mut field| { - field.offset += payload_offset; - field - }) - .collect(), - }) - .collect(); - Some(AggregateLayout { - size: align_to(payload_offset + payload_size, align), - align, - kind: AggregateKind::Enum(EnumLayout { payload_offset, variants }), - }) -} - -fn array_runtime_layout_from_item_abi(ty: Ty<'_>, item_abi: AbiTy) -> Option { - let item_layout = abi_layout(&item_abi)?; - let item_align = item_layout.align.max(1); - let item_size = item_layout.size; - let item_stride = align_to(item_size, item_align); - let data_offset = align_to(ARC_HEADER_SIZE + ARRAY_HEADER_SIZE, item_align) - ARC_HEADER_SIZE; - Some(ArrayRuntimeLayout { - type_bits: array_ty_bits(ty), - item_abi, - item_size, - item_align, - item_stride, - data_offset, - }) -} diff --git a/crates/mitki-codegen-core/src/stage_intrinsic.rs b/crates/mitki-codegen-core/src/stage_intrinsic.rs deleted file mode 100644 index 3c73400..0000000 --- a/crates/mitki-codegen-core/src/stage_intrinsic.rs +++ /dev/null @@ -1,66 +0,0 @@ -use mitki_resolve::CompilerIntrinsic; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum StageIntrinsic { - TypeName, - FieldCount, - FieldName, - VariantCount, - VariantName, - FunctionParamCount, - FunctionParamTypeName, - FunctionReturnTypeName, -} - -impl StageIntrinsic { - pub fn all() -> [Self; 8] { - [ - Self::TypeName, - Self::FieldCount, - Self::FieldName, - Self::VariantCount, - Self::VariantName, - Self::FunctionParamCount, - Self::FunctionParamTypeName, - Self::FunctionReturnTypeName, - ] - } - - pub fn module() -> &'static str { - "mitki_stage" - } - - pub fn name(self) -> &'static str { - match self { - Self::TypeName => "type_name", - Self::FieldCount => "field_count", - Self::FieldName => "field_name", - Self::VariantCount => "variant_count", - Self::VariantName => "variant_name", - Self::FunctionParamCount => "function_param_count", - Self::FunctionParamTypeName => "function_param_type_name", - Self::FunctionReturnTypeName => "function_return_type_name", - } - } - - pub fn from_compiler_intrinsic(intrinsic: CompilerIntrinsic) -> Option { - match intrinsic { - CompilerIntrinsic::Comptime => None, - CompilerIntrinsic::TypeName => Some(Self::TypeName), - CompilerIntrinsic::FieldCount => Some(Self::FieldCount), - CompilerIntrinsic::FieldName => Some(Self::FieldName), - CompilerIntrinsic::VariantCount => Some(Self::VariantCount), - CompilerIntrinsic::VariantName => Some(Self::VariantName), - CompilerIntrinsic::FunctionParamCount => Some(Self::FunctionParamCount), - CompilerIntrinsic::FunctionParamTypeName => Some(Self::FunctionParamTypeName), - CompilerIntrinsic::FunctionReturnTypeName => Some(Self::FunctionReturnTypeName), - CompilerIntrinsic::StackAlloc - | CompilerIntrinsic::PtrRead - | CompilerIntrinsic::PtrWrite - | CompilerIntrinsic::PtrAdd - | CompilerIntrinsic::StrBytes - | CompilerIntrinsic::StrFromUtf8Unchecked - | CompilerIntrinsic::ArrayMutBytes => None, - } - } -} diff --git a/crates/mitki-comptime-wasm/Cargo.toml b/crates/mitki-comptime-wasm/Cargo.toml deleted file mode 100644 index d8eb87f..0000000 --- a/crates/mitki-comptime-wasm/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "mitki-comptime-wasm" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -mitki-abi.workspace = true -mitki-analysis.workspace = true -mitki-backend-wasm.workspace = true -mitki-codegen-core.workspace = true -mitki-errors.workspace = true -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-typeck.workspace = true -mitki-wasm-runtime.workspace = true -rustc-hash.workspace = true -salsa.workspace = true -wasmtime.workspace = true - -[dev-dependencies] -mitki-db.workspace = true diff --git a/crates/mitki-comptime-wasm/src/lib.rs b/crates/mitki-comptime-wasm/src/lib.rs deleted file mode 100644 index b4bc015..0000000 --- a/crates/mitki-comptime-wasm/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod stage; -mod stage_runtime; - -pub use mitki_backend_wasm::CompileOptions; -pub type WasmCodegenOptions = CompileOptions; -pub use stage::{ - compile_file_to_wasm, compile_file_to_wasm_with_options, compile_function_to_wasm, - compile_function_to_wasm_with_options, -}; diff --git a/crates/mitki-comptime-wasm/src/stage.rs b/crates/mitki-comptime-wasm/src/stage.rs deleted file mode 100644 index 24cf47e..0000000 --- a/crates/mitki-comptime-wasm/src/stage.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::cell::RefCell; -use std::sync::Arc; - -use mitki_abi::AbiValue; -use mitki_backend_wasm::{ - CompileConfig, CompileOptions, ComptimeEvaluator, compile_file, compile_function, -}; -use mitki_errors::Diagnostic; -use mitki_inputs::File; -use mitki_lower::item::scope::FunctionLocation; -use rustc_hash::FxHashMap; -use salsa::plumbing::AsId as _; - -use crate::stage_runtime::run_stage_export; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct EvalCacheKey { - db_ptr: usize, - function_bits: u64, -} - -thread_local! { - static COMPTIME_EVAL_STACK: RefCell> = const { RefCell::new(Vec::new()) }; - static COMPTIME_EVAL_CACHE: RefCell>> = - RefCell::new(FxHashMap::default()); -} - -#[derive(Default)] -struct RuntimeComptimeEvaluator; - -impl ComptimeEvaluator for RuntimeComptimeEvaluator { - fn eval_comptime_function( - &self, - db: &dyn salsa::Database, - function: FunctionLocation<'_>, - ) -> Result { - eval_comptime_function(db, function) - } -} - -fn shared_evaluator() -> Arc { - Arc::new(RuntimeComptimeEvaluator) -} - -fn eval_cache_key(db: &dyn salsa::Database, function: FunctionLocation<'_>) -> EvalCacheKey { - EvalCacheKey { - db_ptr: (db as *const dyn salsa::Database as *const ()) as usize, - function_bits: function.as_id().as_bits(), - } -} - -fn eval_comptime_function( - db: &dyn salsa::Database, - function: FunctionLocation<'_>, -) -> Result { - if COMPTIME_EVAL_STACK.with(|stack| stack.borrow().is_empty()) { - COMPTIME_EVAL_CACHE.with(|cache| cache.borrow_mut().clear()); - } - - let key = eval_cache_key(db, function); - if let Some(result) = COMPTIME_EVAL_CACHE.with(|cache| cache.borrow().get(&key).cloned()) { - return result; - } - if COMPTIME_EVAL_STACK.with(|stack| stack.borrow().contains(&key)) { - return Err("recursive `comptime` evaluation is not supported".to_owned()); - } - - COMPTIME_EVAL_STACK.with(|stack| stack.borrow_mut().push(key)); - let result = (|| { - let bytes = compile_function_to_wasm_with_options(db, function, CompileOptions).map_err( - |diagnostics| { - diagnostics.first().map_or_else( - || "comptime evaluation failed".to_owned(), - |diagnostic| diagnostic.message().to_owned(), - ) - }, - )?; - run_stage_export(db, &bytes, "__mitki_stage_root").map_err(|error| error.to_string()) - })(); - COMPTIME_EVAL_STACK.with(|stack| { - stack.borrow_mut().pop(); - }); - COMPTIME_EVAL_CACHE.with(|cache| { - cache.borrow_mut().insert(key, result.clone()); - }); - result -} - -pub fn compile_file_to_wasm( - db: &dyn salsa::Database, - file: File, -) -> Result, Vec> { - compile_file_to_wasm_with_options(db, file, CompileOptions) -} - -pub fn compile_file_to_wasm_with_options( - db: &dyn salsa::Database, - file: File, - options: CompileOptions, -) -> Result, Vec> { - compile_file(db, file, CompileConfig { options, comptime_evaluator: shared_evaluator() }) -} - -pub fn compile_function_to_wasm( - db: &dyn salsa::Database, - function: FunctionLocation<'_>, -) -> Result, Vec> { - compile_function_to_wasm_with_options(db, function, CompileOptions) -} - -pub fn compile_function_to_wasm_with_options( - db: &dyn salsa::Database, - function: FunctionLocation<'_>, - options: CompileOptions, -) -> Result, Vec> { - compile_function( - db, - function, - CompileConfig { options, comptime_evaluator: shared_evaluator() }, - ) -} diff --git a/crates/mitki-comptime-wasm/src/stage_runtime.rs b/crates/mitki-comptime-wasm/src/stage_runtime.rs deleted file mode 100644 index 5ae8c2a..0000000 --- a/crates/mitki-comptime-wasm/src/stage_runtime.rs +++ /dev/null @@ -1,620 +0,0 @@ -use anyhow::{Context as _, anyhow}; -use mitki_abi::{ - AbiValue, TransportClass, decode_canonical_blob, export_wasm_name, find_export_instance, - signature, transport_carrier_type, -}; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{FunctionLocation, enum_variants, struct_fields}; -use mitki_typeck::infer::Inferable as _; -use salsa::plumbing::FromId as _; -use wasmtime::{Caller, Extern, Linker}; - -const ARC_ALIGN: u32 = 8; -const WASM_PAGE_SIZE: u64 = 65_536; - -pub(crate) fn run_stage_export( - db: &dyn salsa::Database, - bytes: &[u8], - export: &str, -) -> anyhow::Result { - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, bytes)?; - let abi_v2 = mitki_wasm_runtime::describe_module_abi_v2(bytes)?; - let metadata = abi_v2 - .metadata - .clone() - .ok_or_else(|| anyhow!("staged execution requires `mitki.abi.v2` metadata"))?; - let instance_metadata = find_export_instance(&metadata, export)?; - let signature = signature(&metadata, instance_metadata.signature)?; - let typed_export_name = export_wasm_name(&metadata, instance_metadata)?.to_owned(); - let mut linker = Linker::new(&engine); - add_alloc_runtime_imports(&mut linker)?; - add_stage_imports(&mut linker)?; - let mut store = wasmtime::Store::new( - &engine, - StageRuntimeStore { - db: db as *const dyn salsa::Database, - allocator: GuestAllocator::default(), - }, - ); - let instance = linker.instantiate(&mut store, &module)?; - let memory = instance - .get_memory(&mut store, "memory") - .ok_or_else(|| anyhow!("staged execution requires an exported memory named `memory`"))?; - let func = instance - .get_func(&mut store, &typed_export_name) - .ok_or_else(|| anyhow!("missing stage export `{typed_export_name}`"))?; - let mut results = mitki_wasm_runtime::typed_result_slots(&metadata, &signature.result)?; - func.call(&mut store, &[], &mut results)?; - - match signature.result.transport_class { - TransportClass::Immediate => { - Ok(AbiValue::Immediate(mitki_wasm_runtime::read_immediate_result( - &metadata, - signature.result.semantic_type, - &results, - )?)) - } - TransportClass::CanonicalValue => { - let ptr = results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected canonical ABI v2 pointer stage result"))?; - let bytes = read_canonical_blob_from_store(&memory, &store, ptr)?; - let value = decode_canonical_blob(&bytes)?; - let release = - instance.get_typed_func::(&mut store, "mitki:abi/2/blob_release")?; - release.call(&mut store, ptr)?; - Ok(value) - } - TransportClass::CapabilityHandle => { - let raw = results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected handle ABI v2 stage result"))?; - Ok(AbiValue::Handle { - type_id: transport_carrier_type(&signature.result), - handle_id: i32_to_handle(raw)?, - }) - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct ActiveAllocation { - requested_size: u64, - reserved_size: u64, - align: u64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct FreeRegion { - start: u64, - len: u64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AllocSource { - Fresh { end: u64 }, - Free { index: usize, region: FreeRegion, alloc_start: u64, alloc_end: u64 }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct AllocationPlan { - ptr: u64, - requested_size: u64, - reserved_size: u64, - align: u64, - source: AllocSource, -} - -impl AllocationPlan { - fn required_end(self) -> u64 { - match self.source { - AllocSource::Fresh { end } => end, - AllocSource::Free { alloc_end, .. } => alloc_end, - } - } -} - -#[derive(Default)] -struct GuestAllocator { - frontier: Option, - active: std::collections::BTreeMap, - free_regions: Vec, -} - -impl GuestAllocator { - fn plan_alloc( - &self, - current_limit: u64, - size: u64, - align: u64, - ) -> anyhow::Result { - if align == 0 || !align.is_power_of_two() { - anyhow::bail!("runtime alloc requires a positive power-of-two alignment"); - } - - let reserved_size = size.max(1); - for (index, region) in self.free_regions.iter().copied().enumerate() { - let ptr = align_to(region.start, align); - let end = ptr - .checked_add(reserved_size) - .ok_or_else(|| anyhow!("runtime alloc overflowed the address space"))?; - let region_end = region - .start - .checked_add(region.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - if end <= region_end { - return Ok(AllocationPlan { - ptr, - requested_size: size, - reserved_size, - align, - source: AllocSource::Free { index, region, alloc_start: ptr, alloc_end: end }, - }); - } - } - - let frontier = self.frontier.unwrap_or(current_limit); - let ptr = align_to(frontier, align); - let end = - ptr.checked_add(reserved_size).ok_or_else(|| anyhow!("runtime alloc overflowed"))?; - Ok(AllocationPlan { - ptr, - requested_size: size, - reserved_size, - align, - source: AllocSource::Fresh { end }, - }) - } - - fn commit_alloc(&mut self, plan: AllocationPlan) -> anyhow::Result { - match plan.source { - AllocSource::Fresh { end } => { - self.frontier = Some(end); - } - AllocSource::Free { index, region, alloc_start, alloc_end } => { - let existing = self.free_regions.get(index).copied().ok_or_else(|| { - anyhow!("runtime allocator plan referenced a missing free slot") - })?; - if existing != region { - anyhow::bail!("runtime allocator plan was invalidated before commit"); - } - - self.free_regions.remove(index); - if alloc_start > region.start { - self.free_regions.insert( - index, - FreeRegion { start: region.start, len: alloc_start - region.start }, - ); - } - if alloc_end < region.start + region.len { - let suffix = - FreeRegion { start: alloc_end, len: region.start + region.len - alloc_end }; - let insert_at = if alloc_start > region.start { index + 1 } else { index }; - self.free_regions.insert(insert_at, suffix); - } - } - } - - if self - .active - .insert( - plan.ptr, - ActiveAllocation { - requested_size: plan.requested_size, - reserved_size: plan.reserved_size, - align: plan.align, - }, - ) - .is_some() - { - anyhow::bail!("runtime allocator produced a duplicate live pointer"); - } - - i32::try_from(plan.ptr) - .map_err(|_error| anyhow!("runtime alloc exceeded i32 address space")) - } - - fn dealloc(&mut self, ptr: i32, size: i32, align: i32) -> anyhow::Result<()> { - let ptr = u64::try_from(ptr) - .map_err(|_error| anyhow!("runtime dealloc requires a non-negative pointer"))?; - let requested_size = u64::try_from(size) - .map_err(|_error| anyhow!("runtime dealloc requires a non-negative size"))?; - let align = u64::try_from(align).map_err(|_error| { - anyhow!("runtime dealloc requires a positive power-of-two alignment") - })?; - if align == 0 || !align.is_power_of_two() { - anyhow::bail!("runtime dealloc requires a positive power-of-two alignment"); - } - - let allocation = self - .active - .get(&ptr) - .copied() - .ok_or_else(|| anyhow!("runtime dealloc received an unknown pointer"))?; - if allocation.requested_size != requested_size || allocation.align != align { - anyhow::bail!("runtime dealloc did not match the original allocation"); - } - self.active.remove(&ptr); - self.insert_free_region(ptr, allocation.reserved_size) - } - - fn insert_free_region(&mut self, start: u64, len: u64) -> anyhow::Result<()> { - if len == 0 { - return Ok(()); - } - - let mut index = self.free_regions.partition_point(|region| region.start < start); - let mut merged_start = start; - let mut merged_end = - start.checked_add(len).ok_or_else(|| anyhow!("runtime free region overflowed"))?; - - if index > 0 { - let prev = self.free_regions[index - 1]; - let prev_end = prev - .start - .checked_add(prev.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - if prev_end > merged_start { - anyhow::bail!("runtime allocator detected overlapping free regions"); - } - if prev_end == merged_start { - merged_start = prev.start; - self.free_regions.remove(index - 1); - index -= 1; - } - } - - while index < self.free_regions.len() { - let next = self.free_regions[index]; - if next.start < merged_end { - anyhow::bail!("runtime allocator detected overlapping free regions"); - } - if next.start != merged_end { - break; - } - merged_end = next - .start - .checked_add(next.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - self.free_regions.remove(index); - } - - self.free_regions - .insert(index, FreeRegion { start: merged_start, len: merged_end - merged_start }); - Ok(()) - } -} - -struct StageRuntimeStore { - db: *const dyn salsa::Database, - allocator: GuestAllocator, -} - -impl StageRuntimeStore { - fn db(&self) -> &dyn salsa::Database { - unsafe { &*self.db } - } -} - -trait AllocatingStore { - fn allocator(&self) -> &GuestAllocator; - fn allocator_mut(&mut self) -> &mut GuestAllocator; -} - -impl AllocatingStore for StageRuntimeStore { - fn allocator(&self) -> &GuestAllocator { - &self.allocator - } - - fn allocator_mut(&mut self) -> &mut GuestAllocator { - &mut self.allocator - } -} - -fn add_alloc_runtime_imports( - linker: &mut Linker, -) -> anyhow::Result<()> { - register_alloc_runtime_builtin(linker, "mitki", "alloc")?; - register_dealloc_runtime_builtin(linker, "mitki", "dealloc")?; - Ok(()) -} - -fn register_alloc_runtime_builtin( - linker: &mut Linker, - module: &str, - name: &str, -) -> anyhow::Result<()> { - linker.func_wrap( - module, - name, - |mut caller: Caller<'_, T>, size: i32, align: i32| -> wasmtime::Result { - let size = u64::try_from(size).map_err(|_error| { - wasmtime::Error::msg("runtime alloc requires a non-negative size") - })?; - let align = u64::try_from(align).map_err(|_error| { - wasmtime::Error::msg("runtime alloc requires a positive power-of-two alignment") - })?; - alloc_caller_region(&mut caller, size, align) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - Ok(()) -} - -fn register_dealloc_runtime_builtin( - linker: &mut Linker, - module: &str, - name: &str, -) -> anyhow::Result<()> { - linker.func_wrap( - module, - name, - |mut caller: Caller<'_, T>, ptr: i32, size: i32, align: i32| -> wasmtime::Result<()> { - caller.data_mut().allocator_mut().dealloc(ptr, size, align).map_err(|error| { - wasmtime::Error::msg(format!( - "runtime dealloc failed for ptr={ptr} size={size} align={align}: {error}" - )) - }) - }, - )?; - Ok(()) -} - -fn add_stage_imports(linker: &mut Linker) -> anyhow::Result<()> { - linker.func_wrap( - "mitki_stage", - "type_name", - |mut caller: Caller<'_, StageRuntimeStore>, ty_bits: i32| -> wasmtime::Result { - let db = caller.data().db(); - let ty = decode_type_id(db, ty_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let text = ty.display(db).to_string(); - write_guest_string(&mut caller, &text) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - linker.func_wrap( - "mitki_stage", - "field_count", - |caller: Caller<'_, StageRuntimeStore>, ty_bits: i32| -> wasmtime::Result { - let db = caller.data().db(); - let ty = decode_type_id(db, ty_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let TyKind::Struct(struct_ty) = ty.kind(db) else { - return Err(wasmtime::Error::msg("field_count requires a struct type")); - }; - Ok(struct_fields(db, *struct_ty).len() as i32) - }, - )?; - linker.func_wrap( - "mitki_stage", - "field_name", - |mut caller: Caller<'_, StageRuntimeStore>, - ty_bits: i32, - index: i32| - -> wasmtime::Result { - let db = caller.data().db(); - let ty = decode_type_id(db, ty_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let TyKind::Struct(struct_ty) = ty.kind(db) else { - return Err(wasmtime::Error::msg("field_name requires a struct type")); - }; - let index = usize::try_from(index).map_err(|_error| { - wasmtime::Error::msg("field_name requires a non-negative index") - })?; - let fields = struct_fields(db, *struct_ty); - let (name, _) = fields - .get(index) - .ok_or_else(|| wasmtime::Error::msg("field_name index out of range"))?; - let text = name.text(db).to_owned(); - write_guest_string(&mut caller, &text) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - linker.func_wrap( - "mitki_stage", - "variant_count", - |caller: Caller<'_, StageRuntimeStore>, ty_bits: i32| -> wasmtime::Result { - let db = caller.data().db(); - let ty = decode_type_id(db, ty_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let TyKind::Enum(enum_ty) = ty.kind(db) else { - return Err(wasmtime::Error::msg("variant_count requires an enum type")); - }; - Ok(enum_variants(db, *enum_ty).len() as i32) - }, - )?; - linker.func_wrap( - "mitki_stage", - "variant_name", - |mut caller: Caller<'_, StageRuntimeStore>, - ty_bits: i32, - index: i32| - -> wasmtime::Result { - let db = caller.data().db(); - let ty = decode_type_id(db, ty_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let TyKind::Enum(enum_ty) = ty.kind(db) else { - return Err(wasmtime::Error::msg("variant_name requires an enum type")); - }; - let index = usize::try_from(index).map_err(|_error| { - wasmtime::Error::msg("variant_name requires a non-negative index") - })?; - let variants = enum_variants(db, *enum_ty); - let (name, _) = variants - .get(index) - .ok_or_else(|| wasmtime::Error::msg("variant_name index out of range"))?; - let text = name.text(db).to_owned(); - write_guest_string(&mut caller, &text) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - linker.func_wrap( - "mitki_stage", - "function_param_count", - |caller: Caller<'_, StageRuntimeStore>, function_bits: i32| -> wasmtime::Result { - let db = caller.data().db(); - let function = decode_function_id(db, function_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - Ok(function.hir_function(db).function(db).params().len() as i32) - }, - )?; - linker.func_wrap( - "mitki_stage", - "function_param_type_name", - |mut caller: Caller<'_, StageRuntimeStore>, - function_bits: i32, - index: i32| - -> wasmtime::Result { - let db = caller.data().db(); - let function = decode_function_id(db, function_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let index = usize::try_from(index).map_err(|_error| { - wasmtime::Error::msg("function_param_type_name requires a non-negative index") - })?; - let hir_function = function.hir_function(db).function(db); - let param = *hir_function.params().get(index).ok_or_else(|| { - wasmtime::Error::msg("function_param_type_name index out of range") - })?; - let nodes = hir_function.node_store(); - let inference = function.infer(db); - let (pattern, _) = nodes.param(param); - let ty = nodes - .pattern_binding_names(pattern) - .into_iter() - .find_map(|name| inference.type_of_node(name.into())) - .unwrap_or_else(|| Ty::new(db, TyKind::Unknown)); - let text = ty.display(db).to_string(); - write_guest_string(&mut caller, &text) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - linker.func_wrap( - "mitki_stage", - "function_return_type_name", - |mut caller: Caller<'_, StageRuntimeStore>, function_bits: i32| -> wasmtime::Result { - let db = caller.data().db(); - let function = decode_function_id(db, function_bits) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - let hir_function = function.hir_function(db).function(db); - let inference = function.infer(db); - let ty = inference - .type_of_node(hir_function.body()) - .unwrap_or_else(|| Ty::new(db, TyKind::Tuple(Vec::new()))); - let text = ty.display(db).to_string(); - write_guest_string(&mut caller, &text) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - Ok(()) -} - -fn read_canonical_blob_from_store( - memory: &wasmtime::Memory, - store: &wasmtime::Store, - ptr: i32, -) -> anyhow::Result> { - let start = - usize::try_from(ptr).map_err(|_error| anyhow!("canonical blob pointer was negative"))?; - let bytes = memory.data(store); - let header = bytes - .get(start..start + 26) - .ok_or_else(|| anyhow!("canonical blob header was truncated in guest memory"))?; - let total = u32::from_le_bytes(header[22..26].try_into().expect("total len bytes")); - let end = start - .checked_add(total as usize) - .ok_or_else(|| anyhow!("canonical blob length overflowed"))?; - let blob = bytes - .get(start..end) - .ok_or_else(|| anyhow!("canonical blob was truncated in guest memory"))?; - Ok(blob.to_vec()) -} - -fn guest_memory(caller: &mut Caller<'_, T>) -> anyhow::Result { - caller - .get_export("memory") - .and_then(Extern::into_memory) - .ok_or_else(|| anyhow!("runtime import requires an exported memory named `memory`")) -} - -fn ensure_memory( - caller: &mut Caller<'_, T>, - memory: &wasmtime::Memory, - end: u64, -) -> anyhow::Result<()> { - let current = memory.data_size(&mut *caller) as u64; - if end <= current { - return Ok(()); - } - - let additional = end - current; - let pages = additional.div_ceil(WASM_PAGE_SIZE); - memory - .grow(&mut *caller, pages) - .map(|_previous| ()) - .map_err(|error| anyhow!("runtime alloc failed to grow memory: {error}")) -} - -fn align_to(offset: u64, align: u64) -> u64 { - if align <= 1 { - offset - } else { - let mask = align - 1; - (offset + mask) & !mask - } -} - -fn alloc_caller_region( - caller: &mut Caller<'_, T>, - size: u64, - align: u64, -) -> anyhow::Result { - let memory = guest_memory(caller)?; - let current_limit = memory.data_size(&*caller) as u64; - let plan = caller.data().allocator().plan_alloc(current_limit, size, align)?; - ensure_memory(caller, &memory, plan.required_end())?; - caller.data_mut().allocator_mut().commit_alloc(plan) -} - -fn decode_type_id<'db>(db: &'db dyn salsa::Database, bits: i32) -> anyhow::Result> { - let _ = db; - let bits = u32::try_from(bits).map_err(|_error| anyhow!("type id must be non-negative"))?; - Ok(Ty::from_id(salsa::Id::from_bits(u64::from(bits)))) -} - -fn decode_function_id<'db>( - db: &'db dyn salsa::Database, - bits: i32, -) -> anyhow::Result> { - let _ = db; - let bits = u32::try_from(bits).map_err(|_error| anyhow!("function id must be non-negative"))?; - Ok(FunctionLocation::from_id(salsa::Id::from_bits(u64::from(bits)))) -} - -fn write_guest_string( - caller: &mut Caller<'_, T>, - value: &str, -) -> anyhow::Result { - let bytes = value.as_bytes(); - let size = 4u64 - .checked_add(u64::try_from(bytes.len()).expect("string length should fit in u64")) - .ok_or_else(|| anyhow!("string write overflowed the address space"))?; - let ptr = alloc_caller_region(caller, size, ARC_ALIGN as u64)?; - let memory = guest_memory(caller)?; - let ptr_usize = - usize::try_from(ptr).map_err(|_error| anyhow!("string write exceeded usize memory"))?; - let len = u32::try_from(bytes.len()).map_err(|_error| anyhow!("string result is too large"))?; - memory - .write(&mut *caller, ptr_usize, &len.to_le_bytes()) - .context("failed to write string length")?; - memory.write(&mut *caller, ptr_usize + 4, bytes).context("failed to write string bytes")?; - Ok(ptr) -} - -fn i32_to_handle(raw: i32) -> anyhow::Result { - u32::try_from(raw).map_err(|_error| anyhow!("handle id was negative")) -} diff --git a/crates/mitki-db/Cargo.toml b/crates/mitki-db/Cargo.toml deleted file mode 100644 index 1f01247..0000000 --- a/crates/mitki-db/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "mitki-db" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-analysis.workspace = true -mitki-errors.workspace = true -salsa.workspace = true - -[dev-dependencies] -mitki-inputs.workspace = true -mitki-lower.workspace = true -tempfile = "3.27" diff --git a/crates/mitki-db/src/lib.rs b/crates/mitki-db/src/lib.rs deleted file mode 100644 index 453083a..0000000 --- a/crates/mitki-db/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub use mitki_analysis::check_file; -pub use mitki_errors::{Diagnostic, Level}; -use salsa::Database; - -#[salsa::db] -#[derive(Default, Clone)] -pub struct RootDatabase { - storage: salsa::Storage, -} - -#[salsa::db] -impl Database for RootDatabase {} diff --git a/crates/mitki-db/tests/modules.rs b/crates/mitki-db/tests/modules.rs deleted file mode 100644 index 1ff3d2b..0000000 --- a/crates/mitki-db/tests/modules.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fs; -use std::path::Path; - -use mitki_db::{RootDatabase, check_file}; -use mitki_inputs::File; -use mitki_lower::item::package::{ - HasPackage as _, child_module_named, module_crate_path, package_modules, root_module, -}; - -fn write_file(path: &Path, text: &str) { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).expect("create parent directories"); - } - fs::write(path, text).expect("write module file"); -} - -fn root_file(db: &RootDatabase, path: &Path, text: &str) -> File { - write_file(path, text); - File::new(db, path.to_str().expect("utf8 path").into(), text.to_owned()) -} - -#[test] -fn package_graph_loads_direct_and_nested_modules() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - let foo_path = tempdir.path().join("foo.mitki"); - let bar_path = tempdir.path().join("foo").join("bar.mitki"); - - let root = root_file(&db, &root_path, "mod foo;\n"); - write_file(&foo_path, "mod bar;\n"); - write_file(&bar_path, "fun answer(): int { 42 }\n"); - - let package = root.package(&db); - let crate_root = root_module(&db, package); - let foo = child_module_named(&db, crate_root, "foo".to_owned()).expect("foo module"); - let bar = child_module_named(&db, foo, "bar".to_owned()).expect("bar module"); - - assert_eq!(crate_root.file(&db), root); - assert_eq!(foo.file(&db).path(&db).as_str(), foo_path.to_str().expect("utf8 path")); - assert_eq!(bar.file(&db).path(&db).as_str(), bar_path.to_str().expect("utf8 path")); - assert_eq!(module_crate_path(&db, bar), "crate::foo::bar"); - assert_eq!(package_modules(&db, package).len(), 3); -} - -#[test] -fn package_graph_falls_back_to_mod_file_layout() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - let foo_mod_path = tempdir.path().join("foo").join("mod.mitki"); - - let root = root_file(&db, &root_path, "mod foo;\n"); - write_file(&foo_mod_path, "fun answer(): int { 42 }\n"); - - let package = root.package(&db); - let foo = child_module_named(&db, root_module(&db, package), "foo".to_owned()) - .expect("foo module from mod.mitki"); - - assert_eq!(foo.file(&db).path(&db).as_str(), foo_mod_path.to_str().expect("utf8 path")); - assert_eq!(module_crate_path(&db, foo), "crate::foo"); -} - -#[test] -fn typecheck_resolves_crate_paths_across_modules() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - let math_path = tempdir.path().join("math.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -mod math; - -fun main(): int { - crate::math::answer() -} -"#, - ); - write_file( - &math_path, - r#" -fun answer(): int { - 42 -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_use_imports_and_module_aliases() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::io::print_int; -use std::io as io; - -fun main() { - print_int(1); - io::print_int(2) -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_alloc_modules() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::alloc as alloc; -use std::alloc as alloc; -use std::alloc::int as int_alloc; - -fun main() { - unsafe { - val bytes: *mut u8 = alloc::alloc(4, 1) - alloc::dealloc(bytes, 4, 1) - - val ints: *mut int = int_alloc::alloc(4, 4) - int_alloc::dealloc(ints, 4, 4) - } -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_alloc_copy_and_realloc_helpers() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::alloc as alloc; -use std::alloc::int as int_alloc; - -fun main() { - unsafe { - val src: *mut int = int_alloc::alloc_items(2) - val dst: *mut int = int_alloc::alloc_items(2) - int_alloc::copy_nonoverlapping(dst, src, 2); - - val grown: *mut int = int_alloc::realloc(dst, 2, 4) - val raw: *mut u8 = alloc::alloc(4, 1) - val raw_grown: *mut u8 = alloc::realloc(raw, 4, 1, 8, 1) - alloc::copy_nonoverlapping(raw_grown, raw_grown, 4); - alloc::dealloc(raw_grown, 8, 1) - - int_alloc::dealloc_items(src, 2) - int_alloc::dealloc_items(grown, 4) - } -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_str_owned_string_helpers() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::str as strings; - -fun main(): str { - val bytes = str_bytes("hi") - unsafe { - strings::from_raw_parts_unchecked(bytes.ptr, bytes.len) - } -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_lexer_debug_dump() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::lexer; - -fun main() { - lexer::debug_dump("fun") -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_env_var() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::env; - -fun main(): str { - match env::var("PATH") { - (found, value) => if found { value } else { "" }, - } -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_std_vec_int_support_functions() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::vec::int as vec; - -fun main() { - var xs: vec::Vec = vec::new() - vec::push(xs, 20); - vec::push(xs, 22); - vec::reserve(xs, 8); - vec::set(xs, 1, 23); - val size: u32 = vec::len(xs) - val cap: u32 = vec::capacity(xs) - - val first: int = match vec::get(xs, 0) { - .Some(value) => value, - .None => 0, - } - - vec::clear(xs); - val cleared: bool = vec::is_empty(xs) - - if cleared { - if size < cap { - first; - } - } -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_resolves_method_calls_to_module_functions() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::vec::int as vec; - -fun main() { - var xs: vec::Vec = vec::new() - xs.push(20); - xs.push(22); - xs.reserve(8); - xs.set(1, 23); - val size: u32 = xs.len() - val cap: u32 = xs.capacity() - - val first: int = match xs.get(0) { - .Some(value) => value, - .None => 0, - } - - if size < cap { - first; - } - - xs.free() -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_supports_in_place_method_updates_on_var_locals() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::vec::int as vec; - -fun main() { - var xs: vec::Vec = vec::new() - xs.push(20); - xs.push(22); - xs.reserve(8); - xs.set(1, 23); - - val size: u32 = xs.len() - val cap: u32 = xs.capacity() - val first: int = match xs.get(0) { - .Some(value) => value, - .None => 0, - } - - if size < cap { - first; - } - - xs.free() -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_supports_direct_field_assignment_on_var_locals() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun main(): int { - var counter: Counter = new(40) - counter.value = counter.value + 2 - counter.value -} -"#, - ); - - let diagnostics = check_file(&db, root); - assert!(diagnostics.is_empty(), "unexpected diagnostics: {diagnostics:#?}"); -} - -#[test] -fn typecheck_requires_var_for_in_place_method_updates() { - let db = RootDatabase::default(); - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_path = tempdir.path().join("main.mitki"); - - let root = root_file( - &db, - &root_path, - r#" -use std::vec::int as vec; - -fun main() { - val xs: vec::Vec = vec::new() - xs.push(20); - xs.free() -} -"#, - ); - - let diagnostics = check_file(&db, root); - let messages = diagnostics.iter().map(|diag| diag.message()).collect::>(); - assert!( - messages - .iter() - .any(|message| message.contains("mutable parameter requires a mutable place")), - "expected mutable-place diagnostic, got {messages:?}" - ); -} diff --git a/crates/mitki-db/tests/typeck.rs b/crates/mitki-db/tests/typeck.rs deleted file mode 100644 index 7f52c05..0000000 --- a/crates/mitki-db/tests/typeck.rs +++ /dev/null @@ -1,2022 +0,0 @@ -use mitki_db::{RootDatabase, check_file}; -use mitki_errors::Diagnostic; -use mitki_inputs::File; - -#[derive(Debug, PartialEq, Eq)] -struct ExpectedDiag { - line: usize, - message: String, -} - -#[derive(Debug, PartialEq, Eq)] -struct ActualDiag { - line: usize, - message: String, -} - -fn parse_expectations(fixture: &str) -> Vec { - let mut expected = Vec::new(); - - for (idx, line) in fixture.lines().enumerate() { - let Some((_, comment)) = line.split_once("//~") else { - continue; - }; - let comment = comment.trim(); - let comment = comment.strip_prefix("ERROR").unwrap_or(comment).trim(); - if comment.is_empty() { - continue; - } - expected.push(ExpectedDiag { line: idx + 1, message: comment.to_owned() }); - } - - expected -} - -fn collect_actual(db: &RootDatabase, file: File, diagnostics: &[Diagnostic]) -> Vec { - let line_index = file.line_index(db); - let mut actual = diagnostics - .iter() - .map(|diag| { - let line = line_index.line_col(diag.range().start()).line as usize + 1; - ActualDiag { line, message: diag.message().to_owned() } - }) - .collect::>(); - actual.sort_by_key(|diag| (diag.line, diag.message.clone())); - actual -} - -#[track_caller] -fn check(fixture: &str) { - let db = RootDatabase::default(); - let file = File::new(&db, "typeck.mitki".into(), fixture.to_owned()); - - let diagnostics = check_file(&db, file); - let mut actual = collect_actual(&db, file, diagnostics); - let mut expected = parse_expectations(fixture); - - expected.sort_by_key(|diag| (diag.line, diag.message.clone())); - - assert_eq!( - expected.len(), - actual.len(), - "expected {} diagnostic(s), got {}\nexpected: {expected:#?}\nactual: {actual:#?}", - expected.len(), - actual.len(), - ); - - for expected_diag in expected { - let Some(pos) = actual.iter().position(|diag| { - diag.line == expected_diag.line && diag.message.contains(&expected_diag.message) - }) else { - panic!( - "missing diagnostic on line {} containing `{}`\nactual: {actual:#?}", - expected_diag.line, expected_diag.message - ); - }; - actual.remove(pos); - } - - assert!(actual.is_empty(), "unexpected diagnostics:\n{actual:#?}"); -} - -#[test] -fn unresolved_identifier() { - check( - r#" -fun main() { - x //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn unknown_type_annotation() { - check( - r#" -fun main() { - val x: Nope = 1 //~ ERROR Unknown type `Nope` -} -"#, - ); -} - -#[test] -fn unknown_type_is_error() { - check( - r#" -fun main() { - val f = { x in x + x } //~ ERROR cannot infer type -} -"#, - ); -} - -#[test] -fn missing_parameter_type_is_type_error() { - check( - r#" -fun main(x) { //~ ERROR Parameter type annotation is required -} -"#, - ); -} - -#[test] -fn missing_parameter_type_still_allows_inference_from_usage() { - check( - r#" -fun main(x) { //~ ERROR Inferred `int` - val y: int = x -} -"#, - ); -} - -#[test] -fn missing_parameter_type_in_callee_signature_still_infers_from_call() { - check( - r#" -fun id(x) { //~ ERROR Parameter type annotation is required -} - -fun main() { - id(42) -} -"#, - ); -} - -#[test] -fn mismatched_annotation() { - check( - r#" -fun main() { - val x: int = true //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn if_condition_must_be_bool() { - check( - r#" -fun main() { - if 1 { //~ ERROR expected `bool`, found `int` - } -} -"#, - ); -} - -#[test] -fn type_used_as_value() { - check( - r#" -fun main() { - int //~ ERROR expected value, found type `int` -} -"#, - ); -} - -#[test] -fn reserved_compiler_intrinsic_name_is_error() { - check( - r#" -fun comptime() { //~ ERROR reserved compiler intrinsic name -} -"#, - ); -} - -#[test] -fn reflection_intrinsics_require_comptime_fun() { - check( - r#" -fun main(): str { - type_name(int) //~ ERROR only allowed inside `comptime fun` -} -"#, - ); -} - -#[test] -fn comptime_requires_comptime_function_target() { - check( - r#" -fun helper(): int { - 42 -} - -fun main(): int { - comptime(helper()) //~ ERROR direct call to a top-level `comptime fun` -} -"#, - ); -} - -#[test] -fn comptime_requires_zero_arg_target() { - check( - r#" -comptime fun helper(value: int): int { - value -} - -fun main(): int { - comptime(helper(42)) //~ ERROR target function to have no parameters -} -"#, - ); -} - -#[test] -fn comptime_rejects_generic_targets() { - check( - r#" -comptime fun helper[T](): int { - 42 -} - -fun main(): int { - comptime(helper()) //~ ERROR comptime does not support generic functions -} -"#, - ); -} - -#[test] -fn binary_operator_type_mismatch() { - check( - r#" -fun main() { - 1 + true //~ ERROR cannot apply `+` to `int` and `bool` -} -"#, - ); -} - -#[test] -fn if_branch_type_mismatch() { - check( - r#" -fun main() { - if true { - 1 //~ ERROR expected `()`, found `int` - } else { - false //~ ERROR expected `()`, found `bool` - } -} -"#, - ); -} - -#[test] -fn if_missing_else_in_value_position() { - check( - r#" -fun main() { - val x: int = if true { //~ ERROR missing `else` branch - 1 - } -} -"#, - ); -} - -#[test] -fn break_outside_loop_is_error() { - check( - r#" -fun main() { - break //~ ERROR `break` is only allowed inside `loop` -} -"#, - ); -} - -#[test] -fn continue_outside_loop_is_error() { - check( - r#" -fun main() { - continue //~ ERROR `continue` is only allowed inside `loop` -} -"#, - ); -} - -#[test] -fn extern_struct_cannot_declare_destructor() { - check( - r#" -extern struct Handle { - drop(var self) { //~ ERROR `extern struct` cannot declare a destructor - } -} -"#, - ); -} - -#[test] -fn destructor_must_be_var_self() { - check( - r#" -struct Vec { - drop(self: Vec) { //~ ERROR destructor parameter type is implicit - } -} -"#, - ); -} - -#[test] -fn duplicate_destructor_is_error() { - check( - r#" -struct Vec { - drop(var self) {}, - drop(var self) {} //~ ERROR duplicate destructor declaration -} -"#, - ); -} - -#[test] -fn typed_wasm_boundary_rejects_non_copy_types() { - check( - r#" -struct Vec { - value: int, - - drop(var self) {} -} - -export fun main(value: Vec) { //~ ERROR typed Wasm imports/exports do not allow non-copy types -} -"#, - ); -} - -#[test] -fn comptime_rejects_non_copy_return_types() { - check( - r#" -struct Vec { - value: int, - - drop(var self) {} -} - -comptime fun build(): Vec { - Vec { value: 1 } -} - -fun main() { - comptime(build()) //~ ERROR return a runtime-lowerable value -} -"#, - ); -} - -#[test] -fn loop_body_is_checked_as_unit() { - check( - r#" -fun main() { - loop { - 1 //~ ERROR expected `()`, found `int` - } -} -"#, - ); -} - -#[test] -fn loop_body_resolves_params_and_locals() { - check( - r#" -struct Counter { - value: int, -} - -fun get(counter: Counter): int { - counter.value -} - -fun read(counter: Counter): int { - var result: int = 0 - loop { - val current: int = counter.get() - result = current - break - } - result -} -"#, - ); -} - -#[test] -fn call_arity_mismatch() { - check( - r#" -fun add(x: int) {} - -fun main() { - add(1, 2) //~ ERROR expected 1 argument(s), found 2 -} -"#, - ); -} - -#[test] -fn prefix_operator_type_mismatch() { - check( - r#" -fun main() { - -true //~ ERROR cannot apply `-` to `bool` -} -"#, - ); -} - -#[test] -fn postfix_operator_type_mismatch() { - check( - r#" -fun main() { - 1! //~ ERROR cannot apply postfix `!` to `int` -} -"#, - ); -} - -#[test] -fn call_non_function() { - check( - r#" -fun main() { - 1() //~ ERROR expected function, found `int` -} -"#, - ); -} - -#[test] -fn val_without_initializer() { - check( - r#" -fun main() { - val x //~ ERROR missing initializer -} -"#, - ); -} - -#[test] -fn tuple_arity_mismatch() { - check( - r#" -fun main() { - val x: (int, bool) = (1,) //~ ERROR expected 2 element(s), found 1 -} -"#, - ); -} - -#[test] -fn call_arity_too_few() { - check( - r#" -fun add(x: int, y: int) {} - -fun main() { - add(1) //~ ERROR expected 2 argument(s), found 1 -} -"#, - ); -} - -#[test] -fn prefix_operator_type_mismatch_string() { - check( - r#" -fun main() { - -"a" //~ ERROR cannot apply `-` to `str` -} -"#, - ); -} - -#[test] -fn postfix_operator_type_mismatch_bool() { - check( - r#" -fun main() { - true! //~ ERROR cannot apply postfix `!` to `bool` -} -"#, - ); -} - -// === Well-typed programs (no diagnostics expected) === - -#[test] -fn no_error_int_arithmetic() { - check( - r#" -fun main() { - 1 + 2; - 3 - 1; - 2 * 4; - 6 / 3; - 7 % 2; -} -"#, - ); -} - -#[test] -fn no_error_float_arithmetic() { - check( - r#" -fun main() { - 1.0 + 2.0; - 3.0 - 1.0; - 2.0 * 4.0; - 6.0 / 3.0; -} -"#, - ); -} - -#[test] -fn no_error_comparison() { - check( - r#" -fun main() { - 1 == 2; - 1 != 2; - 1 < 2; - 1 > 2; - 1 <= 2; - 1 >= 2; -} -"#, - ); -} - -#[test] -fn no_error_logical() { - check( - r#" -fun main() { - true && false; - true || false; -} -"#, - ); -} - -#[test] -fn no_error_prefix_negate() { - check( - r#" -fun main() { - -1; - -1.0; - !true; -} -"#, - ); -} - -#[test] -fn no_error_if_else_matching_branches() { - check( - r#" -fun main(): int { - if true { 1 } else { 2 } -} -"#, - ); -} - -#[test] -fn no_error_function_call() { - check( - r#" -fun add(x: int, y: int): int { x + y } - -fun main() { - add(1, 2); -} -"#, - ); -} - -#[test] -fn no_error_val_binding() { - check( - r#" -fun main() { - val x = 1 - val y: int = 2 - x + y; -} -"#, - ); -} - -#[test] -fn no_error_closure_literal() { - check( - r#" -fun main() { - val f = { x in x } -} -"#, - ); -} - -#[test] -fn no_error_empty_array_with_annotation() { - check( - r#" -fun main(): [int] { - [] -} -"#, - ); -} - -#[test] -fn no_error_nested_if() { - check( - r#" -fun main(): int { - if true { - if false { 1 } else { 2 } - } else { - 3 - } -} -"#, - ); -} - -#[test] -fn no_error_empty_function() { - check( - r#" -fun noop() {} -"#, - ); -} - -// === Additional error tests === - -#[test] -fn binary_float_int_mismatch() { - check( - r#" -fun main() { - 1.0 + 1 //~ ERROR cannot apply `+` to `float` and `int` -} -"#, - ); -} - -#[test] -fn binary_logical_non_bool() { - check( - r#" -fun main() { - 1 && 2 //~ ERROR cannot apply `&&` to `int` and `int` -} -"#, - ); -} - -#[test] -fn prefix_negate_string() { - check( - r#" -fun main() { - !"hello" //~ ERROR cannot apply `!` to `str` -} -"#, - ); -} - -#[test] -fn return_type_mismatch() { - check( - r#" -fun foo(): int { - true //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn multiple_errors() { - check( - r#" -fun main() { - x; //~ ERROR Unresolved identifier - y; //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn call_wrong_arg_type() { - check( - r#" -fun foo(x: int) {} - -fun main() { - foo(true) //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn annotation_bool_given_int() { - check( - r#" -fun main() { - val x: bool = 1 //~ ERROR expected `bool`, found `int` -} -"#, - ); -} - -#[test] -fn comparison_type_mismatch() { - check( - r#" -fun main() { - 1 == true //~ ERROR cannot apply `==` to `int` and `bool` -} -"#, - ); -} - -#[test] -fn array_literal_item_type_mismatch() { - check( - r#" -fun main(): [int] { - [1, true] //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn array_repeat_length_must_be_int() { - check( - r#" -fun main(): [int] { - [1; true] //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn array_ordering_is_rejected() { - check( - r#" -fun main() { - [1, 2] < [1, 3] //~ ERROR cannot apply `<` to `[int]` and `[int]` -} -"#, - ); -} - -#[test] -fn if_condition_string() { - check( - r#" -fun main() { - if "hello" {} //~ ERROR expected `bool`, found `str` -} -"#, - ); -} - -#[test] -fn call_too_many_args_zero_params() { - check( - r#" -fun noop() {} - -fun main() { - noop(1) //~ ERROR expected 0 argument(s), found 1 -} -"#, - ); -} - -#[test] -fn call_wrong_arg_count_two_params() { - check( - r#" -fun add(x: int, y: int): int { x + y } - -fun main() { - add(1, 2, 3); //~ ERROR expected 2 argument(s), found 3 -} -"#, - ); -} - -#[test] -fn no_error_generic_identity() { - check( - r#" -fun id[T](x: T): T { x } - -fun main() { - id(42); - id("hello"); -} -"#, - ); -} - -#[test] -fn no_error_generic_two_params() { - check( - r#" -fun pair[A, B](a: A, b: B): (A, B) { (a, b) } - -fun main() { - pair(1, true); -} -"#, - ); -} - -#[test] -fn no_error_let_polymorphism_identity() { - check( - r#" -fun main() { - val id = { x in x } - val a: int = id(1) - val b: bool = id(true) -} -"#, - ); -} - -#[test] -fn let_polymorphism_does_not_leak_outer_var() { - check( - r#" -fun main() { - val bad = { x in - val g = { y in x } - (g(1), g(true)) - } - val t: (int, bool) = bad(0) //~ ERROR expected `(int, bool)`, found `(int, int)` -} -"#, - ); -} - -#[test] -fn generic_wrong_return_type() { - check( - r#" -fun id[T](x: T): T { x } - -fun main() { - val x: int = id(true) //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn generic_struct_type_application_substitutes_fields() { - check( - r#" -struct Vec[T] { - items: [T], -} - -fun main() { - val xs: Vec[int] = Vec { items: [1, 2, 3] } - val items: [int] = xs.items -} -"#, - ); -} - -// === Struct and enum tests === - -#[test] -fn struct_type_used_as_type_annotation() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun origin(): Point { origin() } -"#, - ); -} - -#[test] -fn struct_type_used_as_value_is_error() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main() { - Point //~ ERROR expected value, found type `Point` -} -"#, - ); -} - -#[test] -fn enum_type_used_as_type_annotation() { - check( - r#" -enum Color { - Red, - Green, - Blue, -} - -fun paint(): Color { paint() } -"#, - ); -} - -#[test] -fn enum_type_used_as_value_is_error() { - check( - r#" -enum Color { - Red, - Green, - Blue, -} - -fun main() { - Color //~ ERROR expected value, found type `Color` -} -"#, - ); -} - -#[test] -fn struct_type_mismatch() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun make_point(): Point { make_point() } - -fun main() { - val x: int = make_point() //~ ERROR expected `int`, found `Point` -} -"#, - ); -} - -#[test] -fn enum_type_mismatch() { - check( - r#" -enum Color { - Red, - Green, - Blue, -} - -fun make_color(): Color { make_color() } - -fun main() { - val x: int = make_color() //~ ERROR expected `int`, found `Color` -} -"#, - ); -} - -// === Struct expression tests === - -#[test] -fn no_error_struct_expr() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - Point { x: 1, y: 2 } -} -"#, - ); -} - -#[test] -fn struct_expr_field_type_mismatch() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - Point { x: true, y: 2 } //~ ERROR expected `int`, found `bool` -} -"#, - ); -} - -#[test] -fn struct_expr_missing_field() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - Point { x: 1 } //~ ERROR missing field `y` -} -"#, - ); -} - -#[test] -fn struct_expr_unknown_field() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - Point { x: 1, y: 2, z: 3 } //~ ERROR unknown field `z` -} -"#, - ); -} - -#[test] -fn struct_expr_not_a_struct() { - check( - r#" -enum Color { - Red, - Green, - Blue, -} - -fun main() { - Color { x: 1 } //~ ERROR `Color` is not a struct -} -"#, - ); -} - -#[test] -fn no_error_struct_field_projection() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main() { - val p = Point { x: 1, y: 2 }; - val x: int = p.x; -} -"#, - ); -} - -#[test] -fn struct_field_projection_unknown_field() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main() { - val p = Point { x: 1, y: 2 }; - p.z //~ ERROR unknown field `z` -} -"#, - ); -} - -#[test] -fn struct_field_projection_not_a_struct() { - check( - r#" -fun main() { - true.x //~ ERROR `bool` is not a struct -} -"#, - ); -} - -#[test] -fn enum_variant_access_as_value() { - check( - r#" -enum Color { - Red, - Green, -} - -fun takes_color(color: Color) {} - -fun main() { - val c = Color.Red; - takes_color(c); -} -"#, - ); -} - -#[test] -fn enum_variant_constructor_call() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun takes_option(value: Option) {} - -fun main() { - val x = Option.Some(1); - val y = Option.None; - takes_option(x); - takes_option(y); -} -"#, - ); -} - -#[test] -fn enum_unknown_variant_is_error() { - check( - r#" -enum Color { - Red, -} - -fun main() { - Color.Blue //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn enum_variant_access_without_type_prefix() { - check( - r#" -enum Color { - Red, -} - -fun takes_color(color: Color) {} - -fun main() { - val n = .Red; - takes_color(n); -} -"#, - ); -} - -#[test] -fn enum_variant_access_without_type_prefix_ambiguous_is_error() { - check( - r#" -enum Color { - Red, -} - -enum Light { - Red, -} - -fun main() { - .Red //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn enum_variant_constructor_without_type_prefix() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun takes_option(value: Option) {} - -fun main() { - val x = .Some(1); - val y = .None; - takes_option(x); - takes_option(y); -} -"#, - ); -} - -#[test] -fn enum_variant_without_type_prefix_not_emitted_too_early() { - check( - r#" -enum Color { - Red, -} - -fun id[T](x: T): T { x } - -fun main() { - val c: Color = id(.Red); -} -"#, - ); -} - -#[test] -fn enum_variant_without_type_prefix_disambiguated_by_annotation() { - check( - r#" -enum Color { - Red, -} - -enum Light { - Red, -} - -fun takes_color(color: Color) {} - -fun main() { - val c: Color = .Red; - takes_color(c); -} -"#, - ); -} - -#[test] -fn enum_variant_access_without_type_prefix_without_context_is_error() { - check( - r#" -enum Color { - Red, -} - -fun main() { - .Red //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn enum_variant_constructor_without_type_prefix_without_context_is_error() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun main() { - .Some(1) //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn enum_variant_without_type_prefix_conflicting_uses_reports_error() { - check( - r#" -enum Color { - Red, -} - -fun main() { - val n = .Red; - val b: Color = n; - val q: int = n; //~ ERROR expected `int`, found `Color` -} -"#, - ); -} - -#[test] -fn enum_variant_without_type_prefix_conflicting_uses_order_independent() { - check( - r#" -enum Color { - Red, -} - -fun main() { - val n = .Red; - val q: int = n; //~ ERROR expected `int`, found `Color` - val b: Color = n; -} -"#, - ); -} - -#[test] -fn enum_variant_constructor_without_type_prefix_conflicting_uses_reports_error() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun main() { - val n = .Some(1); - val ok: Option = n; - val bad: int = n; //~ ERROR expected `int`, found `Option` -} -"#, - ); -} - -#[test] -fn enum_variant_without_type_prefix_with_wrong_expected_type_is_error() { - check( - r#" -enum Color { - Red, -} - -fun main() { - val q: int = .Red //~ ERROR Unresolved identifier -} -"#, - ); -} - -#[test] -fn malformed_function_body_does_not_panic_diagnostic_mapping() { - let db = RootDatabase::default(); - let file = File::new( - &db, - "typeck.mitki".into(), - r#" -fun main() { - fun nested() {} -} -"# - .to_owned(), - ); - - let diagnostics = check_file(&db, file); - assert!( - !diagnostics.is_empty(), - "expected at least one diagnostic for malformed function body" - ); -} - -#[test] -fn malformed_field_expr_does_not_panic() { - let db = RootDatabase::default(); - let file = File::new( - &db, - "typeck.mitki".into(), - r#" -fun main() { - val x = . -} -"# - .to_owned(), - ); - - let diagnostics = check_file(&db, file); - assert!( - !diagnostics.is_empty(), - "expected at least one diagnostic for malformed field expression" - ); -} - -#[test] -fn no_error_anonymous_record_literal_and_field_access() { - check( - r#" -fun main() { - val obj = { x: 42, y: true }; - val x: int = obj.x; - val y: bool = obj.y; -} -"#, - ); -} - -#[test] -fn anonymous_record_unknown_field_is_error() { - check( - r#" -fun main() { - val obj = { x: 42 }; - obj.y //~ ERROR unknown field `y` -} -"#, - ); -} - -#[test] -fn no_error_if_with_anonymous_records() { - check( - r#" -fun main() { - val a = { x: 1, y: true }; - val b = { x: 2, y: false }; - val c = if true { a } else { b }; - val x: int = c.x; -} -"#, - ); -} - -#[test] -fn self_recursive_enum_typechecks() { - check( - r#" -enum List { - Nil, - Cons(int, List), -} - -fun main() { - val list = List.Cons(1, List.Nil); -} -"#, - ); -} - -#[test] -fn mutually_recursive_enums_typecheck() { - check( - r#" -enum Even { - Zero, - Succ(Odd), -} - -enum Odd { - Succ(Even), -} - -fun main() { - val even = Even.Succ(Odd.Succ(Even.Zero)); -} -"#, - ); -} - -#[test] -fn wasm_import_without_body_is_allowed() { - check( - r#" -import "env" fun host(x: int): int; - -fun main(): int { - 0 -} -"#, - ); -} - -#[test] -fn wasm_import_body_is_error() { - check( - r#" -import "env" fun host(): int { //~ ERROR Imported Wasm functions cannot have a body - 0 -} -"#, - ); -} - -#[test] -fn missing_function_body_is_error() { - check( - r#" -export fun answer(): int; //~ ERROR Function body is required -"#, - ); -} - -#[test] -fn generic_wasm_export_is_error() { - check( - r#" -export fun answer[T](): int { //~ ERROR Wasm imports and exports do not support generic functions - 0 -} -"#, - ); -} - -#[test] -fn generic_wasm_import_origin_is_allowed_with_instance() { - check( - r#" -import "env" fun id[T](value: T): T; -import instance id[int]; - -fun main(): int { - id(0) -} -"#, - ); -} - -#[test] -fn boundary_instance_target_must_exist() { - check( - r#" -export instance id[int]; //~ ERROR unknown boundary instance target `id` -"#, - ); -} - -#[test] -fn boundary_instance_target_must_be_generic() { - check( - r#" -fun id(value: int): int { - value -} - -export instance id[int]; //~ ERROR boundary instance target `id` must be a generic function -"#, - ); -} - -#[test] -fn import_instance_requires_imported_generic_function() { - check( - r#" -fun id[T](value: T): T { - value -} - -import instance id[int]; //~ ERROR import instance target `id` must be an imported generic function -"#, - ); -} - -#[test] -fn export_instance_requires_non_import_generic_function() { - check( - r#" -import "env" fun id[T](value: T): T; - -export instance id[int]; //~ ERROR export instance target `id` must be a non-import generic function -"#, - ); -} - -#[test] -fn boundary_instance_type_arg_arity_is_checked() { - check( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int, bool]; //~ ERROR boundary instance target `id` expects 1 type argument(s), found 2 -"#, - ); -} - -#[test] -fn duplicate_boundary_instances_are_rejected() { - check( - r#" -fun id[T](value: T): T { - value -} - -export instance id[int]; -export instance id[int]; //~ ERROR duplicate boundary instance declaration for `id` -"#, - ); -} - -#[test] -fn runtime_name_conflict_is_allowed() { - check( - r#" -fun print_str(value: str) { -} - -fun main() { - print_str("ok") -} -"#, - ); -} - -#[test] -fn non_exhaustive_match_is_error() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun main(value: Option): int { - match value { //~ ERROR non-exhaustive match - .Some(x) => x - } -} -"#, - ); -} - -#[test] -fn unreachable_match_arm_is_error() { - check( - r#" -fun main(value: bool): int { - match value { - _ => 1, - false => 0 //~ ERROR unreachable match arm - } -} -"#, - ); -} - -#[test] -fn union_match_literal_patterns_typecheck() { - check( - r#" -fun main(value: int | bool): int { - match value { - true => 1, - false => 0, - _ => 2, - } -} -"#, - ); -} - -#[test] -fn union_match_typed_binding_patterns_typecheck() { - check( - r#" -fun takes_int(value: int): str { - "int" -} - -fun main(value: int | str): str { - match value { - s: str => s, - n: int => takes_int(n), - } -} -"#, - ); -} - -#[test] -fn union_match_typed_wildcard_pattern_typechecks() { - check( - r#" -fun main(value: int | str): str { - match value { - _: int => "int", - s: str => s, - } -} -"#, - ); -} - -#[test] -fn union_match_tuple_member_typechecks() { - check( - r#" -fun main(value: int | (bool, int)): int { - match value { - (flag, n) => if flag { n } else { 0 }, - _ => 1, - } -} -"#, - ); -} - -#[test] -fn union_match_tuple_member_with_typed_subpatterns_typechecks() { - check( - r#" -fun main(value: int | (int, str)): int { - match value { - (n: int, _: str) => n, - _ => 0, - } -} -"#, - ); -} - -#[test] -fn union_match_struct_member_typechecks() { - check( - r#" -struct Box { - value: int, -} - -fun main(value: int | Box): int { - match value { - Box { value } => value, - _ => 0, - } -} -"#, - ); -} - -#[test] -fn union_match_struct_member_with_typed_subpatterns_typechecks() { - check( - r#" -struct Box { - value: int, -} - -fun main(value: int | Box): int { - match value { - Box { value: n: int } => n, - _ => 0, - } -} -"#, - ); -} - -#[test] -fn union_match_enum_member_typechecks() { - check( - r#" -enum Choice { - Some(int), - None, -} - -fun main(value: int | Choice): int { - match value { - .Some(n) => n, - .None => 0, - _ => 1, - } -} -"#, - ); -} - -#[test] -fn union_match_enum_member_with_typed_subpatterns_typechecks() { - check( - r#" -enum Choice { - Some(int), - None, -} - -fun main(value: int | Choice): int { - match value { - .Some(n: int) => n, - .None => 0, - _ => 1, - } -} -"#, - ); -} - -#[test] -fn ambiguous_union_pattern_is_error() { - check( - r#" -fun main(value: (int, int) | (bool, bool)): int { - match value { - (x, y) => 0 //~ ERROR pattern matches multiple members of union - } -} -"#, - ); -} - -#[test] -fn ambiguous_typed_union_pattern_is_error() { - check( - r#" -struct Box { - value: int, -} - -fun main(value: Box | { value: int }): int { - match value { - v: { value: int } => 0 //~ ERROR pattern matches multiple members of union - } -} -"#, - ); -} - -#[test] -fn literal_pattern_mismatch_points_at_pattern() { - check( - r#" -fun main(value: int): int { - match value { - true => //~ ERROR expected `bool`, found `int` - 1, - _ => 0, - } -} -"#, - ); -} - -#[test] -fn typed_pattern_mismatch_points_at_pattern() { - check( - r#" -fun main(value: int): int { - match value { - _: str => 0, //~ ERROR expected `str`, found `int` - _ => 1, - } -} -"#, - ); -} - -#[test] -fn unknown_type_in_typed_pattern_is_error() { - check( - r#" -fun main(value: int | str): int { - match value { - x: Missing => 0, //~ ERROR Unknown type `Missing` - _ => 1, - } -} -"#, - ); -} - -#[test] -fn refutable_val_pattern_is_error() { - check( - r#" -fun main() { //~ ERROR refutable patterns are only allowed in `match` arms - val 0 = 1 -} -"#, - ); -} - -#[test] -fn refutable_param_pattern_is_error() { - check( - r#" -fun main(0: int) { //~ ERROR refutable patterns are only allowed in `match` arms -} -"#, - ); -} - -#[test] -fn duplicate_pattern_binding_is_error() { - check( - r#" -fun main() { - val (x, x) = (1, 2) //~ ERROR duplicate binding in pattern -} -"#, - ); -} - -#[test] -fn struct_pattern_missing_field_is_error() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(point: Point): int { - match point { - Point { x } => x //~ ERROR missing field `y` - } -} -"#, - ); -} - -#[test] -fn struct_pattern_unknown_field_is_error() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main(point: Point): int { - match point { - Point { x, y, z } => x //~ ERROR unknown field `z` - } -} -"#, - ); -} - -#[test] -fn variant_pattern_unknown_variant_is_error() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun main(value: Option): int { - match value { - .Other => 0 //~ ERROR Unresolved identifier - } -} -"#, - ); -} - -#[test] -fn float_pattern_is_not_supported() { - check( - r#" -fun main(value: float): int { - match value { - 1.0 => 1, //~ ERROR float patterns are not supported - _ => 0, - } -} -"#, - ); -} diff --git a/crates/mitki-errors/Cargo.toml b/crates/mitki-errors/Cargo.toml deleted file mode 100644 index b774fcb..0000000 --- a/crates/mitki-errors/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "mitki-errors" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -annotate-snippets = "0.11" -salsa.workspace = true -text-size.workspace = true diff --git a/crates/mitki-errors/src/lib.rs b/crates/mitki-errors/src/lib.rs deleted file mode 100644 index 199cd6f..0000000 --- a/crates/mitki-errors/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt::Display; - -use annotate_snippets::Snippet; -pub use annotate_snippets::{Level, Renderer}; -pub use text_size::TextRange; - -#[derive(salsa::Update, PartialEq, Clone, Debug)] -pub struct Diagnostic { - level: Level, - message: String, - range: TextRange, - file: Option, -} - -impl Diagnostic { - pub fn new(level: Level, message: impl Into, range: TextRange) -> Self { - Self { level, message: message.into(), range, file: None } - } - - pub fn error(message: impl Into, range: TextRange) -> Self { - Self::new(Level::Error, message, range) - } - - pub fn level(&self) -> Level { - self.level - } - - pub fn message(&self) -> &str { - &self.message - } - - pub fn range(&self) -> TextRange { - self.range - } - - pub fn file(&self) -> Option<&str> { - self.file.as_deref() - } - - pub fn with_file(mut self, file: impl Into) -> Self { - self.file = Some(file.into()); - self - } - - pub fn render<'a>( - &'a self, - renderer: &'a Renderer, - path: &'a str, - text: &'a str, - ) -> impl Display + 'a { - let message = self.level.title(&self.message).snippet( - Snippet::source(text) - .origin(path) - .annotation(Level::Error.span(self.range.into()).label("here")) - .fold(true), - ); - renderer.render(message) - } -} diff --git a/crates/mitki-hir-macros/Cargo.toml b/crates/mitki-hir-macros/Cargo.toml deleted file mode 100644 index b3c48a7..0000000 --- a/crates/mitki-hir-macros/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "mitki-hir-macros" -version = "0.1.0" -edition.workspace = true -license.workspace = true - -[lib] -proc-macro = true - -[lints] -workspace = true - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = { version = "2", features = ["full"] } diff --git a/crates/mitki-hir-macros/src/lib.rs b/crates/mitki-hir-macros/src/lib.rs deleted file mode 100644 index 04fdb43..0000000 --- a/crates/mitki-hir-macros/src/lib.rs +++ /dev/null @@ -1,769 +0,0 @@ -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::parse::{Parse, ParseStream}; -use syn::{Ident, Result, Token, braced, parenthesized}; - -#[proc_macro] -pub fn define_hir(input: TokenStream) -> TokenStream { - let schema = syn::parse_macro_input!(input as Schema); - schema.expand().into() -} - -struct Schema { - categories: Vec, - nodes: Vec, -} - -impl Parse for Schema { - fn parse(input: ParseStream<'_>) -> Result { - let categories_ident: Ident = input.parse()?; - if categories_ident != "categories" { - return Err(input.error("expected `categories`")); - } - - let categories_content; - braced!(categories_content in input); - let mut categories = Vec::new(); - while !categories_content.is_empty() { - let ident: Ident = categories_content.parse()?; - categories.push(ident); - if categories_content.peek(Token![,]) { - let _comma: Token![,] = categories_content.parse()?; - } - } - - let nodes_ident: Ident = input.parse()?; - if nodes_ident != "nodes" { - return Err(input.error("expected `nodes`")); - } - let nodes_content; - braced!(nodes_content in input); - let mut nodes = Vec::new(); - while !nodes_content.is_empty() { - nodes.push(nodes_content.parse()?); - if nodes_content.peek(Token![;]) { - let _semi: Token![;] = nodes_content.parse()?; - } - } - - Ok(Self { categories, nodes }) - } -} - -struct NodeDef { - name: Ident, - layout: Ident, - fields: Vec, - category: Option, -} - -impl Parse for NodeDef { - fn parse(input: ParseStream<'_>) -> Result { - let name: Ident = input.parse()?; - let _colon: Token![:] = input.parse()?; - let layout: Ident = input.parse()?; - - let mut fields = Vec::new(); - if input.peek(syn::token::Paren) { - let fields_content; - parenthesized!(fields_content in input); - while !fields_content.is_empty() { - fields.push(fields_content.parse()?); - if fields_content.peek(Token![,]) { - let _comma: Token![,] = fields_content.parse()?; - } - } - } - - let _fat_arrow: Token![=>] = input.parse()?; - let category = if input.peek(Token![_]) { - let _underscore: Token![_] = input.parse()?; - None - } else { - Some(input.parse()?) - }; - - Ok(Self { name, layout, fields, category }) - } -} - -struct FieldDef { - name: Ident, - ty: TypeSpec, - arg_ty: Option, -} - -impl Parse for FieldDef { - fn parse(input: ParseStream<'_>) -> Result { - let name: Ident = input.parse()?; - let _colon: Token![:] = input.parse()?; - let ty: TypeSpec = input.parse()?; - - let arg_ty = if input.peek(Token![<]) && input.peek2(Token![-]) { - let _lt: Token![<] = input.parse()?; - let _dash: Token![-] = input.parse()?; - let arg: TypeSpec = input.parse()?; - Some(arg) - } else { - None - }; - - Ok(Self { name, ty, arg_ty }) - } -} - -#[derive(Clone, Debug)] -enum TypeSpec { - Symbol, - OptionSymbol, - Node(Ident), -} - -impl TypeSpec { - fn parse_option(input: ParseStream<'_>) -> Result { - let _lt: Token![<] = input.parse()?; - let inner: Ident = input.parse()?; - let _gt: Token![>] = input.parse()?; - if inner == "Symbol" { - Ok(TypeSpec::OptionSymbol) - } else { - Err(input.error("Option<> only supports Symbol")) - } - } -} - -impl Parse for TypeSpec { - fn parse(input: ParseStream<'_>) -> Result { - let ident: Ident = input.parse()?; - if ident == "Symbol" { - return Ok(TypeSpec::Symbol); - } - if ident == "Option" { - if input.peek(Token![<]) { - return TypeSpec::parse_option(input); - } - return Err(input.error("Option requires type arguments")); - } - Ok(TypeSpec::Node(ident)) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum LayoutKind { - BindingInLhs, - ListRange, - CallRange, - BlockWithTail, - TripleLane, - Direct2, - ZeroZero, -} - -impl LayoutKind { - fn parse(ident: &Ident) -> Result { - match ident.to_string().as_str() { - "BindingInLhs" => Ok(LayoutKind::BindingInLhs), - "ListRange" => Ok(LayoutKind::ListRange), - "CallRange" => Ok(LayoutKind::CallRange), - "BlockWithTail" => Ok(LayoutKind::BlockWithTail), - "TripleLane" => Ok(LayoutKind::TripleLane), - "Direct2" => Ok(LayoutKind::Direct2), - "ZeroZero" => Ok(LayoutKind::ZeroZero), - _ => Err(syn::Error::new_spanned(ident, "unknown layout")), - } - } -} - -impl Schema { - fn expand(&self) -> proc_macro2::TokenStream { - let categories = &self.categories; - let node_names: Vec<_> = self.nodes.iter().map(|node| node.name.clone()).collect(); - - let mut layout_nodes = Vec::new(); - for node in &self.nodes { - let layout = LayoutKind::parse(&node.layout).unwrap(); - layout_nodes.push((node, layout)); - } - - let node_kind_variants = node_names.iter(); - - let tag_defs = { - let mut defs = Vec::new(); - defs.push(quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] pub(crate) struct HirTag; }); - defs.push(quote! { pub(crate) type HirId = crate::hir::id::Id; }); - defs.push(quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] pub(crate) struct SymTag; }); - defs.push(quote! { pub(crate) type SymId = crate::hir::id::Id; }); - defs.push(quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] pub(crate) struct IxTag; }); - defs.push(quote! { pub(crate) type IxId = crate::hir::id::Id; }); - for cat in categories { - let tag = format_ident!("{}Tag", cat); - let id = format_ident!("{}Id", cat); - defs.push(quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] pub struct #tag; }); - defs.push(quote! { pub type #id = crate::hir::id::Id<#tag>; }); - } - for node in &node_names { - let tag = format_ident!("{}Tag", node); - let id = format_ident!("{}Id", node); - defs.push(quote! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] pub struct #tag; }); - defs.push(quote! { pub type #id = crate::hir::id::Id<#tag>; }); - } - quote! { #(#defs)* } - }; - - let mut node_id_trait_impls = Vec::new(); - let mut node_id_trait_impls_categories = Vec::new(); - - for node in &node_names { - let id = format_ident!("{}Id", node); - node_id_trait_impls.push(quote! { - impl sealed::Sealed for #id {} - impl NodeId for #id { - fn raw(self) -> u32 { - self.raw().0 - } - } - }); - } - for cat in categories { - let id = format_ident!("{}Id", cat); - node_id_trait_impls_categories.push(quote! { - impl sealed::Sealed for #id {} - impl NodeId for #id { - fn raw(self) -> u32 { - self.raw().0 - } - } - }); - } - - let mut list_wrappers = Vec::new(); - let mut list_wrapper_names = Vec::new(); - let mut list_types = Vec::new(); - - for (node, layout) in &layout_nodes { - match layout { - LayoutKind::ListRange => { - if let Some(field) = node.fields.first() { - list_types.push(field.ty.clone()); - } - } - LayoutKind::CallRange if node.fields.len() > 1 => { - list_types.push(node.fields[1].ty.clone()); - } - LayoutKind::BlockWithTail => { - if let Some(field) = node.fields.first() { - list_types.push(field.ty.clone()); - } - } - _ => {} - } - } - - list_types.sort_by_key(format_type); - list_types.dedup_by(|a, b| format_type(a) == format_type(b)); - - for ty in &list_types { - if let Some((wrapper, id_ty)) = list_wrapper_for(ty) { - if list_wrapper_names.iter().any(|it| it == &wrapper) { - continue; - } - let wrapper_name = wrapper.clone(); - let id_ty = id_ty.clone(); - let iter_ty = quote! { impl Iterator + '_ }; - list_wrappers.push(quote! { - pub struct #wrapper_name<'a> { - slice: &'a [u32], - } - - impl<'a> #wrapper_name<'a> { - pub fn len(&self) -> usize { self.slice.len() } - pub fn is_empty(&self) -> bool { self.slice.is_empty() } - pub fn iter(&self) -> #iter_ty { - self.slice - .iter() - .copied() - .map(|raw| #id_ty::from_raw(crate::hir::id::Raw(raw))) - } - pub fn get(&self, index: usize) -> Option<#id_ty> { - self.slice - .get(index) - .copied() - .map(|raw| #id_ty::from_raw(crate::hir::id::Raw(raw))) - } - } - - impl<'a> IntoIterator for #wrapper_name<'a> { - type Item = #id_ty; - type IntoIter = std::iter::Map< - std::iter::Copied>, - fn(u32) -> #id_ty, - >; - fn into_iter(self) -> Self::IntoIter { - fn map_fn(raw: u32) -> #id_ty { - #id_ty::from_raw(crate::hir::id::Raw(raw)) - } - self.slice.iter().copied().map(map_fn) - } - } - }); - list_wrapper_names.push(wrapper_name); - } - } - - let mut view_structs = Vec::new(); - for (node, layout) in &layout_nodes { - if matches!(layout, LayoutKind::TripleLane | LayoutKind::Direct2) - && let Some(struct_name) = view_struct_name(&node.name) - { - let fields = node.fields.iter().map(|field| { - let field_name = &field.name; - let field_ty = type_to_id(&field.ty); - quote! { pub #field_name: #field_ty } - }); - view_structs.push(quote! { - pub struct #struct_name { - #(#fields,)* - } - }); - } - } - - let mut constructors = Vec::new(); - let mut accessors = Vec::new(); - let mut downcasts = Vec::new(); - let mut upcasts = Vec::new(); - - for (node, layout) in &layout_nodes { - let node_name = &node.name; - let node_id = format_ident!("{}Id", node_name); - let kind_variant = node_name; - - if let Some(category) = &node.category { - let cat_id = format_ident!("{}Id", category); - upcasts.push(quote! { - impl From<#node_id> for #cat_id { - fn from(value: #node_id) -> #cat_id { - #cat_id::from_raw(value.raw()) - } - } - }); - } - - let downcast_name = format_ident!("as_{}", to_snake(&node_name.to_string())); - downcasts.push(quote! { - pub fn #downcast_name(&self, node: impl NodeId) -> Option<#node_id> { - let hir = HirId::from_raw(crate::hir::id::Raw(node.raw())); - if self.node_kind_raw(hir) == NodeKind::#kind_variant { - Some(#node_id::from_raw(hir.raw())) - } else { - None - } - } - }); - - let ctor_name = format_ident!("alloc_{}", constructor_suffix(node_name)); - let accessor_name = format_ident!("{}", accessor_name(node_name)); - - let (ctor_sig, ctor_body, accessor_sig, accessor_body) = match layout { - LayoutKind::BindingInLhs => { - let field = node.fields.first().expect("BindingInLhs requires a field"); - let arg_ty = field.arg_ty.clone().unwrap_or_else(|| field.ty.clone()); - let arg_ty_tokens = type_to_arg(&arg_ty); - let arg_name = &field.name; - let lhs_expr = match &arg_ty { - TypeSpec::Symbol => { - quote! { self.intern_symbol(#arg_name).raw() } - } - TypeSpec::OptionSymbol => { - quote! { - #arg_name.map_or(crate::hir::id::Raw::ZERO, |sym| self.intern_symbol(sym).raw()) - } - } - _ => panic!("BindingInLhs only supports Symbol types"), - }; - let ctor_sig = quote! { pub fn #ctor_name(&mut self, #arg_name: #arg_ty_tokens) -> #node_id }; - let ctor_body = quote! { - let lhs = #lhs_expr; - let hir = self.push_node(NodeKind::#kind_variant, lhs, crate::hir::id::Raw::ZERO); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = - quote! { pub fn #accessor_name(&self, node: #node_id) -> #arg_ty_tokens }; - let accessor_body = match &field.ty { - TypeSpec::Symbol => { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - let sym = SymId::from_raw(node.lhs); - assert!(!sym.is_zero(), "binding symbol is zero"); - self.symbol(sym) - } - } - TypeSpec::OptionSymbol => { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - let sym = SymId::from_raw(node.lhs); - if sym.is_zero() { - None - } else { - Some(self.symbol(sym)) - } - } - } - _ => unreachable!(), - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::ListRange => { - let field = node.fields.first().expect("ListRange requires a field"); - let elem_ty = type_to_id(&field.ty); - let wrapper = list_wrapper_for(&field.ty).map(|(w, _)| w).unwrap(); - let ctor_sig = quote! { - pub fn #ctor_name(&mut self, items: impl IntoIterator) -> #node_id - }; - let ctor_body = quote! { - let (start, end) = self.alloc_list(items.into_iter().map(|it| it.raw())); - let hir = self.push_node(NodeKind::#kind_variant, start.raw(), end.raw()); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = - quote! { pub fn #accessor_name(&self, node: #node_id) -> #wrapper<'_> }; - let accessor_body = quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - let start = IxId::from_raw(node.lhs).get(); - let end = IxId::from_raw(node.rhs).get(); - #wrapper { slice: &self.node_ids[start..end] } - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::CallRange => { - let callee = &node.fields[0]; - let args = &node.fields[1]; - let callee_ty = type_to_id(&callee.ty); - let args_ty = type_to_id(&args.ty); - let wrapper = list_wrapper_for(&args.ty).map(|(w, _)| w).unwrap(); - let ctor_sig = quote! { - pub fn #ctor_name(&mut self, callee: #callee_ty, args: impl IntoIterator) -> #node_id - }; - let ctor_body = quote! { - let (start, end) = self.alloc_list(std::iter::once(callee.raw()).chain(args.into_iter().map(|it| it.raw()))); - let hir = self.push_node(NodeKind::#kind_variant, start.raw(), end.raw()); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = quote! { pub fn #accessor_name(&self, node: #node_id) -> (#callee_ty, #wrapper<'_>) }; - let accessor_body = quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - let start = IxId::from_raw(node.lhs).get(); - let end = IxId::from_raw(node.rhs).get(); - let ids = &self.node_ids[start..end]; - let (callee, args) = ids.split_first().expect("Call node must have at least one element"); - (#callee_ty::from_raw(crate::hir::id::Raw(*callee)), #wrapper { slice: args }) - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::BlockWithTail => { - let list_field = &node.fields[0]; - let tail_field = &node.fields[1]; - let list_ty = type_to_id(&list_field.ty); - let tail_ty = type_to_id(&tail_field.ty); - let wrapper = list_wrapper_for(&list_field.ty).map(|(w, _)| w).unwrap(); - let ctor_sig = quote! { - pub fn #ctor_name(&mut self, items: impl IntoIterator, tail: #tail_ty) -> #node_id - }; - let ctor_body = quote! { - let iter = items.into_iter().map(|it| it.raw()).chain(std::iter::once(tail.raw())); - let (start, end) = self.alloc_list(iter); - let hir = self.push_node(NodeKind::#kind_variant, start.raw(), end.raw()); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = quote! { pub fn #accessor_name(&self, node: #node_id) -> (#wrapper<'_>, #tail_ty) }; - let accessor_body = quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - let start = IxId::from_raw(node.lhs).get(); - let end = IxId::from_raw(node.rhs).get(); - let ids = &self.node_ids[start..end]; - let (tail, head) = ids.split_last().expect("Block-like node must have a tail element"); - (#wrapper { slice: head }, #tail_ty::from_raw(crate::hir::id::Raw(*tail))) - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::TripleLane => { - let fields = &node.fields; - let a = &fields[0]; - let b = &fields[1]; - let c = &fields[2]; - let a_ty = type_to_id(&a.ty); - let b_ty = type_to_id(&b.ty); - let c_ty = type_to_id(&c.ty); - let args = fields.iter().map(|field| { - let name = &field.name; - let arg_ty = type_to_arg(field.arg_ty.as_ref().unwrap_or(&field.ty)); - quote! { #name: #arg_ty } - }); - let ctor_sig = quote! { pub fn #ctor_name(&mut self, #(#args),*) -> #node_id }; - let a_raw = arg_to_raw(a); - let b_raw = arg_to_raw(b); - let c_raw = arg_to_raw(c); - let ctor_body = quote! { - let a_raw = #a_raw; - let b_raw = #b_raw; - let c_raw = #c_raw; - let start = self.alloc_triple(a_raw, b_raw, c_raw); - let rhs = IxId::new(start.get() + 1); - let hir = self.push_node(NodeKind::#kind_variant, start.raw(), rhs.raw()); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = if let Some(struct_name) = view_struct_name(&node.name) { - quote! { pub fn #accessor_name(&self, node: #node_id) -> #struct_name } - } else { - quote! { pub fn #accessor_name(&self, node: #node_id) -> (#a_ty, #b_ty, #c_ty) } - }; - let accessor_body = { - let a_name = &a.name; - let b_name = &b.name; - let c_name = &c.name; - let a_from = - quote! { #a_ty::from_raw(crate::hir::id::Raw(self.node_ids[start])) }; - let b_from = quote! { #b_ty::from_raw(crate::hir::id::Raw(self.node_ids[start + 1])) }; - let c_from = quote! { #c_ty::from_raw(crate::hir::id::Raw(self.node_ids[start + 2])) }; - if let Some(struct_name) = view_struct_name(&node.name) { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - let start = IxId::from_raw(node.lhs).get(); - #struct_name { #a_name: #a_from, #b_name: #b_from, #c_name: #c_from } - } - } else { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - let start = IxId::from_raw(node.lhs).get(); - (#a_from, #b_from, #c_from) - } - } - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::Direct2 => { - let fields = &node.fields; - let a = &fields[0]; - let b = &fields[1]; - let a_ty = type_to_id(&a.ty); - let b_ty = type_to_id(&b.ty); - let args = fields.iter().map(|field| { - let name = &field.name; - let arg_ty = type_to_arg(field.arg_ty.as_ref().unwrap_or(&field.ty)); - quote! { #name: #arg_ty } - }); - let ctor_sig = quote! { pub fn #ctor_name(&mut self, #(#args),*) -> #node_id }; - let a_raw = arg_to_raw(a); - let b_raw = arg_to_raw(b); - let ctor_body = quote! { - let a_raw = #a_raw; - let b_raw = #b_raw; - let hir = self.push_node(NodeKind::#kind_variant, a_raw, b_raw); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = if let Some(struct_name) = view_struct_name(&node.name) { - quote! { pub fn #accessor_name(&self, node: #node_id) -> #struct_name } - } else { - quote! { pub fn #accessor_name(&self, node: #node_id) -> (#a_ty, #b_ty) } - }; - let accessor_body = { - let a_name = &a.name; - let b_name = &b.name; - let a_from = quote! { #a_ty::from_raw(node.lhs) }; - let b_from = quote! { #b_ty::from_raw(node.rhs) }; - if let Some(struct_name) = view_struct_name(&node.name) { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - #struct_name { #a_name: #a_from, #b_name: #b_from } - } - } else { - quote! { - let node = self.node(HirId::from_raw(node.raw())); - assert_eq!(node.kind, NodeKind::#kind_variant); - (#a_from, #b_from) - } - } - }; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - LayoutKind::ZeroZero => { - let ctor_sig = quote! { pub fn #ctor_name(&mut self) -> #node_id }; - let ctor_body = quote! { - let hir = self.push_node(NodeKind::#kind_variant, crate::hir::id::Raw::ZERO, crate::hir::id::Raw::ZERO); - #node_id::from_raw(hir.raw()) - }; - let accessor_sig = quote! {}; - let accessor_body = quote! {}; - (ctor_sig, ctor_body, accessor_sig, accessor_body) - } - }; - - constructors.push(quote! { - #ctor_sig { - #ctor_body - } - }); - - if !matches!(layout, LayoutKind::ZeroZero) { - accessors.push(quote! { - #accessor_sig { - #accessor_body - } - }); - } - } - - let node_kind_enum = quote! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum NodeKind { - #(#node_kind_variants,)* - } - }; - - let node_id_trait = quote! { - pub trait NodeId: sealed::Sealed + Copy { - fn raw(self) -> u32; - } - - mod sealed { - pub trait Sealed {} - } - }; - - let node_store_impl = quote! { - impl<'db> crate::hir::store::NodeStore<'db> { - #(#constructors)* - #(#accessors)* - #(#downcasts)* - - pub fn node_kind(&self, node: impl NodeId) -> NodeKind { - let hir = HirId::from_raw(crate::hir::id::Raw(node.raw())); - self.node_kind_raw(hir) - } - } - }; - - let upcasts_tokens = quote! { #(#upcasts)* }; - let node_id_impl_tokens = - quote! { #(#node_id_trait_impls)* #(#node_id_trait_impls_categories)* }; - - quote! { - #node_kind_enum - #tag_defs - #node_id_trait - #node_id_impl_tokens - #upcasts_tokens - #(#view_structs)* - #(#list_wrappers)* - #node_store_impl - } - } -} - -fn format_type(ty: &TypeSpec) -> String { - match ty { - TypeSpec::Symbol => "Symbol".to_string(), - TypeSpec::OptionSymbol => "OptionSymbol".to_string(), - TypeSpec::Node(ident) => ident.to_string(), - } -} - -fn list_wrapper_for(ty: &TypeSpec) -> Option<(Ident, proc_macro2::TokenStream)> { - match ty { - TypeSpec::Node(ident) => { - let id = format_ident!("{}Id", ident); - let wrapper = format_ident!("{}Ids", ident); - Some((wrapper, quote! { #id })) - } - _ => None, - } -} - -fn type_to_id(ty: &TypeSpec) -> proc_macro2::TokenStream { - match ty { - TypeSpec::Symbol => quote! { mitki_span::Symbol<'db> }, - TypeSpec::OptionSymbol => quote! { Option> }, - TypeSpec::Node(ident) => { - let id = format_ident!("{}Id", ident); - quote! { #id } - } - } -} - -fn type_to_arg(ty: &TypeSpec) -> proc_macro2::TokenStream { - match ty { - TypeSpec::Symbol => quote! { mitki_span::Symbol<'db> }, - TypeSpec::OptionSymbol => quote! { Option> }, - TypeSpec::Node(ident) => { - let id = format_ident!("{}Id", ident); - quote! { #id } - } - } -} - -fn view_struct_name(node: &Ident) -> Option { - match node.to_string().as_str() { - "Binary" => Some(format_ident!("BinaryExpr")), - "If" => Some(format_ident!("IfExpr")), - "Prefix" => Some(format_ident!("PrefixExpr")), - "Postfix" => Some(format_ident!("PostfixExpr")), - "LocalVar" => Some(format_ident!("LocalVar")), - _ => None, - } -} - -fn to_snake(name: &str) -> String { - let mut out = String::new(); - for (i, ch) in name.chars().enumerate() { - if ch.is_ascii_uppercase() { - if i != 0 { - out.push('_'); - } - out.push(ch.to_ascii_lowercase()); - } else { - out.push(ch); - } - } - out -} - -fn constructor_suffix(name: &Ident) -> String { - match name.to_string().as_str() { - "TypePath" => "type_ref".to_string(), - other => to_snake(other), - } -} - -fn accessor_name(name: &Ident) -> String { - match name.to_string().as_str() { - "If" => "if_expr".to_string(), - "Match" => "match_expr".to_string(), - "Closure" => "closure_parts".to_string(), - "Block" => "block_stmts".to_string(), - "TypePath" => "type_ref".to_string(), - other => to_snake(other), - } -} - -fn arg_to_raw(field: &FieldDef) -> proc_macro2::TokenStream { - let name = &field.name; - let arg_ty = field.arg_ty.as_ref().unwrap_or(&field.ty); - match (arg_ty, &field.ty) { - (TypeSpec::Symbol, TypeSpec::Node(store)) if store == "Name" => { - quote! { self.alloc_name(#name).raw() } - } - (TypeSpec::Symbol, _) => quote! { self.intern_symbol(#name).raw() }, - (TypeSpec::OptionSymbol, _) => quote! { - #name.map_or(crate::hir::id::Raw::ZERO, |sym| self.intern_symbol(sym).raw()) - }, - (TypeSpec::Node(_), _) => quote! { #name.raw() }, - } -} - -// No helpers needed; generated IntoIterator uses per-wrapper map_fn. diff --git a/crates/mitki-hir/Cargo.toml b/crates/mitki-hir/Cargo.toml deleted file mode 100644 index 3b72899..0000000 --- a/crates/mitki-hir/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "mitki-hir" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-inputs.workspace = true -mitki-span.workspace = true -mitki-hir-macros = { path = "../mitki-hir-macros" } -salsa.workspace = true - -[dev-dependencies] -trybuild = "1" diff --git a/crates/mitki-hir/src/arena.rs b/crates/mitki-hir/src/arena.rs deleted file mode 100644 index 631d7d5..0000000 --- a/crates/mitki-hir/src/arena.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::marker::PhantomData; -use std::ops::{Index, IndexMut}; - -#[derive(Debug)] -pub struct Key(u32, PhantomData); - -unsafe impl salsa::Update for Key { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - unsafe { salsa::Update::maybe_update(&mut (*old_pointer).0, new_value.0) } - } -} - -impl std::hash::Hash for Key { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl PartialEq for Key { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for Key {} - -impl Clone for Key { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Key {} - -impl Key { - pub fn new(index: u32) -> Self { - Self(index, PhantomData) - } - - pub fn index(self) -> u32 { - self.0 - } -} - -impl From for Key { - fn from(index: u32) -> Self { - Self::new(index) - } -} - -#[derive(Debug, salsa::Update)] -pub struct Range { - pub start: Key, - pub end: Key, -} - -impl std::hash::Hash for Range { - fn hash(&self, state: &mut H) { - self.start.hash(state); - self.end.hash(state); - } -} - -impl Copy for Range {} - -impl PartialEq for Range { - fn eq(&self, other: &Self) -> bool { - self.start == other.start && self.end == other.end - } -} - -impl Eq for Range {} - -impl Clone for Range { - fn clone(&self) -> Self { - *self - } -} - -impl Range { - pub fn new(start: Key, end: Key) -> Self { - Self { start, end } - } - - pub fn new_inclusive(start: Key, end: Key) -> Self { - Self { start, end: Key::new(end.index() + 1) } - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Arena { - items: Vec, -} - -impl IntoIterator for Arena { - type Item = T; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl Extend for Arena { - fn extend>(&mut self, iter: I) { - self.items.extend(iter); - } -} - -impl Default for Arena { - fn default() -> Self { - Self { items: Default::default() } - } -} - -unsafe impl salsa::Update for Arena { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - unsafe { salsa::Update::maybe_update(&mut (*old_pointer).items, new_value.items) } - } -} - -impl Arena { - pub fn new() -> Self { - Self { items: Vec::new() } - } - - pub fn alloc(&mut self, value: T) -> Key { - let idx = self.items.len() as u32; - self.items.push(value); - Key::new(idx) - } - - pub fn iter_enumerated(&self) -> impl Iterator, &T)> { - self.items.iter().enumerate().map(|(i, item)| (Key::new(i as u32), item)) - } - - #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.items.len() - } -} - -impl Index> for Arena { - type Output = T; - fn index(&self, index: Key) -> &Self::Output { - &self.items[index.index() as usize] - } -} - -impl IndexMut> for Arena { - fn index_mut(&mut self, index: Key) -> &mut Self::Output { - &mut self.items[index.index() as usize] - } -} - -impl Index> for Arena { - type Output = [T]; - - fn index(&self, range: Range) -> &Self::Output { - let start = range.start.index() as usize; - let end = range.end.index() as usize; - &self.items[start..end] - } -} diff --git a/crates/mitki-hir/src/hir.rs b/crates/mitki-hir/src/hir.rs deleted file mode 100644 index 433318c..0000000 --- a/crates/mitki-hir/src/hir.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod function; -mod id; -mod schema; -mod store; - -pub use function::{Function, WasmLinkage}; -pub use schema::*; -pub use store::NodeStore; diff --git a/crates/mitki-hir/src/hir/function.rs b/crates/mitki-hir/src/hir/function.rs deleted file mode 100644 index 7ff1033..0000000 --- a/crates/mitki-hir/src/hir/function.rs +++ /dev/null @@ -1,96 +0,0 @@ -use mitki_span::Symbol; - -use super::{ExprId, NodeStore, ParamId, TyId}; - -#[derive(Debug, Clone, PartialEq, Eq, Default, salsa::Update)] -pub enum WasmLinkage<'db> { - #[default] - Internal, - ImplicitMainExport, - Export, - Import { - module: Symbol<'db>, - }, - RawImport { - module: Symbol<'db>, - }, -} - -#[derive(Default, Debug, PartialEq, Eq, salsa::Update)] -pub struct Function<'db> { - node_store: NodeStore<'db>, - - type_params: Vec>, - params: Vec, - body: ExprId, - ret_type: TyId, - linkage: WasmLinkage<'db>, - comptime: bool, - unsafe_: bool, -} - -impl<'db> Function<'db> { - pub fn node_store(&self) -> &NodeStore<'db> { - &self.node_store - } - - pub fn node_store_mut(&mut self) -> &mut NodeStore<'db> { - &mut self.node_store - } - - pub fn set_type_params(&mut self, type_params: Vec>) { - self.type_params = type_params; - } - - pub fn set_params(&mut self, params: Vec) { - self.params = params; - } - - pub fn set_ret_type(&mut self, ret_type: TyId) { - self.ret_type = ret_type; - } - - pub fn set_body(&mut self, body: ExprId) { - self.body = body; - } - - pub fn set_linkage(&mut self, linkage: WasmLinkage<'db>) { - self.linkage = linkage; - } - - pub fn set_comptime(&mut self, comptime: bool) { - self.comptime = comptime; - } - - pub fn set_unsafe(&mut self, unsafe_: bool) { - self.unsafe_ = unsafe_; - } - - pub fn type_params(&self) -> &[Symbol<'db>] { - &self.type_params - } - - pub fn params(&self) -> &[ParamId] { - &self.params - } - - pub fn ret_type(&self) -> TyId { - self.ret_type - } - - pub fn body(&self) -> ExprId { - self.body - } - - pub fn linkage(&self) -> &WasmLinkage<'db> { - &self.linkage - } - - pub fn is_comptime(&self) -> bool { - self.comptime - } - - pub fn is_unsafe(&self) -> bool { - self.unsafe_ - } -} diff --git a/crates/mitki-hir/src/hir/id.rs b/crates/mitki-hir/src/hir/id.rs deleted file mode 100644 index 62de071..0000000 --- a/crates/mitki-hir/src/hir/id.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::marker::PhantomData; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] -pub struct Id { - raw: u32, - _tag: PhantomData, -} - -impl Id { - pub const ZERO: Self = Self { raw: 0, _tag: PhantomData }; - - pub fn is_zero(&self) -> bool { - self.raw == 0 - } - - pub(crate) fn new(index: usize) -> Self { - let raw: u32 = index.try_into().expect("Id overflow"); - Self { raw: raw + 1, _tag: PhantomData } - } - - #[track_caller] - pub(crate) fn get(self) -> usize { - assert!(self.raw != 0, "attempted to access zero id"); - (self.raw - 1) as usize - } - - pub(crate) fn from_raw(raw: Raw) -> Self { - Self { raw: raw.0, _tag: PhantomData } - } - - pub(crate) fn raw(self) -> Raw { - Raw(self.raw) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, salsa::Update)] -pub(crate) struct Raw(pub(crate) u32); - -impl Raw { - pub(crate) const ZERO: Raw = Raw(0); -} - -impl From> for Raw { - fn from(id: Id) -> Self { - Raw(id.raw) - } -} diff --git a/crates/mitki-hir/src/hir/schema.rs b/crates/mitki-hir/src/hir/schema.rs deleted file mode 100644 index fcd8a6c..0000000 --- a/crates/mitki-hir/src/hir/schema.rs +++ /dev/null @@ -1,156 +0,0 @@ -use mitki_hir_macros::define_hir; - -use super::NodeStore; - -define_hir! { - categories { Expr, Stmt, Ty, Pat } - - nodes { - Name: BindingInLhs(sym: Symbol) => Expr; - TypePath: BindingInLhs(sym: Symbol) => Ty; - TypeApply: Direct2(path: Ty, args: Ty) => Ty; - TypeArray: Direct2(item: Ty, unused: Ty) => Ty; - TypeTuple: ListRange(items: Ty) => Ty; - TypeFunction: Direct2(inputs: Ty, output: Ty) => Ty; - TypeUnion: Direct2(lhs: Ty, rhs: Ty) => Ty; - TypeInter: Direct2(lhs: Ty, rhs: Ty) => Ty; - TypeRecord: ListRange(fields: Ty) => Ty; - TypeField: Direct2(name: Name <- Symbol, ty: Ty) => Ty; - TypePtrConst: Direct2(item: Ty, unused: Ty) => Ty; - TypePtrMut: Direct2(item: Ty, unused: Ty) => Ty; - - True: ZeroZero() => Expr; - False: ZeroZero() => Expr; - Error: ZeroZero() => Expr; - - Int: BindingInLhs(sym: Option) => Expr; - Float: BindingInLhs(sym: Option) => Expr; - String: BindingInLhs(sym: Option) => Expr; - Char: BindingInLhs(sym: Option) => Expr; - - Tuple: ListRange(items: Expr) => Expr; - Array: ListRange(items: Expr) => Expr; - ArrayRepeat: Direct2(value: Expr, len: Expr) => Expr; - Call: CallRange(callee: Expr, args: Expr) => Expr; - Field: Direct2(expr: Expr, name: Expr) => Expr; - - Binary: TripleLane(lhs: Expr, op: Expr, rhs: Expr) => Expr; - If: TripleLane(cond: Expr, then_branch: Expr, else_branch: Expr) => Expr; - Match: CallRange(scrutinee: Expr, arms: MatchArm) => Expr; - LoopExpr: Direct2(body: Expr, unused: Expr) => Expr; - UnsafeBlock: Direct2(body: Expr, unused: Expr) => Expr; - BreakExpr: ZeroZero() => Expr; - ContinueExpr: ZeroZero() => Expr; - LocalVar: TripleLane(pattern: Pat, ty: Ty, initializer: Expr) => Stmt; - AssignStmt: Direct2(target: Expr, value: Expr) => Stmt; - ReturnStmt: Direct2(value: Expr, unused: Expr) => Stmt; - - Block: BlockWithTail(stmts: Stmt, tail: Expr) => Expr; - Closure: BlockWithTail(params: Param, body: Expr) => Expr; - - MatchArm: Direct2(pattern: Pat, expr: Expr) => _; - Param: Direct2(pattern: Pat, ty: Ty) => _; - Prefix: Direct2(op: Expr, expr: Expr) => Expr; - Postfix: Direct2(expr: Expr, op: Expr) => Expr; - StructExpr: ListRange(items: Expr) => Expr; - - PatBinding: Direct2(name: Name <- Symbol, unused: Pat) => Pat; - PatWildcard: ZeroZero() => Pat; - PatTrue: ZeroZero() => Pat; - PatFalse: ZeroZero() => Pat; - PatInt: BindingInLhs(sym: Option) => Pat; - PatFloat: BindingInLhs(sym: Option) => Pat; - PatString: BindingInLhs(sym: Option) => Pat; - PatChar: BindingInLhs(sym: Option) => Pat; - PatTyped: Direct2(pattern: Pat, ty: Ty) => Pat; - PatParen: Direct2(pattern: Pat, unused: Pat) => Pat; - PatTuple: ListRange(items: Pat) => Pat; - PatVariant: CallRange(path: Expr, args: Pat) => Pat; - PatStructField: Direct2(name: Name <- Symbol, pat: Pat) => _; - PatStruct: CallRange(path: Expr, fields: PatStructField) => Pat; - } -} - -impl From for StmtId { - fn from(value: ExprId) -> StmtId { - StmtId::from_raw(value.raw()) - } -} - -impl StmtId { - pub fn node_id(self) -> ExprId { - ExprId::from_raw(self.raw()) - } -} - -impl<'db> NodeStore<'db> { - pub fn pattern_binding_names(&self, pattern: PatId) -> Vec { - if pattern == PatId::ZERO { - return Vec::new(); - } - - let mut names = Vec::new(); - self.collect_pattern_binding_names(pattern, &mut names); - names - } - - fn collect_pattern_binding_names(&self, pattern: PatId, names: &mut Vec) { - if pattern == PatId::ZERO { - return; - } - - match self.node_kind(pattern) { - NodeKind::PatBinding => { - let (name, _) = self.pat_binding(self.as_pat_binding(pattern).expect("binding")); - names.push(name); - } - NodeKind::PatTyped => { - let (inner, _) = self.pat_typed(self.as_pat_typed(pattern).expect("typed")); - if inner != PatId::ZERO { - self.collect_pattern_binding_names(inner, names); - } - } - NodeKind::PatParen => { - let (inner, _) = self.pat_paren(self.as_pat_paren(pattern).expect("paren")); - if inner != PatId::ZERO { - self.collect_pattern_binding_names(inner, names); - } - } - NodeKind::PatTuple => { - for item in self.pat_tuple(self.as_pat_tuple(pattern).expect("tuple")).iter() { - if item != PatId::ZERO { - self.collect_pattern_binding_names(item, names); - } - } - } - NodeKind::PatVariant => { - let (_, items) = self.pat_variant(self.as_pat_variant(pattern).expect("variant")); - for item in items.iter() { - if item != PatId::ZERO { - self.collect_pattern_binding_names(item, names); - } - } - } - NodeKind::PatStruct => { - let (_, fields) = self.pat_struct(self.as_pat_struct(pattern).expect("struct")); - for field in fields.iter() { - let (name, pat) = - self.pat_struct_field(self.as_pat_struct_field(field).expect("field")); - if pat != PatId::ZERO { - self.collect_pattern_binding_names(pat, names); - } else { - names.push(name); - } - } - } - NodeKind::PatWildcard - | NodeKind::PatTrue - | NodeKind::PatFalse - | NodeKind::PatInt - | NodeKind::PatFloat - | NodeKind::PatString - | NodeKind::PatChar => {} - _ => {} - } - } -} diff --git a/crates/mitki-hir/src/hir/store.rs b/crates/mitki-hir/src/hir/store.rs deleted file mode 100644 index 08401aa..0000000 --- a/crates/mitki-hir/src/hir/store.rs +++ /dev/null @@ -1,62 +0,0 @@ -use mitki_span::Symbol; - -use super::id::Raw; -use super::schema::{HirId, IxId, NodeKind, SymId}; - -#[derive(Default, Debug, PartialEq, Eq, salsa::Update)] -pub struct NodeStore<'db> { - pub(crate) nodes: Vec, - pub(crate) symbols: Vec>, - pub(crate) node_ids: Vec, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub(crate) struct Node { - pub(crate) kind: NodeKind, - pub(crate) lhs: Raw, - pub(crate) rhs: Raw, -} - -impl<'db> NodeStore<'db> { - pub(crate) fn push_node(&mut self, kind: NodeKind, lhs: Raw, rhs: Raw) -> HirId { - let index = self.nodes.len(); - self.nodes.push(Node { kind, lhs, rhs }); - HirId::new(index) - } - - pub(crate) fn intern_symbol(&mut self, symbol: Symbol<'db>) -> SymId { - let index = self.symbols.len(); - self.symbols.push(symbol); - SymId::new(index) - } - - #[track_caller] - pub(crate) fn symbol(&self, symbol: SymId) -> Symbol<'db> { - self.symbols[symbol.get()] - } - - pub(crate) fn alloc_list(&mut self, iter: impl IntoIterator) -> (IxId, IxId) { - let start = IxId::new(self.node_ids.len()); - self.node_ids.extend(iter.into_iter().map(|raw| raw.0)); - let end = IxId::new(self.node_ids.len()); - (start, end) - } - - pub(crate) fn alloc_triple(&mut self, a: Raw, b: Raw, c: Raw) -> IxId { - let start = self.node_ids.len(); - self.node_ids.push(a.0); - self.node_ids.push(b.0); - self.node_ids.push(c.0); - IxId::new(start) - } - - #[track_caller] - pub(crate) fn node_kind_raw(&self, node: HirId) -> NodeKind { - self.nodes[node.get()].kind - } - - #[track_caller] - pub(crate) fn node(&self, node: HirId) -> &Node { - &self.nodes[node.get()] - } -} diff --git a/crates/mitki-hir/src/lib.rs b/crates/mitki-hir/src/lib.rs deleted file mode 100644 index 1b96b54..0000000 --- a/crates/mitki-hir/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod arena; -pub mod hir; -pub mod ty; diff --git a/crates/mitki-hir/src/ty.rs b/crates/mitki-hir/src/ty.rs deleted file mode 100644 index ee389c2..0000000 --- a/crates/mitki-hir/src/ty.rs +++ /dev/null @@ -1,209 +0,0 @@ -use mitki_inputs::{File, ModuleId}; -use mitki_span::Symbol; - -#[salsa::interned(debug)] -pub struct StructTy<'db> { - pub module: ModuleId<'db>, - pub index: u32, - pub name: Symbol<'db>, - #[returns(ref)] - pub args: Vec>, -} - -#[salsa::interned(debug)] -pub struct EnumTy<'db> { - pub module: ModuleId<'db>, - pub index: u32, - pub name: Symbol<'db>, - #[returns(ref)] - pub args: Vec>, -} - -#[salsa::interned(debug)] -pub struct Ty<'db> { - #[returns(ref)] - pub kind: TyKind<'db>, -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, salsa::Update)] -pub enum ExactInt { - U8, - U16, - U32, - U64, - I8, - I16, - I32, - I64, -} - -impl ExactInt { - pub fn signed(self) -> bool { - matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64) - } - - pub fn bits(self) -> u8 { - match self { - Self::U8 | Self::I8 => 8, - Self::U16 | Self::I16 => 16, - Self::U32 | Self::I32 => 32, - Self::U64 | Self::I64 => 64, - } - } - - pub fn display(self) -> &'static str { - match self { - Self::U8 => "u8", - Self::U16 => "u16", - Self::U32 => "u32", - Self::U64 => "u64", - Self::I8 => "i8", - Self::I16 => "i16", - Self::I32 => "i32", - Self::I64 => "i64", - } - } -} - -impl<'db> Ty<'db> { - pub fn display(self, db: &'db dyn salsa::Database) -> TyDisplay<'db> { - TyDisplay { db, ty_kind: self.kind(db) } - } -} - -impl<'db> StructTy<'db> { - pub fn file(self, db: &'db dyn salsa::Database) -> File { - self.module(db).file(db) - } -} - -impl<'db> EnumTy<'db> { - pub fn file(self, db: &'db dyn salsa::Database) -> File { - self.module(db).file(db) - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, salsa::Update)] -pub enum TyKind<'db> { - Bool, - Float, - Int, - ExactInt(ExactInt), - String, - Char, - Array(Ty<'db>), - Tuple(Vec>), - Record(Vec<(Symbol<'db>, Ty<'db>)>), - Pointer { mutable: bool, pointee: Ty<'db> }, - Unknown, - Function { inputs: Vec>, output: Ty<'db> }, - Var(u32), - Union(Vec>), - Inter(Vec>), - Rec(u32, Ty<'db>), - Struct(StructTy<'db>), - ExternStruct(StructTy<'db>), - Enum(EnumTy<'db>), -} - -pub struct TyDisplay<'db> { - ty_kind: &'db TyKind<'db>, - db: &'db dyn salsa::Database, -} - -impl<'db> std::fmt::Display for TyDisplay<'db> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.ty_kind { - TyKind::Bool => write!(f, "bool"), - TyKind::Float => write!(f, "float"), - TyKind::Int => write!(f, "int"), - TyKind::ExactInt(int_ty) => write!(f, "{}", int_ty.display()), - TyKind::String => write!(f, "str"), - TyKind::Char => write!(f, "char"), - TyKind::Array(item) => write!(f, "[{}]", item.display(self.db)), - TyKind::Tuple(items) => { - write!(f, "(")?; - write_joined(f, self.db, items.iter(), ", ")?; - if items.len() == 1 { - write!(f, ",")?; - } - write!(f, ")") - } - TyKind::Record(fields) => { - write!(f, "{{")?; - for (idx, (name, ty)) in fields.iter().enumerate() { - if idx > 0 { - write!(f, ", ")?; - } - write!(f, "{}: {}", name.text(self.db), ty.display(self.db))?; - } - write!(f, "}}") - } - TyKind::Pointer { mutable, pointee } => { - let qualifier = if *mutable { "mut" } else { "const" }; - write!(f, "*{qualifier} {}", pointee.display(self.db)) - } - TyKind::Function { inputs, output } => { - write!(f, "fun(")?; - write_joined(f, self.db, inputs.iter(), ", ")?; - write!(f, ") -> {}", output.display(self.db)) - } - TyKind::Unknown => write!(f, "{{unknown}}"), - TyKind::Var(id) => write_var(f, *id), - TyKind::Union(items) => write_joined(f, self.db, items.iter(), " | "), - TyKind::Inter(items) => write_joined(f, self.db, items.iter(), " & "), - TyKind::Rec(id, body) => { - write!(f, "μ")?; - write_var(f, *id)?; - write!(f, ". {}", body.display(self.db)) - } - TyKind::Struct(struct_ty) => { - write_nominal(f, self.db, struct_ty.name(self.db), struct_ty.args(self.db)) - } - TyKind::ExternStruct(struct_ty) => { - write_nominal(f, self.db, struct_ty.name(self.db), struct_ty.args(self.db)) - } - TyKind::Enum(enum_ty) => { - write_nominal(f, self.db, enum_ty.name(self.db), enum_ty.args(self.db)) - } - } - } -} - -fn write_var(f: &mut std::fmt::Formatter<'_>, id: u32) -> std::fmt::Result { - let c = (b'A' + (id % 26) as u8) as char; - if id >= 26 { write!(f, "{c}{}", id / 26) } else { write!(f, "{c}") } -} - -fn write_joined<'db>( - f: &mut std::fmt::Formatter<'_>, - db: &'db dyn salsa::Database, - iter: impl ExactSizeIterator>, - sep: &str, -) -> std::fmt::Result { - let mut first = true; - for e in iter { - if !first { - write!(f, "{sep}")?; - } - first = false; - write!(f, "{}", e.display(db))?; - } - Ok(()) -} - -fn write_nominal<'db>( - f: &mut std::fmt::Formatter<'_>, - db: &'db dyn salsa::Database, - name: Symbol<'db>, - args: &'db [Ty<'db>], -) -> std::fmt::Result { - write!(f, "{}", name.text(db))?; - if args.is_empty() { - return Ok(()); - } - - write!(f, "[")?; - write_joined(f, db, args.iter(), ", ")?; - write!(f, "]") -} diff --git a/crates/mitki-hir/tests/hir_roundtrip.rs b/crates/mitki-hir/tests/hir_roundtrip.rs deleted file mode 100644 index 3567272..0000000 --- a/crates/mitki-hir/tests/hir_roundtrip.rs +++ /dev/null @@ -1,113 +0,0 @@ -use mitki_hir::hir::*; -use mitki_span::Symbol; - -#[salsa::db] -#[derive(Clone, Default)] -struct TestDb { - storage: salsa::Storage, -} - -#[salsa::db] -impl salsa::Database for TestDb {} - -fn sym<'db>(db: &'db TestDb, text: &str) -> Symbol<'db> { - Symbol::new(db, text) -} - -#[test] -fn roundtrip_nodes() { - let db = TestDb::default(); - let mut store = NodeStore::default(); - - let name_sym = sym(&db, "x"); - let name = store.alloc_name(name_sym); - assert_eq!(store.name(name), name_sym); - let pat_binding = store.alloc_pat_binding(name_sym, PatId::ZERO); - let (pat_name, _) = store.pat_binding(pat_binding); - assert_eq!(store.name(pat_name), name_sym); - - let ty_sym = sym(&db, "T"); - let ty = store.alloc_type_ref(ty_sym); - assert_eq!(store.type_ref(ty), ty_sym); - - let int_sym = sym(&db, "123"); - let int = store.alloc_int(Some(int_sym)); - assert_eq!(store.int(int), Some(int_sym)); - let missing_int = store.alloc_int(None); - assert_eq!(store.int(missing_int), None); - - let float_sym = sym(&db, "1.25"); - let float = store.alloc_float(Some(float_sym)); - assert_eq!(store.float(float), Some(float_sym)); - - let string_sym = sym(&db, "hello"); - let string = store.alloc_string(Some(string_sym)); - assert_eq!(store.string(string), Some(string_sym)); - - let char_sym = sym(&db, "'a'"); - let ch = store.alloc_char(Some(char_sym)); - assert_eq!(store.char(ch), Some(char_sym)); - - let _true = store.alloc_true(); - let _false = store.alloc_false(); - let _error = store.alloc_error(); - - let tuple = store.alloc_tuple(vec![name.into(), int.into()]); - let tuple_items: Vec<_> = store.tuple(tuple).iter().collect(); - assert_eq!(tuple_items, vec![name.into(), int.into()]); - - let call = store.alloc_call(name.into(), vec![int.into(), float.into()]); - let (call_callee, call_args) = store.call(call); - let call_args: Vec<_> = call_args.iter().collect(); - assert_eq!(call_callee, name.into()); - assert_eq!(call_args, vec![int.into(), float.into()]); - - let binary = store.alloc_binary(name.into(), int.into(), float.into()); - let binary = store.binary(binary); - assert_eq!(binary.lhs, name.into()); - assert_eq!(binary.op, int.into()); - assert_eq!(binary.rhs, float.into()); - - let if_expr = store.alloc_if(name.into(), int.into(), float.into()); - let if_expr = store.if_expr(if_expr); - assert_eq!(if_expr.cond, name.into()); - assert_eq!(if_expr.then_branch, int.into()); - assert_eq!(if_expr.else_branch, float.into()); - - let local = store.alloc_local_var(pat_binding.into(), ty.into(), int.into()); - let local = store.local_var(local); - assert_eq!(local.pattern, pat_binding.into()); - assert_eq!(local.initializer, int.into()); - - let stmt_expr: StmtId = ExprId::from(name).into(); - let stmt_local: StmtId = - store.alloc_local_var(pat_binding.into(), ty.into(), int.into()).into(); - let tail = float.into(); - let block = store.alloc_block(vec![stmt_local, stmt_expr], tail); - let (stmts, block_tail) = store.block_stmts(block); - let stmts: Vec<_> = stmts.iter().collect(); - assert_eq!(stmts, vec![stmt_local, stmt_expr]); - assert_eq!(block_tail, tail); - - let param_pat = store.alloc_pat_binding(sym(&db, "p"), PatId::ZERO); - let param = store.alloc_param(param_pat.into(), ty.into()); - let (param_pattern, param_ty) = store.param(param); - assert_eq!(param_pattern, param_pat.into()); - assert_eq!(param_ty, ty.into()); - - let closure = store.alloc_closure(vec![param], tail); - let (params, closure_body) = store.closure_parts(closure); - let params: Vec<_> = params.iter().collect(); - assert_eq!(params, vec![param]); - assert_eq!(closure_body, tail); - - let prefix = store.alloc_prefix(int.into(), name.into()); - let prefix = store.prefix(prefix); - assert_eq!(prefix.op, int.into()); - assert_eq!(prefix.expr, name.into()); - - let postfix = store.alloc_postfix(name.into(), int.into()); - let postfix = store.postfix(postfix); - assert_eq!(postfix.expr, name.into()); - assert_eq!(postfix.op, int.into()); -} diff --git a/crates/mitki-hir/tests/trybuild.rs b/crates/mitki-hir/tests/trybuild.rs deleted file mode 100644 index 90fc847..0000000 --- a/crates/mitki-hir/tests/trybuild.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn id_mixups_fail_to_compile() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/trybuild/*.rs"); -} diff --git a/crates/mitki-hir/tests/trybuild/id_mixup.rs b/crates/mitki-hir/tests/trybuild/id_mixup.rs deleted file mode 100644 index b6d37e5..0000000 --- a/crates/mitki-hir/tests/trybuild/id_mixup.rs +++ /dev/null @@ -1,20 +0,0 @@ -use mitki_hir::hir::{ExprId, StmtId, TyId}; - -fn takes_expr(_expr: ExprId) {} -fn takes_exprs(_exprs: Vec) {} -fn takes_ty(_ty: TyId) {} - -fn main() { - let expr = ExprId::ZERO; - let stmt = StmtId::ZERO; - let ty = TyId::ZERO; - - // StmtId should not be accepted where ExprId is required. - takes_expr(stmt); - - // TyId should not be accepted where ExprId is required. - takes_exprs(vec![ty]); - - // ExprId should not be accepted where TyId is required. - takes_ty(expr); -} diff --git a/crates/mitki-hir/tests/trybuild/id_mixup.stderr b/crates/mitki-hir/tests/trybuild/id_mixup.stderr deleted file mode 100644 index 08029f1..0000000 --- a/crates/mitki-hir/tests/trybuild/id_mixup.stderr +++ /dev/null @@ -1,40 +0,0 @@ -error[E0308]: mismatched types - --> tests/trybuild/id_mixup.rs:13:16 - | -13 | takes_expr(stmt); - | ---------- ^^^^ expected `Id`, found `Id` - | | - | arguments to this function are incorrect - | - = note: expected struct `hir::id::Id` - found struct `hir::id::Id` -note: function defined here - --> tests/trybuild/id_mixup.rs:3:4 - | - 3 | fn takes_expr(_expr: ExprId) {} - | ^^^^^^^^^^ ------------- - -error[E0308]: mismatched types - --> tests/trybuild/id_mixup.rs:16:22 - | -16 | takes_exprs(vec![ty]); - | ^^ expected `Id`, found `Id` - | - = note: expected struct `hir::id::Id` - found struct `hir::id::Id` - -error[E0308]: mismatched types - --> tests/trybuild/id_mixup.rs:19:14 - | -19 | takes_ty(expr); - | -------- ^^^^ expected `Id`, found `Id` - | | - | arguments to this function are incorrect - | - = note: expected struct `hir::id::Id` - found struct `hir::id::Id` -note: function defined here - --> tests/trybuild/id_mixup.rs:5:4 - | - 5 | fn takes_ty(_ty: TyId) {} - | ^^^^^^^^ --------- diff --git a/crates/mitki-ide/Cargo.toml b/crates/mitki-ide/Cargo.toml deleted file mode 100644 index 5264c68..0000000 --- a/crates/mitki-ide/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "mitki-ide" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-analysis.workspace = true -mitki-db.workspace = true -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-parse.workspace = true -mitki-resolve.workspace = true -mitki-span.workspace = true -mitki-typeck.workspace = true -mitki-yellow.workspace = true -salsa.workspace = true -text-size.workspace = true diff --git a/crates/mitki-ide/src/analysis.rs b/crates/mitki-ide/src/analysis.rs deleted file mode 100644 index 2c723f3..0000000 --- a/crates/mitki-ide/src/analysis.rs +++ /dev/null @@ -1,24 +0,0 @@ -mod goto_definition; -mod hover; -mod inlay_hints; -mod semantic_tokens; - -pub use hover::HoverResult; -pub use inlay_hints::InlayHint; -use mitki_db::RootDatabase; -pub use semantic_tokens::{SemanticToken, SemanticTokenKind}; - -#[derive(Default)] -pub struct Analysis { - db: RootDatabase, -} - -impl Analysis { - pub fn db(&self) -> &RootDatabase { - &self.db - } - - pub fn db_mut(&mut self) -> &mut RootDatabase { - &mut self.db - } -} diff --git a/crates/mitki-ide/src/analysis/goto_definition.rs b/crates/mitki-ide/src/analysis/goto_definition.rs deleted file mode 100644 index 9313780..0000000 --- a/crates/mitki-ide/src/analysis/goto_definition.rs +++ /dev/null @@ -1,460 +0,0 @@ -use mitki_analysis::{ResolveIntent, Semantics}; -use mitki_parse::FileParse as _; -use mitki_yellow::SyntaxKind; -use text_size::TextRange; - -use crate::{FilePosition, find_name_at_offset}; - -impl super::Analysis { - pub fn goto_definition( - &self, - FilePosition { file, offset }: FilePosition, - ) -> Option<(TextRange, TextRange)> { - let db = self.db(); - let semantics = Semantics::new(db, file); - let root = file.parse(db).syntax_node(); - - let name_at_offset = find_name_at_offset(root, offset, |kind| { - kind == SyntaxKind::NAME_REF - || kind == SyntaxKind::PATH_EXPR - || kind == SyntaxKind::PATH_TYPE - })?; - let original_token = name_at_offset.token; - let name_node = if name_at_offset.node.kind() == SyntaxKind::NAME_REF { - name_at_offset - .node - .ancestors() - .find(|ancestor| { - matches!(ancestor.kind(), SyntaxKind::PATH_EXPR | SyntaxKind::PATH_TYPE) - }) - .unwrap_or(name_at_offset.node) - } else { - name_at_offset.node - }; - - let resolution = semantics.resolve_at(db, &name_node, ResolveIntent::Any); - let target = semantics.definition_target_at(db, &name_node, resolution.target?)?; - - Some((original_token.trimmed_range(), target)) - } -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use mitki_inputs::File; - use text_size::{TextRange, TextSize}; - - use crate::{Analysis, FilePosition, extract_cursor_offset}; - - const DEF_MARKER: &str = "$def$"; - - fn extract_annotations(text: &str) -> Vec { - let mut line_start_map = BTreeMap::new(); - let mut annotations = Vec::new(); - let mut line_start: TextSize = 0.into(); - - for line in text.split_inclusive('\n') { - let line_length = if let Some((prefix, suffix)) = line.split_once("//") { - let ss_len = TextSize::of("//"); - let annotation_offset = TextSize::of(prefix) + ss_len; - for mut annotation in extract_line_annotations(suffix.trim_end_matches('\n')) { - annotation += annotation_offset; - let line_start = line_start_map.range(annotation.end()..).next().unwrap(); - - annotations.push(annotation + line_start.1); - } - - annotation_offset - } else { - TextSize::of(line) - }; - - line_start_map = line_start_map.split_off(&line_length); - line_start_map.insert(line_length, line_start); - line_start += TextSize::of(line); - } - - annotations - } - - fn extract_line_annotations(mut line: &str) -> Vec { - let mut annotations = Vec::new(); - let mut offset: TextSize = 0.into(); - - while let Some(idx) = line.find('^') { - offset += TextSize::try_from(idx).unwrap(); - line = &line[idx..]; - - let len = line.chars().take_while(|&it| it == '^').count(); - let range = TextRange::at(offset, len.try_into().unwrap()); - - annotations.push(range); - line = &line[len..]; - } - - annotations - } - - #[track_caller] - fn check(fixture: &str) { - let analysis = Analysis::default(); - let (offset, fixture) = extract_cursor_offset(fixture); - let annotations = extract_annotations(&fixture); - let file = File::new(analysis.db(), "".into(), fixture.clone()); - let file_position = FilePosition { file, offset }; - - assert_eq!(annotations.len(), 1); - let expected = annotations.into_iter().next().unwrap(); - - let (_, focus) = analysis.goto_definition(file_position).expect("no definition found"); - - assert_eq!(focus, expected); - } - - #[track_caller] - fn check_none(fixture: &str) { - let analysis = Analysis::default(); - let (offset, fixture) = extract_cursor_offset(fixture); - let file = File::new(analysis.db(), "".into(), fixture); - let file_position = FilePosition { file, offset }; - - assert!(analysis.goto_definition(file_position).is_none()); - } - - fn extract_offset_and_expected_range(text: &str) -> (TextSize, TextRange, String) { - let mut text = text.to_owned(); - let def_pos = text.find(DEF_MARKER).expect("Definition marker not found"); - text.replace_range(def_pos..def_pos + DEF_MARKER.len(), ""); - - let ident_len = text[def_pos..] - .chars() - .take_while(|ch| ch.is_ascii_alphanumeric() || *ch == '_') - .count(); - let expected = - TextRange::at(TextSize::from(def_pos as u32), TextSize::from(ident_len as u32)); - - let (cursor_pos, text) = extract_cursor_offset(&text); - - (cursor_pos, expected, text) - } - - #[track_caller] - fn check_def_marker(fixture: &str) { - let analysis = Analysis::default(); - let (offset, expected, fixture) = extract_offset_and_expected_range(fixture); - let file = File::new(analysis.db(), "".into(), fixture); - let file_position = FilePosition { file, offset }; - let (_, focus) = analysis.goto_definition(file_position).expect("no definition found"); - assert_eq!(focus, expected); - } - - #[test] - fn variable() { - check( - r#" -fun main() { - val x = 42 - //^ - $0x -} -"#, - ); - } - - #[test] - fn function() { - check( - r#" -fun add() {} - //^^^ -fun main() { - add$0(); -} -"#, - ); - } - - #[test] - fn parameter() { - check( - r#" -fun foo(x: i32) { - //^ - $0x -} -"#, - ); - } - - #[test] - fn if_block() { - check( - r#" -fun main() { - val x = 42 - //^ - if true { - $0x - } -} -"#, - ); - } - - #[test] - fn function_forward() { - check( - r#" -fun main() { - add$0(); -} - -fun add() {} - //^^^ -"#, - ); - } - - #[test] - fn closure_param() { - check( - r#" -fun main() { - val f = { x in - //^ - $0x - } -} -"#, - ); - } - - #[test] - fn closure_capture() { - check( - r#" -fun main() { - val x = 0 - //^ - val f = { - $0x - } -} -"#, - ); - } - - #[test] - fn shadow_inner() { - check( - r#" -fun main() { - val x = 0 - { - val x = 1 - //^ - $0x - } -} -"#, - ); - } - - #[test] - fn param_shadow() { - check( - r#" -fun foo(x: i32) { - val x = 42 - //^ - $0x -} -"#, - ); - } - - #[test] - fn else_if_param_does_not_jump_to_other_function_param() { - check_def_marker( - r#" -fun other(src: str) { - src -} - -fun digit_name($def$value: u32): str { - if value == 0 { - "0" - } else if val$0ue == 1 { - "1" - } else { - "2" - } -} -"#, - ); - } - - #[test] - fn nested_closure_capture() { - check( - r#" -fun main() { - val f = { x in - //^ - val g = { - $0x - } - } -} -"#, - ); - } - - #[test] - fn enum_variant_qualified() { - check( - r#" -enum Color { - Red, - //^^^ -} - -fun main() { - Color.$0Red -} -"#, - ); - } - - #[test] - fn enum_variant_without_prefix() { - check( - r#" -enum Color { - Red, - //^^^ -} - -fun main() { - .R$0ed -} -"#, - ); - } - - #[test] - fn enum_variant_without_prefix_ambiguous_no_definition() { - check_none( - r#" -enum Color { - Red, -} - -enum Light { - Red, -} - -fun main() { - .R$0ed -} -"#, - ); - } - - #[test] - fn struct_type_annotation() { - check_def_marker( - r#" -struct $def$Point { - x: int, - y: int, -} - -fun main() { - val p: P$0oint = Point { x: 1, y: 2 } -} -"#, - ); - } - - #[test] - fn enum_type_annotation() { - check_def_marker( - r#" -enum $def$Color { - Red, -} - -fun paint(color: C$0olor) {} -"#, - ); - } - - #[test] - fn top_level_type_reference() { - check_def_marker( - r#" -struct $def$Point { - x: int, -} - -struct Wrapper { - value: P$0oint, -} -"#, - ); - } - - #[test] - fn type_used_as_value() { - check_def_marker( - r#" -struct $def$Point { - x: int, -} - -fun main() { - P$0oint -} -"#, - ); - } - - #[test] - fn builtin_type_no_definition() { - check_none( - r#" -fun main() { - val x: i$0nt = 1 -} -"#, - ); - } - - #[test] - fn runtime_builtin_has_no_definition() { - check_none( - r#" -fun main() { - pr$0int_i32(1) -} -"#, - ); - } - - #[test] - fn crate_qualified_function() { - check( - r#" -fun add() {} - //^^^ - -fun main() { - crate::ad$0d(); -} -"#, - ); - } -} diff --git a/crates/mitki-ide/src/analysis/hover.rs b/crates/mitki-ide/src/analysis/hover.rs deleted file mode 100644 index 7c63f3e..0000000 --- a/crates/mitki-ide/src/analysis/hover.rs +++ /dev/null @@ -1,469 +0,0 @@ -use mitki_analysis::{ResolveIntent, Semantics}; -use mitki_hir::hir::{ExprId, NodeKind, TyId}; -use mitki_lower::hir::HasFunction as _; -use mitki_parse::FileParse as _; -use mitki_resolve::BindingId; -use mitki_span::IntoSymbol as _; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::SyntaxKind; -use mitki_yellow::ast::{self, HasName as _, Node as _}; -use text_size::TextRange; - -use crate::{FilePosition, find_name_at_offset}; - -pub struct HoverResult { - pub range: TextRange, - pub contents: String, -} - -impl super::Analysis { - pub fn hover(&self, FilePosition { file, offset }: FilePosition) -> Option { - let db = self.db(); - let semantics = Semantics::new(db, file); - let root = file.parse(db).syntax_node(); - - let name_at_offset = find_name_at_offset(root, offset, |kind| { - kind == SyntaxKind::NAME_REF || kind == SyntaxKind::IDENT - })?; - let original_token = name_at_offset.token; - let name_node = name_at_offset - .node - .ancestors() - .find(|ancestor| { - matches!(ancestor.kind(), SyntaxKind::PATH_EXPR | SyntaxKind::PATH_TYPE) - }) - .unwrap_or(name_at_offset.node); - - let name = original_token.text_trimmed().into_symbol(db); - let function_hover_contents = |function: ast::Function<'_>| { - let fn_name = function.name().map(|n| n.as_str()).unwrap_or_default(); - let input_tys = function - .params() - .map(|params| { - params - .iter() - .map(|param| { - param.ty().map_or_else( - || "{unknown}".to_owned(), - |ty| ty.syntax().text_trimmed().to_owned(), - ) - }) - .collect::>() - }) - .unwrap_or_default(); - let output_ty = function - .ret_type() - .and_then(|ret| ret.ty()) - .map_or_else(|| "()".to_owned(), |ty| ty.syntax().text_trimmed().to_owned()); - format!( - "```mitki\nfun {}: fun({}) -> {}\n```", - fn_name, - input_tys.join(", "), - output_ty - ) - }; - - if name_node.kind() == SyntaxKind::IDENT { - if let Some(function_syntax) = name_node.ancestors().find_map(ast::Function::cast) - && function_syntax.name().is_some_and(|decl_name| { - decl_name.syntax().text_range() == name_node.text_range() - }) - { - return Some(HoverResult { - range: original_token.trimmed_range(), - contents: function_hover_contents(function_syntax), - }); - } - - if let Some(struct_syntax) = name_node.ancestors().find_map(ast::StructDef::cast) - && struct_syntax.name().is_some_and(|decl_name| { - decl_name.syntax().text_range() == name_node.text_range() - }) - { - return Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\ntype {}\n```", struct_syntax.name()?.as_str()), - }); - } - - if let Some(enum_syntax) = name_node.ancestors().find_map(ast::EnumDef::cast) - && enum_syntax.name().is_some_and(|decl_name| { - decl_name.syntax().text_range() == name_node.text_range() - }) - { - return Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\ntype {}\n```", enum_syntax.name()?.as_str()), - }); - } - } - - let location = name_node - .ancestors() - .find_map(ast::Function::cast) - .map(|function| semantics.function(function.syntax()))?; - let inference = location.infer(db); - let function = location.hir_function(db).function(db); - let source_map = location.hir_function(db).source_map(db); - let param_annotation_ty_text = |binding_expr: ExprId| { - for ¶m in function.params() { - let (pattern, param_ty) = function.node_store().param(param); - if param_ty == TyId::ZERO - || function.node_store().node_kind(pattern) != NodeKind::PatBinding - { - continue; - } - let (param_name, _) = function - .node_store() - .pat_binding(function.node_store().as_pat_binding(pattern)?); - if ExprId::from(param_name) != binding_expr { - continue; - } - let ty_syntax = source_map.try_type_syntax(param_ty)?.to_node(&root); - return Some(ty_syntax.text_trimmed().to_owned()); - } - None - }; - - if name_node.kind() == SyntaxKind::IDENT { - if let Some(expr_id) = source_map.syntax_expr(&name_node) { - let ty = inference.type_of_node(expr_id)?; - let ty_text = param_annotation_ty_text(expr_id) - .unwrap_or_else(|| format!("{}", ty.display(db))); - - return Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\nval {}: {ty_text}\n```", name.text(db)), - }); - } - - return None; - } - - let resolution = semantics.resolve_at(db, &name_node, ResolveIntent::Any); - - match resolution.binding? { - BindingId::Local(binding) | BindingId::Param(binding) => { - let ty = inference.type_of_node(binding.into())?; - let ty_text = param_annotation_ty_text(binding.into()) - .unwrap_or_else(|| format!("{}", ty.display(db))); - Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\nval {}: {ty_text}\n```", name.text(db)), - }) - } - BindingId::CompilerIntrinsic(intrinsic) => Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\nintrinsic {}\n```", intrinsic.source_name()), - }), - BindingId::RuntimeFunction(function) => Some(HoverResult { - range: original_token.trimmed_range(), - contents: function.hover_text(), - }), - BindingId::Function(func) => { - let function_syntax = func.source(db); - - Some(HoverResult { - range: original_token.trimmed_range(), - contents: function_hover_contents(function_syntax), - }) - } - BindingId::Struct(_) | BindingId::Enum(_) | BindingId::BuiltinType(_) => { - Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!("```mitki\ntype {}\n```", name.text(db)), - }) - } - BindingId::EnumVariant(variant) => Some(HoverResult { - range: original_token.trimmed_range(), - contents: format!( - "```mitki\nenum-member {}\n```", - variant.source(db).name()?.as_str() - ), - }), - } - } -} - -#[cfg(test)] -mod tests { - use mitki_inputs::File; - - use crate::{Analysis, FilePosition, extract_cursor_offset}; - - #[track_caller] - fn check(fixture: &str, expected: &str) { - let analysis = Analysis::default(); - let (offset, fixture) = extract_cursor_offset(fixture); - let file = File::new(analysis.db(), "".into(), fixture); - let result = analysis.hover(FilePosition { file, offset }).expect("expected hover result"); - assert_eq!(result.contents, expected); - } - - #[track_caller] - fn check_no_hover(fixture: &str) { - let analysis = Analysis::default(); - let (offset, fixture) = extract_cursor_offset(fixture); - let file = File::new(analysis.db(), "".into(), fixture); - assert!(analysis.hover(FilePosition { file, offset }).is_none()); - } - - #[test] - fn local_variable() { - check( - r#" -fun main() { - val x = 42 - $0x -} -"#, - "```mitki\nval x: int\n```", - ); - } - - #[test] - fn parameter() { - check( - r#" -fun foo(x: int) { - $0x -} -"#, - "```mitki\nval x: int\n```", - ); - } - - #[test] - fn parameter_declaration() { - check( - r#" -fun foo($0x: int) { - x -} -"#, - "```mitki\nval x: int\n```", - ); - } - - #[test] - fn generic_parameter_reference() { - check( - r#" -fun hello[A](id: A): A { - $0id -} -"#, - "```mitki\nval id: A\n```", - ); - } - - #[test] - fn function_call() { - check( - r#" -fun add(x: int, y: int): int { x + y } - -fun main() { - $0add(1, 2); -} -"#, - "```mitki\nfun add: fun(int, int) -> int\n```", - ); - } - - #[test] - fn generic_function_call_declared_signature() { - check( - r#" -enum Color { - Red, -} - -fun hello[A](id: A): A { - id -} - -fun main() { - $0hello(Color.Red); -} -"#, - "```mitki\nfun hello: fun(A) -> A\n```", - ); - } - - #[test] - fn function_declaration_name() { - check( - r#" -fun $0hello[A](id: A): A { - id -} -"#, - "```mitki\nfun hello: fun(A) -> A\n```", - ); - } - - #[test] - fn struct_declaration_name() { - check( - r#" -struct $0Point { - x: int, - y: int, -} -"#, - "```mitki\ntype Point\n```", - ); - } - - #[test] - fn enum_declaration_name() { - check( - r#" -enum $0Color { - Red, -} -"#, - "```mitki\ntype Color\n```", - ); - } - - #[test] - fn function_no_params() { - check( - r#" -fun noop() {} - -fun main() { - $0noop(); -} -"#, - "```mitki\nfun noop: fun() -> ()\n```", - ); - } - - #[test] - fn runtime_builtin_call() { - check( - r#" -fun main() { - $0std::io::print_int(1) -} -"#, - "```mitki\nfun print_int: fun(int) -> ()\n```", - ); - } - - #[test] - fn builtin_type_in_annotation() { - check_no_hover( - r#" -fun main() { - val x: $0int = 42 -} -"#, - ); - } - - #[test] - fn type_used_as_value() { - check( - r#" -fun main() { - $0int -} -"#, - "```mitki\ntype int\n```", - ); - } - - #[test] - fn struct_type_used_as_value() { - check( - r#" -struct Point { - x: int, - y: int, -} - -fun main() { - $0Point -} -"#, - "```mitki\ntype Point\n```", - ); - } - - #[test] - fn bool_variable() { - check( - r#" -fun main() { - val flag = true - $0flag -} -"#, - "```mitki\nval flag: bool\n```", - ); - } - - #[test] - fn closure_param_unknown() { - check( - r#" -fun main() { - val f = { x in - $0x - } -} -"#, - "```mitki\nval x: {unknown}\n```", - ); - } - - #[test] - fn no_hover_on_keyword() { - check_no_hover( - r#" -$0fun main() {} -"#, - ); - } - - #[test] - fn no_hover_on_literal() { - check_no_hover( - r#" -fun main() { - $042 -} -"#, - ); - } - - #[test] - fn string_variable() { - check( - r#" -fun main() { - val s = "hello" - $0s -} -"#, - "```mitki\nval s: str\n```", - ); - } - - #[test] - fn float_variable() { - check( - r#" -fun main() { - val f = 3.14 - $0f -} -"#, - "```mitki\nval f: float\n```", - ); - } -} diff --git a/crates/mitki-ide/src/analysis/inlay_hints.rs b/crates/mitki-ide/src/analysis/inlay_hints.rs deleted file mode 100644 index 44adf6f..0000000 --- a/crates/mitki-ide/src/analysis/inlay_hints.rs +++ /dev/null @@ -1,632 +0,0 @@ -use mitki_hir::hir::{ExprId, NodeKind, StmtId, TyId}; -use mitki_hir::ty::TyKind; -use mitki_inputs::File; -use mitki_lower::HasItemDecls as _; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::Declaration; -use mitki_typeck::infer::Inferable as _; -use text_size::{TextRange, TextSize}; - -pub struct InlayHint { - pub offset: TextSize, - pub label: String, -} - -impl super::Analysis { - pub fn inlay_hints(&self, file: File, range: TextRange) -> Vec { - let db = self.db(); - let mut hints = Vec::new(); - - for declaration in file.item_decls(db).declarations() { - match declaration { - Declaration::Function(func) => { - let source_map = func.hir_function(db).source_map(db); - let function = func.hir_function(db).function(db); - let nodes = function.node_store(); - let inference = func.infer(db); - - // Hints for function parameters without type annotations. - for ¶m in function.params() { - let (pattern, ty_id) = nodes.param(param); - if ty_id != TyId::ZERO { - continue; - } - for name in nodes.pattern_binding_names(pattern) { - push_binding_hint( - db, - source_map, - inference, - name.into(), - false, - range, - &mut hints, - ); - } - } - - // Hints for local variable bindings without type annotations. - if function.body() != ExprId::ZERO { - collect_binding_hints( - db, - nodes, - source_map, - inference, - function.body(), - range, - &mut hints, - ); - } - } - Declaration::BoundaryInstance(_) - | Declaration::Struct(_) - | Declaration::Enum(_) => {} - } - } - - hints.sort_by_key(|h| h.offset); - hints - } -} - -fn collect_binding_hints<'db>( - db: &'db dyn salsa::Database, - nodes: &mitki_hir::hir::NodeStore<'db>, - source_map: &mitki_lower::hir::FunctionSourceMap, - inference: &mitki_typeck::infer::Inference<'db>, - expr: ExprId, - range: TextRange, - hints: &mut Vec, -) { - match nodes.node_kind(expr) { - NodeKind::Tuple => { - let Some(tuple_id) = nodes.as_tuple(expr) else { return }; - for item in nodes.tuple(tuple_id).iter() { - collect_binding_hints(db, nodes, source_map, inference, item, range, hints); - } - } - NodeKind::Block => { - let Some(block_id) = nodes.as_block(expr) else { return }; - let (stmts, tail) = nodes.block_stmts(block_id); - for stmt in stmts.iter() { - if nodes.node_kind(stmt) == NodeKind::LocalVar { - let Some(var_id) = nodes.as_local_var(stmt) else { continue }; - let var = nodes.local_var(var_id); - if var.ty == TyId::ZERO { - for name in nodes.pattern_binding_names(var.pattern) { - push_binding_hint( - db, - source_map, - inference, - name.into(), - true, - range, - hints, - ); - } - } - if var.initializer != ExprId::ZERO { - collect_binding_hints( - db, - nodes, - source_map, - inference, - var.initializer, - range, - hints, - ); - } - } else if let Some(expr) = stmt_as_expr(nodes, stmt) { - collect_binding_hints(db, nodes, source_map, inference, expr, range, hints); - } - } - if tail != ExprId::ZERO { - collect_binding_hints(db, nodes, source_map, inference, tail, range, hints); - } - } - NodeKind::Call => { - let Some(call_id) = nodes.as_call(expr) else { return }; - let (callee, args) = nodes.call(call_id); - if callee != ExprId::ZERO { - collect_binding_hints(db, nodes, source_map, inference, callee, range, hints); - } - for arg in args.iter() { - collect_binding_hints(db, nodes, source_map, inference, arg, range, hints); - } - } - NodeKind::Binary => { - let Some(binary_id) = nodes.as_binary(expr) else { return }; - let binary = nodes.binary(binary_id); - collect_binding_hints(db, nodes, source_map, inference, binary.lhs, range, hints); - collect_binding_hints(db, nodes, source_map, inference, binary.rhs, range, hints); - } - NodeKind::Postfix => { - let Some(postfix_id) = nodes.as_postfix(expr) else { return }; - let postfix = nodes.postfix(postfix_id); - collect_binding_hints(db, nodes, source_map, inference, postfix.expr, range, hints); - } - NodeKind::Prefix => { - let Some(prefix_id) = nodes.as_prefix(expr) else { return }; - let prefix = nodes.prefix(prefix_id); - collect_binding_hints(db, nodes, source_map, inference, prefix.expr, range, hints); - } - NodeKind::LoopExpr => { - let Some(loop_id) = nodes.as_loop_expr(expr) else { return }; - let (body, _) = nodes.loop_expr(loop_id); - if body != ExprId::ZERO { - collect_binding_hints(db, nodes, source_map, inference, body, range, hints); - } - } - NodeKind::If => { - let Some(if_id) = nodes.as_if(expr) else { return }; - let if_expr = nodes.if_expr(if_id); - collect_binding_hints(db, nodes, source_map, inference, if_expr.cond, range, hints); - if if_expr.then_branch != ExprId::ZERO { - collect_binding_hints( - db, - nodes, - source_map, - inference, - if_expr.then_branch, - range, - hints, - ); - } - if if_expr.else_branch != ExprId::ZERO { - collect_binding_hints( - db, - nodes, - source_map, - inference, - if_expr.else_branch, - range, - hints, - ); - } - } - NodeKind::Match => { - let Some(match_id) = nodes.as_match(expr) else { return }; - let (scrutinee, arms) = nodes.match_expr(match_id); - collect_binding_hints(db, nodes, source_map, inference, scrutinee, range, hints); - for arm in arms.iter() { - let Some(arm_id) = nodes.as_match_arm(arm) else { continue }; - let (pattern, body) = nodes.match_arm(arm_id); - for name in nodes.pattern_binding_names(pattern) { - push_binding_hint(db, source_map, inference, name.into(), false, range, hints); - } - collect_binding_hints(db, nodes, source_map, inference, body, range, hints); - } - } - NodeKind::Closure => { - let Some(closure_id) = nodes.as_closure(expr) else { return }; - let (params, body) = nodes.closure_parts(closure_id); - for param in params.iter() { - let (pattern, ty_id) = nodes.param(param); - if ty_id != TyId::ZERO { - continue; - } - for name in nodes.pattern_binding_names(pattern) { - push_binding_hint(db, source_map, inference, name.into(), false, range, hints); - } - } - if body != ExprId::ZERO { - collect_binding_hints(db, nodes, source_map, inference, body, range, hints); - } - } - NodeKind::Array => { - let Some(array_id) = nodes.as_array(expr) else { return }; - for item in nodes.array(array_id).iter() { - collect_binding_hints(db, nodes, source_map, inference, item, range, hints); - } - } - NodeKind::ArrayRepeat => { - let Some(array_repeat_id) = nodes.as_array_repeat(expr) else { return }; - let (value, len) = nodes.array_repeat(array_repeat_id); - collect_binding_hints(db, nodes, source_map, inference, value, range, hints); - collect_binding_hints(db, nodes, source_map, inference, len, range, hints); - } - NodeKind::Field => { - let Some(field_id) = nodes.as_field(expr) else { return }; - let (base, _) = nodes.field(field_id); - if base != ExprId::ZERO { - collect_binding_hints(db, nodes, source_map, inference, base, range, hints); - } - } - NodeKind::StructExpr => { - let Some(struct_id) = nodes.as_struct_expr(expr) else { return }; - let items = nodes.struct_expr(struct_id); - let has_struct_name = items.len() % 2 == 1; - let mut index = if has_struct_name { 2 } else { 1 }; - while index < items.len() { - collect_binding_hints( - db, - nodes, - source_map, - inference, - items.get(index).unwrap(), - range, - hints, - ); - index += 2; - } - } - _ => {} - } -} - -fn push_binding_hint<'db>( - db: &'db dyn salsa::Database, - source_map: &mitki_lower::hir::FunctionSourceMap, - inference: &mitki_typeck::infer::Inference<'db>, - binding: ExprId, - allow_unit: bool, - range: TextRange, - hints: &mut Vec, -) { - let Some(ty) = inference.type_of_node(binding) else { - return; - }; - if matches!(ty.kind(db), TyKind::Unknown) - || (!allow_unit && matches!(ty.kind(db), TyKind::Tuple(items) if items.is_empty())) - { - return; - } - let ptr = source_map.node_syntax(binding); - if !range.contains_range(ptr.range) { - return; - } - hints.push(InlayHint { offset: ptr.range.end(), label: format!(": {}", ty.display(db)) }); -} - -fn stmt_as_expr(nodes: &mitki_hir::hir::NodeStore<'_>, stmt: StmtId) -> Option { - match nodes.node_kind(stmt) { - NodeKind::Name => nodes.as_name(stmt).map(Into::into), - NodeKind::True => nodes.as_true(stmt).map(Into::into), - NodeKind::False => nodes.as_false(stmt).map(Into::into), - NodeKind::Error => nodes.as_error(stmt).map(Into::into), - NodeKind::Int => nodes.as_int(stmt).map(Into::into), - NodeKind::Float => nodes.as_float(stmt).map(Into::into), - NodeKind::String => nodes.as_string(stmt).map(Into::into), - NodeKind::Char => nodes.as_char(stmt).map(Into::into), - NodeKind::Tuple => nodes.as_tuple(stmt).map(Into::into), - NodeKind::Array => nodes.as_array(stmt).map(Into::into), - NodeKind::ArrayRepeat => nodes.as_array_repeat(stmt).map(Into::into), - NodeKind::Call => nodes.as_call(stmt).map(Into::into), - NodeKind::Field => nodes.as_field(stmt).map(Into::into), - NodeKind::Binary => nodes.as_binary(stmt).map(Into::into), - NodeKind::Postfix => nodes.as_postfix(stmt).map(Into::into), - NodeKind::Prefix => nodes.as_prefix(stmt).map(Into::into), - NodeKind::LoopExpr => nodes.as_loop_expr(stmt).map(Into::into), - NodeKind::BreakExpr => nodes.as_break_expr(stmt).map(Into::into), - NodeKind::ContinueExpr => nodes.as_continue_expr(stmt).map(Into::into), - NodeKind::If => nodes.as_if(stmt).map(Into::into), - NodeKind::Match => nodes.as_match(stmt).map(Into::into), - NodeKind::Closure => nodes.as_closure(stmt).map(Into::into), - NodeKind::Block => nodes.as_block(stmt).map(Into::into), - NodeKind::StructExpr => nodes.as_struct_expr(stmt).map(Into::into), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use mitki_inputs::File; - use text_size::{TextRange, TextSize}; - - use crate::Analysis; - - fn whole_file_range() -> TextRange { - TextRange::new(TextSize::from(0), TextSize::from(u32::MAX)) - } - - #[track_caller] - fn check(fixture: &str, expected: &[&str]) { - let analysis = Analysis::default(); - let file = File::new(analysis.db(), "".into(), fixture.to_owned()); - let hints = analysis.inlay_hints(file, whole_file_range()); - let labels: Vec<&str> = hints.iter().map(|h| h.label.as_str()).collect(); - assert_eq!(labels, expected); - } - - #[test] - fn local_variable_int() { - check( - r#" -fun main() { - val x = 42 -} -"#, - &[": int"], - ); - } - - #[test] - fn local_variable_string() { - check( - r#" -fun main() { - val s = "hello" -} -"#, - &[": str"], - ); - } - - #[test] - fn local_variable_float() { - check( - r#" -fun main() { - val f = 3.14 -} -"#, - &[": float"], - ); - } - - #[test] - fn local_variable_bool() { - check( - r#" -fun main() { - val b = true -} -"#, - &[": bool"], - ); - } - - #[test] - fn no_hint_with_type_annotation() { - check( - r#" -fun main() { - val x: int = 42 -} -"#, - &[], - ); - } - - #[test] - fn multiple_bindings() { - check( - r#" -fun main() { - val x = 1 - val y = 2 - val z = 3 -} -"#, - &[": int", ": int", ": int"], - ); - } - - #[test] - fn mixed_annotated_and_inferred() { - check( - r#" -fun main() { - val x: int = 1 - val y = "hello" - val z: bool = true -} -"#, - &[": str"], - ); - } - - #[test] - fn parameter_without_type() { - check( - r#" -fun foo(x) { - x -} -"#, - &[], - ); - } - - #[test] - fn parameter_with_type_no_hint() { - check( - r#" -fun foo(x: int) { - x -} -"#, - &[], - ); - } - - #[test] - fn empty_function() { - check( - r#" -fun main() {} -"#, - &[], - ); - } - - #[test] - fn binding_in_if_branch() { - check( - r#" -fun main() { - if true { - val a = 1 - } else { - val b = 2 - } -} -"#, - &[": int", ": int"], - ); - } - - #[test] - fn nested_blocks() { - check( - r#" -fun main() { - val x = 1 - { - val y = 2 - } -} -"#, - &[": int", ": int"], - ); - } - - #[test] - fn function_call_binding() { - check( - r#" -fun add(x: int, y: int): int { x + y } - -fun main() { - val result = add(1, 2) -} -"#, - &[": int"], - ); - } - - #[test] - fn multiple_functions() { - check( - r#" -fun foo() { - val a = 1 -} - -fun bar() { - val b = "hello" -} -"#, - &[": int", ": str"], - ); - } - - #[test] - fn hints_sorted_by_offset() { - check( - r#" -fun main() { - val first = 1 - val second = "two" - val third = true -} -"#, - &[": int", ": str", ": bool"], - ); - } - - #[test] - fn range_filter() { - let fixture = r#" -fun main() { - val x = 1 - val y = 2 -} -"#; - let analysis = Analysis::default(); - let file = File::new(analysis.db(), "".into(), fixture.to_owned()); - // Use an empty range at the beginning — should produce no hints. - let hints = - analysis.inlay_hints(file, TextRange::new(TextSize::from(0), TextSize::from(0))); - assert!(hints.is_empty()); - } - - #[test] - fn struct_no_hints() { - check( - r#" -struct Point { - x: int, - y: int, -} -"#, - &[], - ); - } - - #[test] - fn local_variable_enum_variant_without_type_prefix() { - check( - r#" -enum Color { - Red, -} - -fun main() { - val c = .Red -} -"#, - &[], - ); - } - - #[test] - fn local_variable_enum_variant_constructor_without_type_prefix() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun main() { - val value = .Some(1) -} -"#, - &[], - ); - } - - #[test] - fn local_variable_enum_variant_constructor_without_type_prefix_with_usage() { - check( - r#" -enum Option { - Some(int), - None, -} - -fun takes_option(value: Option) {} - -fun main() { - val value = .Some(1) - takes_option(value) -} -"#, - &[": Option"], - ); - } - - #[test] - fn local_variable_if_unit_not_duplicated() { - check( - r#" -fun main() { - val n = if true { } else { } -} -"#, - &[": ()"], - ); - } - - #[test] - fn incomplete_tuple_pattern_does_not_panic() { - check( - r#" -fun main() { - val (x, ) = (1, 2) -} -"#, - &[], - ); - } -} diff --git a/crates/mitki-ide/src/analysis/semantic_tokens.rs b/crates/mitki-ide/src/analysis/semantic_tokens.rs deleted file mode 100644 index ea58cc6..0000000 --- a/crates/mitki-ide/src/analysis/semantic_tokens.rs +++ /dev/null @@ -1,140 +0,0 @@ -use mitki_analysis::{ResolveIntent, Semantics}; -use mitki_inputs::File; -use mitki_parse::FileParse as _; -use mitki_resolve::BindingId; -use mitki_yellow::SyntaxKind; -use text_size::TextRange; - -#[derive(Clone, Copy)] -pub struct SemanticToken { - pub range: TextRange, - pub kind: SemanticTokenKind, -} - -#[derive(Clone, Copy)] -pub enum SemanticTokenKind { - Function, - Parameter, - Variable, - Type, - EnumMember, - BuiltinType, - BuiltinFunction, -} - -impl super::Analysis { - pub fn semantic_tokens(&self, file: File, range: Option) -> Vec { - let db = self.db(); - let semantics = Semantics::new(db, file); - let root = file.parse(db).syntax_node(); - let mut tokens = Vec::new(); - let mut nodes = vec![root]; - - while let Some(node) = nodes.pop() { - let children = node.children_with_tokens().collect::>(); - for child in children.into_iter().rev() { - match child { - mitki_yellow::SyntaxElement::Node(child_node) => nodes.push(child_node), - mitki_yellow::SyntaxElement::Token(token) => { - if token.kind() != SyntaxKind::NAME || token.is_trivia() { - continue; - } - - let trimmed_range = token.trimmed_range(); - if let Some(filter) = range - && (trimmed_range.end() <= filter.start() - || trimmed_range.start() >= filter.end()) - { - continue; - } - - let node = token.parent(); - let resolved_node = node - .ancestors() - .find(|ancestor| { - matches!( - ancestor.kind(), - SyntaxKind::PATH_EXPR | SyntaxKind::PATH_TYPE - ) - }) - .unwrap_or(node); - if !matches!( - resolved_node.kind(), - SyntaxKind::IDENT - | SyntaxKind::NAME_REF - | SyntaxKind::PATH_EXPR - | SyntaxKind::PATH_TYPE - ) { - continue; - } - - let resolution = - semantics.resolve_at(db, &resolved_node, ResolveIntent::Any); - let Some(binding) = resolution.binding else { - continue; - }; - - let kind = match binding { - BindingId::Function(_) => SemanticTokenKind::Function, - BindingId::Param(_) => SemanticTokenKind::Parameter, - BindingId::Local(_) => SemanticTokenKind::Variable, - BindingId::Struct(_) | BindingId::Enum(_) => SemanticTokenKind::Type, - BindingId::EnumVariant(_) => SemanticTokenKind::EnumMember, - BindingId::BuiltinType(_) => SemanticTokenKind::BuiltinType, - BindingId::RuntimeFunction(_) | BindingId::CompilerIntrinsic(_) => { - SemanticTokenKind::BuiltinFunction - } - }; - - tokens.push(SemanticToken { range: trimmed_range, kind }); - } - } - } - } - - tokens.sort_by_key(|token| token.range.start()); - tokens - } -} - -#[cfg(test)] -mod tests { - use mitki_inputs::File; - - use super::SemanticTokenKind; - use crate::Analysis; - - #[test] - fn classifies_binding_kinds() { - let analysis = Analysis::default(); - let file = File::new( - analysis.db(), - "semantic_tokens.mitki".into(), - r#" -struct Point { - x: int, -} - -enum Color { - Red, -} - -fun paint(point: Point) { - val local = point - local - Color.Red - std::io::print_int(1) -} -"# - .to_owned(), - ); - - let tokens = analysis.semantic_tokens(file, None); - - assert!(tokens.iter().any(|token| matches!(token.kind, SemanticTokenKind::Type))); - assert!(tokens.iter().any(|token| matches!(token.kind, SemanticTokenKind::Function))); - assert!(tokens.iter().any(|token| matches!(token.kind, SemanticTokenKind::Parameter))); - assert!(tokens.iter().any(|token| matches!(token.kind, SemanticTokenKind::Variable))); - assert!(tokens.iter().any(|token| matches!(token.kind, SemanticTokenKind::EnumMember))); - } -} diff --git a/crates/mitki-ide/src/lib.rs b/crates/mitki-ide/src/lib.rs deleted file mode 100644 index 66aca41..0000000 --- a/crates/mitki-ide/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -mod analysis; - -pub use analysis::{Analysis, HoverResult, InlayHint, SemanticToken, SemanticTokenKind}; - -#[derive(Clone, Copy)] -pub struct FilePosition { - pub file: mitki_inputs::File, - pub offset: text_size::TextSize, -} - -pub fn extract_cursor_offset(text: &str) -> (text_size::TextSize, String) { - let marker = "$0"; - let cursor_pos = text.find(marker).expect("Cursor marker not found"); - let mut new_text = String::with_capacity(text.len() - marker.len()); - new_text.push_str(&text[..cursor_pos]); - new_text.push_str(&text[cursor_pos + marker.len()..]); - (text_size::TextSize::from(cursor_pos as u32), new_text) -} - -#[derive(Clone, Copy)] -struct NameAtOffset<'db> { - token: mitki_yellow::SyntaxToken<'db>, - node: mitki_yellow::SyntaxNode<'db>, -} - -fn find_name_at_offset<'db>( - root: mitki_yellow::SyntaxNode<'db>, - offset: text_size::TextSize, - is_valid_parent: impl Fn(mitki_yellow::SyntaxKind) -> bool, -) -> Option> { - let tokens = root.token_at_offset(offset); - let token = pick_best_token(tokens, |kind| match kind { - mitki_yellow::SyntaxKind::NAME => 2, - _ => 1, - })?; - - let node = token.parent(); - if !is_valid_parent(node.kind()) { - return None; - } - - Some(NameAtOffset { token, node }) -} - -fn pick_best_token<'db>( - tokens: mitki_yellow::TokenAtOffset>, - f: impl Fn(mitki_yellow::SyntaxKind) -> usize, -) -> Option> { - tokens.filter(|token| !token.is_trivia()).max_by_key(move |t| f(t.kind())) -} diff --git a/crates/mitki-inputs/Cargo.toml b/crates/mitki-inputs/Cargo.toml deleted file mode 100644 index 5d5bbe6..0000000 --- a/crates/mitki-inputs/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "mitki-inputs" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -camino.workspace = true -line-index = "0.1" -salsa.workspace = true diff --git a/crates/mitki-inputs/src/lib.rs b/crates/mitki-inputs/src/lib.rs deleted file mode 100644 index 3c77551..0000000 --- a/crates/mitki-inputs/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub use line_index::LineIndex; - -#[salsa::input(debug)] -pub struct File { - #[returns(deref)] - pub path: camino::Utf8PathBuf, - #[returns(deref)] - pub text: String, -} - -#[salsa::tracked] -impl File { - #[salsa::tracked(returns(ref), no_eq)] - pub fn line_index(self, db: &dyn salsa::Database) -> LineIndex { - LineIndex::new(self.text(db)) - } -} - -#[salsa::interned(debug)] -pub struct PackageId<'db> { - pub root_file: File, -} - -#[salsa::interned(debug)] -pub struct ModuleId<'db> { - pub package: PackageId<'db>, - pub file: File, -} diff --git a/crates/mitki-lower/Cargo.toml b/crates/mitki-lower/Cargo.toml deleted file mode 100644 index d073281..0000000 --- a/crates/mitki-lower/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "mitki-lower" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -camino.workspace = true -hashbrown = "0.15" -indexmap = "2.6" -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-parse.workspace = true -mitki-span.workspace = true -mitki-yellow.workspace = true -rustc-hash = "2.0" -salsa.workspace = true diff --git a/crates/mitki-lower/src/ast_map.rs b/crates/mitki-lower/src/ast_map.rs deleted file mode 100644 index 0279998..0000000 --- a/crates/mitki-lower/src/ast_map.rs +++ /dev/null @@ -1,75 +0,0 @@ -use hashbrown::HashTable; -use mitki_hir::arena::{Arena, Key}; -use mitki_inputs::File; -use mitki_parse::FileParse as _; -use mitki_yellow::{SyntaxKind, SyntaxNode, SyntaxNodePtr}; -use salsa::Database; - -pub trait HasAstMap { - fn ast_map(self, db: &dyn Database) -> &AstMap; -} - -#[salsa::tracked] -impl HasAstMap for File { - #[salsa::tracked(returns(ref), no_eq)] - fn ast_map(self, db: &dyn Database) -> AstMap { - AstMap::from_root(&self.parse(db).syntax_node()) - } -} - -#[derive(Debug)] -pub struct AstMap { - arena: Arena, - map: HashTable>, -} - -impl PartialEq for AstMap { - fn eq(&self, other: &Self) -> bool { - self.arena == other.arena - } -} - -impl Eq for AstMap {} - -impl AstMap { - pub fn from_root(root: &SyntaxNode<'_>) -> Self { - let mut arena = Arena::new(); - let mut map = HashTable::default(); - - root.children().for_each(|node| { - if matches!( - node.kind(), - SyntaxKind::FN - | SyntaxKind::INSTANCE_ITEM - | SyntaxKind::MOD_ITEM - | SyntaxKind::USE_ITEM - | SyntaxKind::STRUCT_DEF - | SyntaxKind::ENUM_DEF - ) { - arena.alloc(SyntaxNodePtr::new(&node)); - } - }); - - for (key, value) in arena.iter_enumerated() { - let hash = hash_one(&value); - map.insert_unique(hash, key, |&key| hash_one(&arena[key])); - } - - Self { arena, map } - } - - pub fn find_id(&self, node: &SyntaxNode<'_>) -> Key { - let ptr = SyntaxNodePtr::new(node); - *self.map.find(hash_one(&ptr), |&key| self.arena[key] == ptr).unwrap() - } - - pub fn find_node(&self, index: Key) -> &SyntaxNodePtr { - &self.arena[index] - } -} - -fn hash_one(t: &T) -> u64 { - use std::hash::BuildHasher as _; - - std::hash::BuildHasherDefault::::default().hash_one(t) -} diff --git a/crates/mitki-lower/src/hir.rs b/crates/mitki-lower/src/hir.rs deleted file mode 100644 index 3952381..0000000 --- a/crates/mitki-lower/src/hir.rs +++ /dev/null @@ -1,62 +0,0 @@ -mod function; -pub use function::FunctionSourceMap; -use mitki_hir::hir::Function; -use mitki_span::IntoSymbol as _; -use mitki_yellow::ast::HasName as _; - -#[salsa::tracked] -pub struct FunctionWithSourceMap<'db> { - #[tracked] - #[returns(ref)] - pub function: Function<'db>, - #[tracked] - #[no_eq] - #[returns(ref)] - pub source_map: FunctionSourceMap, -} - -pub trait HasFunction<'db> { - fn hir_function(self, db: &'db dyn salsa::Database) -> FunctionWithSourceMap<'db>; -} - -#[salsa::tracked] -impl<'db> HasFunction<'db> for crate::item::scope::FunctionLocation<'db> { - #[salsa::tracked] - fn hir_function(self, db: &'db dyn salsa::Database) -> FunctionWithSourceMap<'db> { - function::FunctionBuilder::new(db).build(&self.source(db)) - } -} - -#[salsa::tracked] -impl<'db> HasFunction<'db> for crate::item::scope::StructDestructorLocation<'db> { - #[salsa::tracked] - fn hir_function(self, db: &'db dyn salsa::Database) -> FunctionWithSourceMap<'db> { - let parent = self.parent(db); - let source = parent.source(db); - let type_params = - source.type_params().map(|tp| tp.as_str().into_symbol(db)).collect::>(); - let owner_name = source.name().expect("struct destructor owner should have a name"); - function::FunctionBuilder::new(db).build_destructor( - owner_name.as_str().into_symbol(db), - type_params, - &self.source(db), - ) - } -} - -#[salsa::tracked] -impl<'db> HasFunction<'db> for crate::item::scope::EnumDestructorLocation<'db> { - #[salsa::tracked] - fn hir_function(self, db: &'db dyn salsa::Database) -> FunctionWithSourceMap<'db> { - let parent = self.parent(db); - let source = parent.source(db); - let type_params = - source.type_params().map(|tp| tp.as_str().into_symbol(db)).collect::>(); - let owner_name = source.name().expect("enum destructor owner should have a name"); - function::FunctionBuilder::new(db).build_destructor( - owner_name.as_str().into_symbol(db), - type_params, - &self.source(db), - ) - } -} diff --git a/crates/mitki-lower/src/hir/function.rs b/crates/mitki-lower/src/hir/function.rs deleted file mode 100644 index d6b3edf..0000000 --- a/crates/mitki-lower/src/hir/function.rs +++ /dev/null @@ -1,729 +0,0 @@ -use mitki_hir::hir::{ExprId, Function, NameId, ParamId, PatId, StmtId, TyId, WasmLinkage}; -use mitki_span::{IntoSymbol as _, Symbol}; -use mitki_yellow::ast::{self, HasName as _, Node as _}; -use mitki_yellow::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr}; -use rustc_hash::{FxHashMap, FxHashSet}; -use salsa::Database; - -use super::FunctionWithSourceMap; - -fn operator_precedence(op: &str) -> u8 { - match op { - "||" => 1, - "&&" => 2, - "==" | "!=" => 3, - "<" | ">" | "<=" | ">=" => 4, - "+" | "-" => 5, - "*" | "/" | "%" => 6, - _ => 0, - } -} - -#[derive(Default, PartialEq, Eq, salsa::Update)] -pub struct FunctionSourceMap { - node_map: FxHashMap, - node_map_back: FxHashMap, - pat_map: FxHashMap, - pat_map_back: FxHashMap, - type_map: FxHashMap, - type_map_back: FxHashMap, - mutable_bindings: FxHashSet, -} - -impl FunctionSourceMap { - pub fn syntax_expr(&self, syntax: &SyntaxNode) -> Option { - self.node_map.get(&SyntaxNodePtr::new(syntax)).copied() - } - - pub fn try_node_syntax(&self, node: ExprId) -> Option { - self.node_map_back.get(&node).copied() - } - - #[track_caller] - pub fn node_syntax(&self, node: ExprId) -> SyntaxNodePtr { - self.node_map_back[&node] - } - - pub fn syntax_pat(&self, syntax: &SyntaxNode) -> Option { - self.pat_map.get(&SyntaxNodePtr::new(syntax)).copied() - } - - pub fn try_pat_syntax(&self, pat: PatId) -> Option { - self.pat_map_back.get(&pat).copied() - } - - #[track_caller] - pub fn pat_syntax(&self, pat: PatId) -> SyntaxNodePtr { - self.pat_map_back[&pat] - } - - pub fn syntax_type(&self, syntax: &SyntaxNode) -> Option { - self.type_map.get(&SyntaxNodePtr::new(syntax)).copied() - } - - pub fn try_type_syntax(&self, ty: TyId) -> Option { - self.type_map_back.get(&ty).copied() - } - - #[track_caller] - pub fn type_syntax(&self, ty: TyId) -> SyntaxNodePtr { - self.type_map_back[&ty] - } - - pub fn is_mutable_binding(&self, name: NameId) -> bool { - self.mutable_bindings.contains(&name) - } -} - -pub(crate) struct FunctionBuilder<'db> { - db: &'db dyn Database, - function: Function<'db>, - source_map: FunctionSourceMap, -} - -impl<'db> FunctionBuilder<'db> { - pub(crate) fn new(db: &'db dyn Database) -> Self { - Self { db, function: Function::default(), source_map: FunctionSourceMap::default() } - } - - pub(super) fn build(mut self, node: &ast::Function<'db>) -> FunctionWithSourceMap<'db> { - let type_params: Vec<_> = - node.type_params().map(|tp| tp.as_str().into_symbol(self.db)).collect(); - let params = self.build_params(node.params()); - let ret_type = self.build_ty(node.ret_type().and_then(|ret_type| ret_type.ty())); - let body = self.build_block(node.body()); - let linkage = function_linkage(self.db, node); - let comptime = node.is_comptime(); - let unsafe_ = node.is_unsafe(); - - self.function.set_type_params(type_params); - self.function.set_params(params); - self.function.set_ret_type(ret_type); - self.function.set_body(body); - self.function.set_linkage(linkage); - self.function.set_comptime(comptime); - self.function.set_unsafe(unsafe_); - - FunctionWithSourceMap::new(self.db, self.function, self.source_map) - } - - pub(super) fn build_destructor( - mut self, - owner_name: Symbol<'db>, - type_params: Vec>, - node: &ast::DestructorDef<'db>, - ) -> FunctionWithSourceMap<'db> { - let self_ty = self.synthetic_owner_ty(owner_name, &type_params); - let params = self.build_destructor_params(node.params(), self_ty); - let ret_type = self.function.node_store_mut().alloc_type_tuple(Vec::new()).into(); - let body = self.build_block(node.body()); - - self.function.set_type_params(type_params); - self.function.set_params(params); - self.function.set_ret_type(ret_type); - self.function.set_body(body); - self.function.set_linkage(WasmLinkage::Internal); - self.function.set_comptime(false); - self.function.set_unsafe(false); - - FunctionWithSourceMap::new(self.db, self.function, self.source_map) - } - - fn build_params(&mut self, params: Option>) -> Vec { - let Some(params) = params else { - return Vec::new(); - }; - - params - .iter() - .map(|param| { - let pattern = self.build_pattern(param.pattern()); - let ty = self.build_ty(param.ty()); - if param.is_mutable() { - for name in self.function.node_store().pattern_binding_names(pattern) { - self.source_map.mutable_bindings.insert(name); - } - } - self.function.node_store_mut().alloc_param(pattern, ty) - }) - .collect() - } - - fn build_destructor_params( - &mut self, - params: Option>, - self_ty: TyId, - ) -> Vec { - let Some(params) = params else { - return Vec::new(); - }; - - params - .iter() - .enumerate() - .map(|(index, param)| { - let pattern = self.build_pattern(param.pattern()); - let ty = if index == 0 { self_ty } else { self.build_ty(param.ty()) }; - if param.is_mutable() { - for name in self.function.node_store().pattern_binding_names(pattern) { - self.source_map.mutable_bindings.insert(name); - } - } - self.function.node_store_mut().alloc_param(pattern, ty) - }) - .collect() - } - - fn synthetic_owner_ty(&mut self, owner_name: Symbol<'db>, type_params: &[Symbol<'db>]) -> TyId { - let path: TyId = self.function.node_store_mut().alloc_type_ref(owner_name).into(); - if type_params.is_empty() { - return path; - } - - let args = type_params - .iter() - .map(|¶m| self.function.node_store_mut().alloc_type_ref(param).into()) - .collect::>(); - let args = self.function.node_store_mut().alloc_type_tuple(args).into(); - self.function.node_store_mut().alloc_type_apply(path, args).into() - } - - fn build_block(&mut self, block: Option>) -> ExprId { - let Some(block) = block else { - return ExprId::ZERO; - }; - - let mut stmts = Vec::new(); - let mut tail = ExprId::ZERO; - let children = block.syntax().children().collect::>(); - let last_index = children.len().saturating_sub(1); - - for (index, child) in children.into_iter().enumerate() { - if let Some(stmt) = ast::Stmt::cast(child) { - stmts.push(self.build_stmt(&stmt)); - continue; - } - let Some(expr) = ast::Expr::cast(child) else { - continue; - }; - if index == last_index { - tail = self.build_expr(Some(expr)); - } else { - stmts.push(self.build_expr(Some(expr)).into()); - } - } - - let node = self.function.node_store_mut().alloc_block(stmts, tail); - let expr = node.into(); - self.alloc_ptr(expr, block.syntax()); - expr - } - - fn build_stmt(&mut self, stmt: &ast::Stmt<'db>) -> StmtId { - match &stmt { - ast::Stmt::Val(val) => { - let pattern = self.build_pattern(val.pattern()); - let ty = self.build_ty(val.ty()); - let initializer = - val.expr().map_or(ExprId::ZERO, |expr| self.build_expr(Some(expr))); - if val.is_mutable() { - for name in self.function.node_store().pattern_binding_names(pattern) { - self.source_map.mutable_bindings.insert(name); - } - } - self.function.node_store_mut().alloc_local_var(pattern, ty, initializer).into() - } - ast::Stmt::Assign(assign_stmt) => { - let target = self.build_expr(assign_stmt.target()); - let value = self.build_expr(assign_stmt.expr()); - self.function.node_store_mut().alloc_assign_stmt(target, value).into() - } - ast::Stmt::Return(return_stmt) => { - let value = - return_stmt.expr().map_or(ExprId::ZERO, |expr| self.build_expr(Some(expr))); - self.function.node_store_mut().alloc_return_stmt(value, ExprId::ZERO).into() - } - ast::Stmt::Expr(stmt) => self.build_expr(stmt.expr()).into(), - } - } - - fn alloc_ptr(&mut self, node: ExprId, syntax: &SyntaxNode) { - let ptr = SyntaxNodePtr::new(syntax); - self.source_map.node_map.insert(ptr, node); - self.source_map.node_map_back.insert(node, ptr); - } - - fn alloc_pat_ptr(&mut self, pat: PatId, syntax: &SyntaxNode) { - let ptr = SyntaxNodePtr::new(syntax); - self.source_map.pat_map.insert(ptr, pat); - self.source_map.pat_map_back.insert(pat, ptr); - } - - fn alloc_type_ptr(&mut self, ty: TyId, syntax: &SyntaxNode) { - let ptr = SyntaxNodePtr::new(syntax); - self.source_map.type_map.insert(ptr, ty); - self.source_map.type_map_back.insert(ty, ptr); - } - - fn build_expr(&mut self, expr: Option>) -> ExprId { - let Some(expr) = expr else { - return self.function.node_store_mut().alloc_error().into(); - }; - - let node: ExprId = match &expr { - ast::Expr::Path(path) => { - let path = syntax_non_trivia_text(path.syntax()).into_symbol(self.db); - self.function.node_store_mut().alloc_name(path).into() - } - ast::Expr::Field(field_expr) => { - let expr = - field_expr.expr().map_or(ExprId::ZERO, |base| self.build_expr(Some(base))); - let field_name = field_expr.name(); - let field_name_sym = - field_name.as_ref().map_or("", |name| name.as_str()).into_symbol(self.db); - let field_name_id: ExprId = - self.function.node_store_mut().alloc_name(field_name_sym).into(); - - match field_name { - Some(name) => self.alloc_ptr(field_name_id, name.syntax()), - None => self.alloc_ptr(field_name_id, field_expr.syntax()), - } - - self.function.node_store_mut().alloc_field(expr, field_name_id).into() - } - ast::Expr::Literal(literal) => self.build_literal(literal), - ast::Expr::Paren(paren) => self.build_expr(paren.expr()), - ast::Expr::BinOpSeq(seq) => self.build_bin_op_seq(seq), - ast::Expr::Postfix(postfix) => { - let expr = self.build_expr(postfix.expr()); - let op_sym = postfix.op().map(|op| op.into_symbol(self.db)); - let op = op_sym.map_or(ExprId::ZERO, |sym| { - self.function.node_store_mut().alloc_name(sym).into() - }); - self.function.node_store_mut().alloc_postfix(expr, op).into() - } - ast::Expr::Prefix(prefix) => { - let op_sym = prefix.op().map(|op| op.into_symbol(self.db)); - let op = op_sym.map_or(ExprId::ZERO, |sym| { - self.function.node_store_mut().alloc_name(sym).into() - }); - let expr = self.build_expr(prefix.expr()); - self.function.node_store_mut().alloc_prefix(op, expr).into() - } - ast::Expr::Loop(loop_expr) => { - let body = self.build_block(loop_expr.body()); - self.function.node_store_mut().alloc_loop_expr(body, ExprId::ZERO).into() - } - ast::Expr::Break(_) => self.function.node_store_mut().alloc_break_expr().into(), - ast::Expr::Continue(_) => self.function.node_store_mut().alloc_continue_expr().into(), - ast::Expr::If(if_expr) => self.build_if_expr(if_expr), - ast::Expr::Match(match_expr) => { - let scrutinee = self.build_expr(match_expr.scrutinee()); - let arms = match_expr - .arms() - .map(|arm| { - let pattern = self.build_pattern(arm.pattern()); - let expr = self.build_expr(arm.expr()); - self.function.node_store_mut().alloc_match_arm(pattern, expr) - }) - .collect::>(); - self.function.node_store_mut().alloc_match(scrutinee, arms).into() - } - ast::Expr::Unsafe(unsafe_expr) => { - let body = self.build_block(unsafe_expr.body()); - self.function.node_store_mut().alloc_unsafe_block(body, ExprId::ZERO).into() - } - ast::Expr::Closure(closure) => { - let params = self.build_params(closure.params()); - let body = self.build_block(Some(closure.body())); - self.function.node_store_mut().alloc_closure(params, body).into() - } - ast::Expr::Call(call_expr) => { - let callee = self.build_expr(call_expr.callee()); - let arg_list = call_expr.arg_list().unwrap(); - let args = - arg_list.args().map(|arg| self.build_expr(arg.into())).collect::>(); - - self.function.node_store_mut().alloc_call(callee, args).into() - } - ast::Expr::Tuple(tuple_expr) => { - let exprs = - tuple_expr.exprs().map(|expr| self.build_expr(expr.into())).collect::>(); - self.function.node_store_mut().alloc_tuple(exprs).into() - } - ast::Expr::Array(array_expr) => { - let exprs = - array_expr.exprs().map(|expr| self.build_expr(Some(expr))).collect::>(); - let is_repeat = array_expr - .syntax() - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .any(|token| !token.is_trivia() && token.kind() == SyntaxKind::SEMICOLON); - if is_repeat && exprs.len() == 2 { - self.function.node_store_mut().alloc_array_repeat(exprs[0], exprs[1]).into() - } else { - self.function.node_store_mut().alloc_array(exprs).into() - } - } - ast::Expr::Struct(struct_expr) => { - let mut items: Vec = Vec::new(); - - if let Some(path) = struct_expr.path() { - let name_sym = path.name().map_or("", |n| n.as_str()).into_symbol(self.db); - let name_id: ExprId = - self.function.node_store_mut().alloc_name(name_sym).into(); - items.push(name_id); - } - - if let Some(field_list) = struct_expr.field_list() { - for field in field_list.fields() { - let field_name_sym = - field.name().map_or("", |n| n.as_str()).into_symbol(self.db); - let field_name: ExprId = - self.function.node_store_mut().alloc_name(field_name_sym).into(); - let field_expr = if let Some(expr) = field.expr() { - self.build_expr(Some(expr)) - } else { - self.function.node_store_mut().alloc_name(field_name_sym).into() - }; - items.push(field_name); - items.push(field_expr); - } - } - - self.function.node_store_mut().alloc_struct_expr(items).into() - } - }; - - self.alloc_ptr(node, expr.syntax()); - - node - } - - fn build_if_expr(&mut self, if_expr: &ast::IfExpr<'db>) -> ExprId { - let cond = self.build_expr(if_expr.condition()); - let then_branch = self.build_block(if_expr.then_block()); - let else_branch = if let Some(else_if) = if_expr.else_if() { - self.build_if_expr(&else_if) - } else { - self.build_block(if_expr.else_block()) - }; - self.function.node_store_mut().alloc_if(cond, then_branch, else_branch).into() - } - - fn build_pattern(&mut self, pattern: Option>) -> PatId { - let Some(pattern) = pattern else { - return PatId::ZERO; - }; - - let pat = match &pattern { - ast::Pattern::Binding(binding) => { - let name_sym = binding.name().map_or("", |name| name.as_str()).into_symbol(self.db); - let pat = self.function.node_store_mut().alloc_pat_binding(name_sym, PatId::ZERO); - let (name, _) = self.function.node_store_mut().pat_binding(pat); - if let Some(name_syntax) = binding.name() { - self.alloc_ptr(name.into(), name_syntax.syntax()); - } - pat.into() - } - ast::Pattern::Wildcard(_) => self.function.node_store_mut().alloc_pat_wildcard().into(), - ast::Pattern::Literal(literal) => self.build_literal_pattern(literal), - ast::Pattern::Typed(typed) => { - let inner = self.build_pattern(typed.pattern()); - let ty = self.build_ty(typed.ty()); - self.function.node_store_mut().alloc_pat_typed(inner, ty).into() - } - ast::Pattern::Paren(paren) => { - let inner = self.build_pattern(paren.pattern()); - self.function.node_store_mut().alloc_pat_paren(inner, PatId::ZERO).into() - } - ast::Pattern::Tuple(tuple) => { - let items: Vec<_> = - tuple.patterns().map(|item| self.build_pattern(Some(item))).collect(); - self.function.node_store_mut().alloc_pat_tuple(items).into() - } - ast::Pattern::Variant(variant) => { - let path = variant - .path() - .as_ref() - .map_or(ExprId::ZERO, |path| self.build_field_pattern_path(path)); - let args = variant - .patterns() - .map(|item| self.build_pattern(Some(item))) - .collect::>(); - self.function.node_store_mut().alloc_pat_variant(path, args).into() - } - ast::Pattern::Struct(struct_pattern) => { - let path = struct_pattern - .path() - .as_ref() - .map_or(ExprId::ZERO, |path| self.build_path_pattern_path(path)); - let fields = struct_pattern - .fields() - .map(|field| { - let name_sym = - field.name().map_or("", |name| name.as_str()).into_symbol(self.db); - let pat = self.build_pattern(field.pattern()); - let field_id = - self.function.node_store_mut().alloc_pat_struct_field(name_sym, pat); - let (name, _) = self.function.node_store_mut().pat_struct_field(field_id); - if let Some(name_syntax) = field.name() { - self.alloc_ptr(name.into(), name_syntax.syntax()); - } - field_id - }) - .collect::>(); - self.function.node_store_mut().alloc_pat_struct(path, fields).into() - } - }; - - self.alloc_pat_ptr(pat, pattern.syntax()); - pat - } - - fn build_literal_pattern(&mut self, literal: &ast::LiteralPattern<'db>) -> PatId { - match literal.kind() { - ast::LiteralKind::Bool(true) => self.function.node_store_mut().alloc_pat_true().into(), - ast::LiteralKind::Bool(false) => { - self.function.node_store_mut().alloc_pat_false().into() - } - ast::LiteralKind::Int(token) => self - .function - .node_store_mut() - .alloc_pat_int(Some(token.text_trimmed().into_symbol(self.db))) - .into(), - ast::LiteralKind::Float(token) => self - .function - .node_store_mut() - .alloc_pat_float(Some(token.text_trimmed().into_symbol(self.db))) - .into(), - ast::LiteralKind::String(token) => self - .function - .node_store_mut() - .alloc_pat_string(Some(token.text_trimmed().into_symbol(self.db))) - .into(), - ast::LiteralKind::Char(token) => self - .function - .node_store_mut() - .alloc_pat_char(Some(token.text_trimmed().into_symbol(self.db))) - .into(), - } - } - - fn build_path_pattern_path(&mut self, path: &ast::PathPattern<'db>) -> ExprId { - let name_sym = path.name().map_or("", |name| name.as_str()).into_symbol(self.db); - let expr: ExprId = self.function.node_store_mut().alloc_name(name_sym).into(); - if let Some(name) = path.name() { - self.alloc_ptr(expr, name.syntax()); - } - self.alloc_ptr(expr, path.syntax()); - expr - } - - fn build_field_pattern_path(&mut self, path: &ast::FieldPattern<'db>) -> ExprId { - let base = - path.base().as_ref().map_or(ExprId::ZERO, |base| self.build_path_pattern_path(base)); - let field_name = path.name(); - let field_name_sym = - field_name.as_ref().map_or("", |name| name.as_str()).into_symbol(self.db); - let field_name_id: ExprId = - self.function.node_store_mut().alloc_name(field_name_sym).into(); - - match field_name { - Some(name) => self.alloc_ptr(field_name_id, name.syntax()), - None => self.alloc_ptr(field_name_id, path.syntax()), - } - - let expr: ExprId = self.function.node_store_mut().alloc_field(base, field_name_id).into(); - self.alloc_ptr(expr, path.syntax()); - expr - } - - fn build_bin_op_seq(&mut self, seq: &ast::BinOpSeq<'db>) -> ExprId { - let mut operands: Vec = Vec::new(); - let mut operators: Vec<&'db str> = Vec::new(); - - for element in seq.elements() { - match element { - SyntaxElement::Node(node) => { - let expr = ast::Expr::cast(node); - operands.push(self.build_expr(expr)); - } - SyntaxElement::Token(token) => { - if !token.is_trivia() { - operators.push(token.text_trimmed()); - } - } - } - } - - self.pratt_parse(&operands, &operators, 0, operands.len()) - } - - fn pratt_parse( - &mut self, - operands: &[ExprId], - operators: &[&'db str], - start: usize, - end: usize, - ) -> ExprId { - if end - start == 1 { - return operands[start]; - } - - // Find the lowest-precedence operator (rightmost among ties for left-assoc). - let mut min_prec = u8::MAX; - let mut split = start; - for (i, &op) in operators.iter().enumerate().take(end - 1).skip(start) { - let prec = operator_precedence(op); - if prec <= min_prec { - min_prec = prec; - split = i; - } - } - - let lhs = self.pratt_parse(operands, operators, start, split + 1); - let op_sym = operators[split].into_symbol(self.db); - let op: ExprId = self.function.node_store_mut().alloc_name(op_sym).into(); - let rhs = self.pratt_parse(operands, operators, split + 1, end); - self.function.node_store_mut().alloc_binary(lhs, op, rhs).into() - } - - fn build_literal(&mut self, literal: &ast::Literal<'db>) -> ExprId { - let db = self.db; - match literal.kind() { - ast::LiteralKind::Bool(true) => self.function.node_store_mut().alloc_true().into(), - ast::LiteralKind::Bool(false) => self.function.node_store_mut().alloc_false().into(), - ast::LiteralKind::Int(token) => self - .function - .node_store_mut() - .alloc_int(Some(token.text_trimmed().into_symbol(db))) - .into(), - ast::LiteralKind::Float(token) => self - .function - .node_store_mut() - .alloc_float(Some(token.text_trimmed().into_symbol(db))) - .into(), - ast::LiteralKind::String(token) => self - .function - .node_store_mut() - .alloc_string(Some(token.text_trimmed().into_symbol(db))) - .into(), - ast::LiteralKind::Char(token) => self - .function - .node_store_mut() - .alloc_char(Some(token.text_trimmed().into_symbol(db))) - .into(), - } - } - - fn build_ty(&mut self, ty: Option>) -> TyId { - ty.map_or(TyId::ZERO, |ty| { - let syntax = ty.syntax(); - let ty_id = match &ty { - ast::Type::Path(path) => { - let path_ref: TyId = self - .function - .node_store_mut() - .alloc_type_ref(path.path_text().into_symbol(self.db)) - .into(); - let type_args = path - .type_args() - .into_iter() - .map(|ty| self.build_ty(Some(ty))) - .collect::>(); - if type_args.is_empty() { - path_ref - } else { - let args = - self.function.node_store_mut().alloc_type_tuple(type_args).into(); - self.function.node_store_mut().alloc_type_apply(path_ref, args).into() - } - } - ast::Type::Array(array_type) => { - let item = self.build_ty(array_type.item()); - self.function.node_store_mut().alloc_type_array(item, TyId::ZERO).into() - } - ast::Type::Tuple(tuple_type) => { - let items: Vec = - tuple_type.types().map(|t| self.build_ty(Some(t))).collect(); - self.function.node_store_mut().alloc_type_tuple(items).into() - } - ast::Type::Function(function_type) => { - let inputs = self.build_ty(function_type.inputs().map(ast::Type::Tuple)); - let output = self.build_ty(function_type.output()); - self.function.node_store_mut().alloc_type_function(inputs, output).into() - } - ast::Type::Union(union_type) => { - let lhs = self.build_ty(union_type.lhs()); - let rhs = self.build_ty(union_type.rhs()); - self.function.node_store_mut().alloc_type_union(lhs, rhs).into() - } - ast::Type::Inter(inter_type) => { - let lhs = self.build_ty(inter_type.lhs()); - let rhs = self.build_ty(inter_type.rhs()); - self.function.node_store_mut().alloc_type_inter(lhs, rhs).into() - } - ast::Type::Record(record_type) => { - let fields: Vec = record_type - .fields() - .filter_map(|field| { - let name = field.name()?; - let ty = self.build_ty(field.ty()); - let name = name.as_str().into_symbol(self.db); - Some(self.function.node_store_mut().alloc_type_field(name, ty).into()) - }) - .collect(); - self.function.node_store_mut().alloc_type_record(fields).into() - } - ast::Type::Pointer(pointer_type) => { - let item = self.build_ty(pointer_type.pointee()); - if pointer_type.is_mut() { - self.function.node_store_mut().alloc_type_ptr_mut(item, TyId::ZERO).into() - } else { - self.function.node_store_mut().alloc_type_ptr_const(item, TyId::ZERO).into() - } - } - }; - self.alloc_type_ptr(ty_id, syntax); - ty_id - }) - } -} - -fn syntax_non_trivia_text(syntax: &SyntaxNode<'_>) -> String { - let mut text = String::new(); - collect_non_trivia_text(syntax, &mut text); - text -} - -fn collect_non_trivia_text(syntax: &SyntaxNode<'_>, text: &mut String) { - for child in syntax.children_with_tokens() { - match child { - SyntaxElement::Token(token) if !token.is_trivia() => { - text.push_str(token.text_trimmed()) - } - SyntaxElement::Node(node) => collect_non_trivia_text(&node, text), - SyntaxElement::Token(_) => {} - } - } -} - -fn function_linkage<'db>(db: &'db dyn Database, node: &ast::Function<'db>) -> WasmLinkage<'db> { - if let Some(module) = node.import_module() { - if node.is_unsafe() { - return WasmLinkage::RawImport { module: module.into_symbol(db) }; - } - return WasmLinkage::Import { module: module.into_symbol(db) }; - } - - if node.is_exported() { - return WasmLinkage::Export; - } - - if node.name().is_some_and(|name| name.as_str() == "main") { - return WasmLinkage::ImplicitMainExport; - } - - WasmLinkage::Internal -} diff --git a/crates/mitki-lower/src/item.rs b/crates/mitki-lower/src/item.rs deleted file mode 100644 index 63f991e..0000000 --- a/crates/mitki-lower/src/item.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod package; -pub mod scope; -pub mod stdlib; -pub mod tree; diff --git a/crates/mitki-lower/src/item/package.rs b/crates/mitki-lower/src/item/package.rs deleted file mode 100644 index 1db49bb..0000000 --- a/crates/mitki-lower/src/item/package.rs +++ /dev/null @@ -1,233 +0,0 @@ -use mitki_inputs::{File, ModuleId, PackageId}; -use mitki_parse::FileParse as _; -use mitki_yellow::ast::HasName as _; -use salsa::Database; - -use super::stdlib::stdlib_package; - -pub trait HasPackage<'db> { - fn package(self, db: &'db dyn Database) -> PackageId<'db>; -} - -#[salsa::tracked] -impl<'db> HasPackage<'db> for File { - #[salsa::tracked] - fn package(self, db: &'db dyn Database) -> PackageId<'db> { - PackageId::new(db, self) - } -} - -#[derive(Debug, Default, PartialEq, Eq, salsa::Update)] -pub struct PackageGraph<'db> { - root: Option>, - modules: Vec>, - entries: Vec>, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -struct ModuleGraphEntry<'db> { - module: ModuleId<'db>, - parent: Option>, - name: String, - crate_path: String, - public: bool, - children: Vec>, -} - -impl<'db> PackageGraph<'db> { - fn entry(&self, module: ModuleId<'db>) -> Option<&ModuleGraphEntry<'db>> { - self.entries.iter().find(|entry| entry.module == module) - } -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn package_graph<'db>(db: &'db dyn Database, package: PackageId<'db>) -> PackageGraph<'db> { - let root_file = package.root_file(db); - let root = ModuleId::new(db, package, root_file); - let mut graph = PackageGraph { root: Some(root), ..PackageGraph::default() }; - let root_name = package_root_name(db, package); - ModuleLoader { db, package, graph: &mut graph }.load_module_graph( - root, - None, - root_name.as_str(), - ModuleFlags { is_root: true, is_public: true }, - ); - graph -} - -#[salsa::tracked] -pub fn package_root_name<'db>(db: &'db dyn Database, package: PackageId<'db>) -> String { - if package == stdlib_package(db) { "std".to_owned() } else { "crate".to_owned() } -} - -#[salsa::tracked] -pub fn root_module<'db>(db: &'db dyn Database, package: PackageId<'db>) -> ModuleId<'db> { - package_graph(db, package).root.expect("package graph should always have a root module") -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn package_modules<'db>(db: &'db dyn Database, package: PackageId<'db>) -> Vec> { - package_graph(db, package).modules.clone() -} - -#[salsa::tracked(returns(ref), no_eq)] -pub fn module_children<'db>(db: &'db dyn Database, module: ModuleId<'db>) -> Vec> { - let graph = package_graph(db, module.package(db)); - graph.entry(module).map(|entry| entry.children.clone()).unwrap_or_default() -} - -#[salsa::tracked] -pub fn module_name<'db>(db: &'db dyn Database, module: ModuleId<'db>) -> String { - let graph = package_graph(db, module.package(db)); - graph.entry(module).map(|entry| entry.name.clone()).unwrap_or_default() -} - -#[salsa::tracked] -pub fn module_crate_path<'db>(db: &'db dyn Database, module: ModuleId<'db>) -> String { - let graph = package_graph(db, module.package(db)); - graph.entry(module).map_or_else(|| "crate".to_owned(), |entry| entry.crate_path.clone()) -} - -#[salsa::tracked] -#[allow(clippy::needless_pass_by_value)] -pub fn child_module_named<'db>( - db: &'db dyn Database, - module: ModuleId<'db>, - name: String, -) -> Option> { - let graph = package_graph(db, module.package(db)); - graph.entry(module).and_then(|entry| { - entry.children.iter().copied().find(|child| module_name(db, *child) == name) - }) -} - -#[salsa::tracked] -pub fn module_is_public<'db>(db: &'db dyn Database, module: ModuleId<'db>) -> bool { - let graph = package_graph(db, module.package(db)); - graph.entry(module).is_some_and(|entry| entry.public) -} - -#[salsa::tracked] -#[allow(clippy::needless_pass_by_value)] -pub fn exported_child_module_named<'db>( - db: &'db dyn Database, - module: ModuleId<'db>, - name: String, -) -> Option> { - let graph = package_graph(db, module.package(db)); - graph.entry(module).and_then(|entry| { - entry - .children - .iter() - .copied() - .find(|child| module_name(db, *child) == name && module_is_public(db, *child)) - }) -} - -#[derive(Clone, Copy)] -struct ModuleFlags { - is_root: bool, - is_public: bool, -} - -struct ModuleLoader<'a, 'db> { - db: &'db dyn Database, - package: PackageId<'db>, - graph: &'a mut PackageGraph<'db>, -} - -impl<'db> ModuleLoader<'_, 'db> { - fn load_module_graph( - &mut self, - module: ModuleId<'db>, - parent: Option>, - crate_path: &str, - flags: ModuleFlags, - ) { - if self.graph.modules.contains(&module) { - return; - } - - let db = self.db; - let file = module.file(db); - self.graph.modules.push(module); - self.graph.entries.push(ModuleGraphEntry { - module, - parent, - name: if flags.is_root { - crate_path.to_owned() - } else if file.path(db).file_name() == Some("mod.mitki") { - file.path(db) - .parent() - .and_then(|parent| parent.file_name()) - .unwrap_or_default() - .to_owned() - } else { - file.path(db).file_stem().unwrap_or_default().to_owned() - }, - crate_path: crate_path.to_owned(), - public: flags.is_public, - children: Vec::new(), - }); - - let declared_children = file - .parse(db) - .tree() - .items() - .filter_map(|item| match item { - mitki_yellow::ast::Item::Module(module_item) => module_item - .name() - .map(|name| (name.as_str().to_owned(), module_item.is_public())), - _ => None, - }) - .collect::>(); - - let mut child_ids = Vec::new(); - for (child_name, child_public) in declared_children { - let Some(child_file) = load_child_file(db, file, &child_name, flags.is_root) else { - continue; - }; - let child_module = ModuleId::new(db, self.package, child_file); - let child_path = format!("{crate_path}::{child_name}"); - self.load_module_graph( - child_module, - Some(module), - child_path.as_str(), - ModuleFlags { is_root: false, is_public: child_public }, - ); - child_ids.push(child_module); - } - - if let Some(entry) = self.graph.entries.iter_mut().find(|entry| entry.module == module) { - entry.children = child_ids; - } - } -} - -fn load_child_file( - db: &dyn Database, - current_file: File, - child_name: &str, - is_root: bool, -) -> Option { - let path = current_file.path(db); - let parent = path.parent()?; - let module_dir = if is_root || path.file_name() == Some("mod.mitki") { - parent.to_owned() - } else { - parent.join(path.file_stem()?) - }; - - let direct = module_dir.join(format!("{child_name}.mitki")); - if let Some(file) = read_file(db, &direct) { - return Some(file); - } - - let nested = module_dir.join(child_name).join("mod.mitki"); - read_file(db, &nested) -} - -fn read_file(db: &dyn Database, path: &camino::Utf8Path) -> Option { - let text = std::fs::read_to_string(path).ok()?; - Some(File::new(db, path.to_owned(), text)) -} diff --git a/crates/mitki-lower/src/item/scope.rs b/crates/mitki-lower/src/item/scope.rs deleted file mode 100644 index 2702110..0000000 --- a/crates/mitki-lower/src/item/scope.rs +++ /dev/null @@ -1,1415 +0,0 @@ -use mitki_hir::hir::{ExprId, NodeStore, ParamId, PatId, TyId}; -use mitki_hir::ty::{EnumTy, ExactInt, StructTy, Ty, TyKind}; -use mitki_inputs::{File, ModuleId, PackageId}; -use mitki_span::{IntoSymbol as _, Symbol}; -use mitki_yellow::SyntaxNodePtr; -use mitki_yellow::ast::{self, HasName as _, Node as _}; -use salsa::Database; - -use super::package::{HasPackage as _, child_module_named, package_modules, root_module}; -use super::stdlib::stdlib_package; -use super::tree::{BoundaryInstance, Item, ItemTree, UseData}; -use crate::ast_map::HasAstMap as _; -use crate::item::tree::{Enum, Function, HasItemTree as _, Struct}; - -type FxIndexMap = - indexmap::IndexMap>; - -pub trait HasItemDecls<'db> { - fn item_decls(self, db: &'db dyn Database) -> &'db ItemDecls<'db>; -} - -pub trait HasVisibleItems<'db> { - fn visible_items(self, db: &'db dyn Database) -> &'db VisibleItems<'db>; -} - -pub trait HasPackageDecls<'db> { - fn package_decls(self, db: &'db dyn Database) -> &'db ItemDecls<'db>; -} - -#[salsa::tracked] -impl<'db> HasItemDecls<'db> for File { - #[salsa::tracked(returns(ref))] - fn item_decls(self, db: &'db dyn Database) -> ItemDecls<'db> { - ItemCollector::new(db, self).build_decls() - } -} - -#[salsa::tracked] -impl<'db> HasVisibleItems<'db> for File { - #[salsa::tracked(returns(ref))] - fn visible_items(self, db: &'db dyn Database) -> VisibleItems<'db> { - ItemCollector::new(db, self).build_visible_items() - } -} - -#[salsa::tracked] -impl<'db> HasItemDecls<'db> for ModuleId<'db> { - #[salsa::tracked(returns(ref))] - fn item_decls(self, db: &'db dyn Database) -> ItemDecls<'db> { - ItemCollector::for_module(db, self).build_decls() - } -} - -#[salsa::tracked] -impl<'db> HasVisibleItems<'db> for ModuleId<'db> { - #[salsa::tracked(returns(ref))] - fn visible_items(self, db: &'db dyn Database) -> VisibleItems<'db> { - ItemCollector::for_module(db, self).build_visible_items() - } -} - -#[salsa::tracked] -impl<'db> HasPackageDecls<'db> for PackageId<'db> { - #[salsa::tracked(returns(ref))] - fn package_decls(self, db: &'db dyn Database) -> ItemDecls<'db> { - let mut decls = ItemDecls::default(); - for &module in package_modules(db, self) { - let module_decls = module.item_decls(db); - decls.declarations.extend_from_slice(module_decls.declarations()); - for &variant in module_decls.enum_variants() { - decls.enum_variants.push(variant); - decls.enum_variants_by_name.entry(variant.name(db)).or_default().push(variant); - } - } - decls - } -} - -#[derive(salsa::Update, Debug, PartialEq, Eq, Clone, Copy)] -pub enum Declaration<'db> { - Function(FunctionLocation<'db>), - BoundaryInstance(BoundaryInstanceLocation<'db>), - Struct(StructLocation<'db>), - Enum(EnumLocation<'db>), -} - -#[derive(salsa::Update, Debug, PartialEq, Eq, Clone, Copy)] -pub enum TypeDeclaration<'db> { - Struct(StructLocation<'db>), - Enum(EnumLocation<'db>), -} - -#[salsa::tracked(debug)] -pub struct FunctionLocation<'db> { - pub module: ModuleId<'db>, - pub index: Function<'db>, -} - -#[derive(salsa::Update, Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum BoundaryInstanceKind { - Import, - Export, -} - -#[salsa::tracked(debug)] -pub struct BoundaryInstanceLocation<'db> { - pub module: ModuleId<'db>, - pub index: BoundaryInstance<'db>, -} - -#[salsa::tracked(debug)] -pub struct StructLocation<'db> { - pub module: ModuleId<'db>, - pub index: Struct<'db>, -} - -#[salsa::tracked(debug)] -pub struct EnumLocation<'db> { - pub module: ModuleId<'db>, - pub index: Enum<'db>, -} - -#[salsa::tracked(debug)] -pub struct StructDestructorLocation<'db> { - pub parent: StructLocation<'db>, -} - -#[salsa::tracked(debug)] -pub struct EnumDestructorLocation<'db> { - pub parent: EnumLocation<'db>, -} - -#[salsa::tracked(debug)] -pub struct EnumVariantLocation<'db> { - pub parent: EnumLocation<'db>, - pub variant_index: u32, -} - -#[salsa::tracked] -impl<'db> FunctionLocation<'db> { - pub fn file(self, db: &'db dyn Database) -> File { - self.module(db).file(db) - } - - #[salsa::tracked(returns(ref))] - pub fn signature(self, db: &'db dyn Database) -> Signature<'db> { - let mut node_store = NodeStore::default(); - let func = self.source(db); - - // Lower type parameters - let type_params: Vec> = - func.type_params().map(|tp| tp.as_str().into_symbol(db)).collect(); - - // Lower parameters - let params = func.params().map_or_else(Vec::new, |param_list| { - param_list - .iter() - .map(|param| { - let pattern = param - .pattern() - .map(|pattern| lower_pattern_ref(db, &mut node_store, pattern)); - let type_id = param - .ty() - .and_then(|ty| lower_type_ref(db, &mut node_store, ty)) - .unwrap_or(TyId::ZERO); - node_store.alloc_param(pattern.unwrap_or(PatId::ZERO), type_id) - }) - .collect() - }); - - // Lower return type - let ret_type = func - .ret_type() - .and_then(|r| r.ty()) - .and_then(|ty| lower_type_ref(db, &mut node_store, ty)) - .unwrap_or(TyId::ZERO); - - Signature::new(db, type_params, params, ret_type, node_store) - } -} - -#[salsa::tracked] -impl<'db> BoundaryInstanceLocation<'db> { - pub fn file(self, db: &'db dyn Database) -> File { - self.module(db).file(db) - } - - #[salsa::tracked(returns(ref))] - pub fn type_args(self, db: &'db dyn Database) -> Vec> { - let visible_items = self.module(db).visible_items(db); - self.source(db) - .type_args() - .into_iter() - .map(|ty| resolve_ast_type_in_scope(db, self.module(db), visible_items, ty)) - .collect() - } -} - -impl<'db> BoundaryInstanceLocation<'db> { - pub fn source(self, db: &'db dyn Database) -> ast::InstanceItem<'db> { - let file = self.file(db); - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - let index = self.index(db); - - let item = item_tree[index].id; - let syntax = source_syntax(db, file, ast_map.find_node(item)); - ast::InstanceItem::cast(syntax).unwrap() - } - - pub fn kind(self, db: &'db dyn Database) -> BoundaryInstanceKind { - let source = self.source(db); - if source.is_imported() { - BoundaryInstanceKind::Import - } else { - BoundaryInstanceKind::Export - } - } - - pub fn origin(self, db: &'db dyn Database) -> Option> { - let source = self.source(db); - let name = source.name()?.as_str().into_symbol(db); - self.module(db).visible_items(db).get_value(&name) - } -} - -fn lower_type_ref<'db>( - db: &'db dyn Database, - node_store: &mut NodeStore<'db>, - ty: ast::Type, -) -> Option { - match ty { - ast::Type::Path(path) => { - let path_ref: TyId = node_store.alloc_type_ref(path.path_text().into_symbol(db)).into(); - let type_args = path - .type_args() - .into_iter() - .filter_map(|arg| lower_type_ref(db, node_store, arg)) - .collect::>(); - if type_args.is_empty() { - Some(path_ref) - } else { - let args = node_store.alloc_type_tuple(type_args).into(); - Some(node_store.alloc_type_apply(path_ref, args).into()) - } - } - ast::Type::Array(array_type) => { - let item = array_type - .item() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - Some(node_store.alloc_type_array(item, TyId::ZERO).into()) - } - ast::Type::Tuple(tuple_type) => { - let items: Vec = - tuple_type.types().filter_map(|t| lower_type_ref(db, node_store, t)).collect(); - Some(node_store.alloc_type_tuple(items).into()) - } - ast::Type::Function(function_type) => { - let inputs = function_type - .inputs() - .map(ast::Type::Tuple) - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - let output = function_type - .output() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - Some(node_store.alloc_type_function(inputs, output).into()) - } - ast::Type::Union(union_type) => { - let lhs = union_type - .lhs() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - let rhs = union_type - .rhs() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - Some(node_store.alloc_type_union(lhs, rhs).into()) - } - ast::Type::Inter(inter_type) => { - let lhs = inter_type - .lhs() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - let rhs = inter_type - .rhs() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - Some(node_store.alloc_type_inter(lhs, rhs).into()) - } - ast::Type::Record(record_type) => { - let fields: Vec = record_type - .fields() - .filter_map(|field| { - let name = field.name()?; - let ty = lower_type_ref(db, node_store, field.ty()?).unwrap_or(TyId::ZERO); - let name = name.as_str().into_symbol(db); - Some(node_store.alloc_type_field(name, ty).into()) - }) - .collect(); - Some(node_store.alloc_type_record(fields).into()) - } - ast::Type::Pointer(pointer_type) => { - let item = pointer_type - .pointee() - .and_then(|ty| lower_type_ref(db, node_store, ty)) - .unwrap_or(TyId::ZERO); - if pointer_type.is_mut() { - Some(node_store.alloc_type_ptr_mut(item, TyId::ZERO).into()) - } else { - Some(node_store.alloc_type_ptr_const(item, TyId::ZERO).into()) - } - } - } -} - -fn lower_pattern_ref<'db>( - db: &'db dyn Database, - node_store: &mut NodeStore<'db>, - pattern: ast::Pattern<'db>, -) -> PatId { - match pattern { - ast::Pattern::Binding(binding) => { - let name = binding.name().map_or("", |name| name.as_str()).into_symbol(db); - node_store.alloc_pat_binding(name, PatId::ZERO).into() - } - ast::Pattern::Wildcard(_) => node_store.alloc_pat_wildcard().into(), - ast::Pattern::Literal(literal) => match literal.kind() { - ast::LiteralKind::Bool(true) => node_store.alloc_pat_true().into(), - ast::LiteralKind::Bool(false) => node_store.alloc_pat_false().into(), - ast::LiteralKind::Int(token) => { - node_store.alloc_pat_int(Some(token.text_trimmed().into_symbol(db))).into() - } - ast::LiteralKind::Float(token) => { - node_store.alloc_pat_float(Some(token.text_trimmed().into_symbol(db))).into() - } - ast::LiteralKind::String(token) => { - node_store.alloc_pat_string(Some(token.text_trimmed().into_symbol(db))).into() - } - ast::LiteralKind::Char(token) => { - node_store.alloc_pat_char(Some(token.text_trimmed().into_symbol(db))).into() - } - }, - ast::Pattern::Typed(typed) => { - let inner = typed - .pattern() - .map_or(PatId::ZERO, |pattern| lower_pattern_ref(db, node_store, pattern)); - let ty = - typed.ty().and_then(|ty| lower_type_ref(db, node_store, ty)).unwrap_or(TyId::ZERO); - node_store.alloc_pat_typed(inner, ty).into() - } - ast::Pattern::Paren(paren) => { - let inner = paren - .pattern() - .map_or(PatId::ZERO, |pattern| lower_pattern_ref(db, node_store, pattern)); - node_store.alloc_pat_paren(inner, PatId::ZERO).into() - } - ast::Pattern::Tuple(tuple) => { - let items: Vec<_> = tuple - .patterns() - .map(|pattern| lower_pattern_ref(db, node_store, pattern)) - .collect(); - node_store.alloc_pat_tuple(items).into() - } - ast::Pattern::Variant(variant) => { - let path = variant - .path() - .as_ref() - .map_or(ExprId::ZERO, |path| lower_field_pattern_path(db, node_store, path)); - let args: Vec<_> = variant - .patterns() - .map(|pattern| lower_pattern_ref(db, node_store, pattern)) - .collect(); - node_store.alloc_pat_variant(path, args).into() - } - ast::Pattern::Struct(struct_pattern) => { - let path = struct_pattern - .path() - .as_ref() - .map_or(ExprId::ZERO, |path| lower_path_pattern_path(db, node_store, path)); - let fields = struct_pattern - .fields() - .map(|field| { - let name = field.name().map_or("", |name| name.as_str()).into_symbol(db); - let pat = field - .pattern() - .map_or(PatId::ZERO, |pattern| lower_pattern_ref(db, node_store, pattern)); - node_store.alloc_pat_struct_field(name, pat) - }) - .collect::>(); - node_store.alloc_pat_struct(path, fields).into() - } - } -} - -fn lower_path_pattern_path<'db>( - db: &'db dyn Database, - node_store: &mut NodeStore<'db>, - path: &ast::PathPattern<'db>, -) -> ExprId { - let name = path.name().map_or("", |name| name.as_str()).into_symbol(db); - node_store.alloc_name(name).into() -} - -fn lower_field_pattern_path<'db>( - db: &'db dyn Database, - node_store: &mut NodeStore<'db>, - path: &ast::FieldPattern<'db>, -) -> ExprId { - let base = path - .base() - .as_ref() - .map_or(ExprId::ZERO, |base| lower_path_pattern_path(db, node_store, base)); - let name = path.name().map_or("", |name| name.as_str()).into_symbol(db); - let field_name: ExprId = node_store.alloc_name(name).into(); - node_store.alloc_field(base, field_name).into() -} - -impl<'db> FunctionLocation<'db> { - pub fn source(self, db: &'db dyn Database) -> ast::Function<'db> { - let file = self.file(db); - let ptr = file.ast_map(db).find_node(file.item_tree(db)[self.index(db)].id); - let syntax = source_syntax(db, file, ptr); - ast::Function::cast(syntax).unwrap() - } -} - -impl<'db> StructLocation<'db> { - pub fn file(self, db: &'db dyn Database) -> File { - self.module(db).file(db) - } - - pub fn source(self, db: &'db dyn Database) -> ast::StructDef<'db> { - let file = self.file(db); - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - let index = self.index(db); - - let item = item_tree[index].id; - let syntax = source_syntax(db, file, ast_map.find_node(item)); - ast::StructDef::cast(syntax).unwrap() - } - - pub fn destructor(self, db: &'db dyn Database) -> Option> { - self.source(db).destructor().map(|_| StructDestructorLocation::new(db, self)) - } -} - -impl<'db> EnumLocation<'db> { - pub fn file(self, db: &'db dyn Database) -> File { - self.module(db).file(db) - } - - pub fn source(self, db: &'db dyn Database) -> ast::EnumDef<'db> { - let file = self.file(db); - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - let index = self.index(db); - - let item = item_tree[index].id; - let syntax = source_syntax(db, file, ast_map.find_node(item)); - ast::EnumDef::cast(syntax).unwrap() - } - - pub fn destructor(self, db: &'db dyn Database) -> Option> { - self.source(db).destructor().map(|_| EnumDestructorLocation::new(db, self)) - } -} - -impl<'db> StructDestructorLocation<'db> { - pub fn source(self, db: &'db dyn Database) -> ast::DestructorDef<'db> { - self.parent(db).source(db).destructor().expect("struct destructor should exist") - } - - pub fn file(self, db: &'db dyn Database) -> File { - self.parent(db).file(db) - } -} - -impl<'db> EnumDestructorLocation<'db> { - pub fn source(self, db: &'db dyn Database) -> ast::DestructorDef<'db> { - self.parent(db).source(db).destructor().expect("enum destructor should exist") - } - - pub fn file(self, db: &'db dyn Database) -> File { - self.parent(db).file(db) - } -} - -impl<'db> EnumVariantLocation<'db> { - pub fn source(self, db: &'db dyn Database) -> ast::EnumVariant<'db> { - let variants = self.parent(db).source(db).variant_list().expect("enum variant list"); - variants - .variants() - .nth(self.variant_index(db) as usize) - .expect("enum variant index should be valid") - } - - pub fn name(self, db: &'db dyn Database) -> Symbol<'db> { - self.source(db).name().expect("enum variant should have a name").as_str().into_symbol(db) - } -} - -#[salsa::tracked(returns(ref))] -pub fn struct_fields<'db>( - db: &'db dyn Database, - nominal: StructTy<'db>, -) -> Vec<(Symbol<'db>, Ty<'db>)> { - let source = struct_source(db, nominal); - let visible_items = nominal.module(db).visible_items(db); - let type_params = nominal_type_param_env(db, source.type_params()); - source - .field_list() - .map(|field_list| { - field_list - .fields() - .filter_map(|field| { - let name = field.name()?.as_str().into_symbol(db); - let ty = resolve_ast_type_in_scope_with_type_params( - db, - nominal.module(db), - visible_items, - &type_params, - field.ty()?, - ); - Some((name, substitute_type_params(db, ty, nominal.args(db)))) - }) - .collect() - }) - .unwrap_or_default() -} - -#[salsa::tracked(returns(ref))] -pub fn enum_variants<'db>( - db: &'db dyn Database, - nominal: EnumTy<'db>, -) -> Vec<(Symbol<'db>, Vec>)> { - let source = enum_source(db, nominal); - let visible_items = nominal.module(db).visible_items(db); - let type_params = nominal_type_param_env(db, source.type_params()); - source - .variant_list() - .map(|variant_list| { - variant_list - .variants() - .filter_map(|variant| { - let name = variant.name()?.as_str().into_symbol(db); - let fields = variant - .field_types() - .map(|types| { - types - .types() - .map(|ty| { - substitute_type_params( - db, - resolve_ast_type_in_scope_with_type_params( - db, - nominal.module(db), - visible_items, - &type_params, - ty, - ), - nominal.args(db), - ) - }) - .collect() - }) - .unwrap_or_default(); - Some((name, fields)) - }) - .collect() - }) - .unwrap_or_default() -} - -fn source_syntax<'db>( - db: &'db dyn Database, - file: File, - ptr: &SyntaxNodePtr, -) -> mitki_yellow::SyntaxNode<'db> { - use mitki_parse::FileParse as _; - - ptr.to_node(&file.parse(db).syntax_node()) -} - -fn struct_source<'db>(db: &'db dyn Database, nominal: StructTy<'db>) -> ast::StructDef<'db> { - let file = nominal.file(db); - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - let index = Struct::new(nominal.index(db)); - let item = item_tree[index].id; - let syntax = source_syntax(db, file, ast_map.find_node(item)); - ast::StructDef::cast(syntax).unwrap() -} - -fn enum_source<'db>(db: &'db dyn Database, nominal: EnumTy<'db>) -> ast::EnumDef<'db> { - let file = nominal.file(db); - let item_tree = file.item_tree(db); - let ast_map = file.ast_map(db); - let index = Enum::new(nominal.index(db)); - let item = item_tree[index].id; - let syntax = source_syntax(db, file, ast_map.find_node(item)); - ast::EnumDef::cast(syntax).unwrap() -} - -#[salsa::tracked] -pub struct Signature<'db> { - #[tracked] - #[returns(deref)] - pub type_params: Vec>, - #[tracked] - #[returns(deref)] - pub params: Vec, - #[tracked] - pub ret_type: TyId, - #[tracked] - #[returns(ref)] - pub nodes: NodeStore<'db>, -} - -#[derive(Debug, Default, PartialEq, Eq, salsa::Update)] -pub struct ItemDecls<'db> { - declarations: Vec>, - enum_variants: Vec>, - enum_variants_by_name: FxIndexMap, Vec>>, -} - -impl<'db> ItemDecls<'db> { - pub fn declarations(&self) -> &[Declaration<'db>] { - &self.declarations - } - - pub fn enum_variants(&self) -> &[EnumVariantLocation<'db>] { - &self.enum_variants - } - - pub fn enum_variants_by_name(&self, name: &Symbol<'db>) -> &[EnumVariantLocation<'db>] { - self.enum_variants_by_name.get(name).map_or(&[], Vec::as_slice) - } -} - -#[derive(Debug, Default, PartialEq, Eq, salsa::Update)] -pub struct VisibleItems<'db> { - values: FxIndexMap, FunctionLocation<'db>>, - types: FxIndexMap, Ty<'db>>, - type_declarations: FxIndexMap, TypeDeclaration<'db>>, - modules: FxIndexMap, ModuleBinding<'db>>, - module_aliases: FxIndexMap, ModuleBinding<'db>>, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, salsa::Update)] -pub struct ModuleBinding<'db> { - pub module: ModuleId<'db>, - pub public: bool, -} - -impl<'db> VisibleItems<'db> { - pub fn get_value(&self, name: &Symbol<'db>) -> Option> { - self.values.get(name).copied() - } - - pub fn get_type(&self, name: &Symbol<'db>) -> Option> { - self.types.get(name).copied() - } - - pub fn get_type_declaration(&self, name: &Symbol<'db>) -> Option> { - self.type_declarations.get(name).copied() - } - - pub fn get_module(&self, name: &Symbol<'db>) -> Option> { - self.modules.get(name).copied() - } - - pub fn get_module_alias(&self, name: &Symbol<'db>) -> Option> { - self.module_aliases.get(name).copied() - } - - pub fn types(&self) -> impl Iterator, &Ty<'db>)> { - self.types.iter() - } - - pub fn values(&self) -> impl Iterator, &FunctionLocation<'db>)> { - self.values.iter() - } - - pub fn modules(&self) -> impl Iterator, &ModuleBinding<'db>)> { - self.modules.iter() - } -} - -struct ItemCollector<'db> { - db: &'db dyn Database, - module: ModuleId<'db>, - item_tree: &'db ItemTree<'db>, -} - -impl<'db> ItemCollector<'db> { - fn new(db: &'db dyn Database, file: File) -> Self { - let module = root_module(db, file.package(db)); - Self { db, module, item_tree: file.item_tree(db) } - } - - fn for_module(db: &'db dyn Database, module: ModuleId<'db>) -> Self { - let file = module.file(db); - Self { db, module, item_tree: file.item_tree(db) } - } - - fn build_decls(&self) -> ItemDecls<'db> { - let mut decls = ItemDecls::default(); - - for item in self.item_tree.items() { - match item { - Item::Function(index) => { - let func_loc = FunctionLocation::new(self.db, self.module, index); - decls.declarations.push(Declaration::Function(func_loc)); - } - Item::BoundaryInstance(index) => { - let loc = BoundaryInstanceLocation::new(self.db, self.module, index); - decls.declarations.push(Declaration::BoundaryInstance(loc)); - } - Item::Module(_) => {} - Item::Use(_) => {} - Item::Struct(index) => { - let loc = StructLocation::new(self.db, self.module, index); - decls.declarations.push(Declaration::Struct(loc)); - } - Item::Enum(index) => { - let loc = EnumLocation::new(self.db, self.module, index); - decls.declarations.push(Declaration::Enum(loc)); - - let variants = loc - .source(self.db) - .variant_list() - .map(|variants| variants.variants().collect::>()) - .unwrap_or_default(); - for (variant_index, variant) in variants.into_iter().enumerate() { - let Some(name) = variant.name() else { - continue; - }; - let variant_loc = - EnumVariantLocation::new(self.db, loc, variant_index as u32); - decls.enum_variants.push(variant_loc); - decls - .enum_variants_by_name - .entry(name.as_str().into_symbol(self.db)) - .or_default() - .push(variant_loc); - } - } - } - } - - decls - } - - fn build_visible_items(&self) -> VisibleItems<'db> { - let mut visible_items = VisibleItems::default(); - - for item in self.item_tree.items() { - match item { - Item::Function(index) => { - let func = &self.item_tree[index]; - let func_loc = FunctionLocation::new(self.db, self.module, index); - visible_items.values.insert(func.name, func_loc); - } - Item::BoundaryInstance(index) => { - let _ = BoundaryInstanceLocation::new(self.db, self.module, index); - } - Item::Module(index) => { - let data = &self.item_tree[index]; - if let Some(child) = - child_module_named(self.db, self.module, data.name.text(self.db).to_owned()) - { - visible_items.modules.insert( - data.name, - ModuleBinding { module: child, public: data.public }, - ); - } - } - Item::Use(_) => {} - Item::Struct(index) => { - let data = &self.item_tree[index]; - let loc = StructLocation::new(self.db, self.module, index); - let nominal = - StructTy::new(self.db, self.module, index.index(), data.name, Vec::new()); - let ty = if loc.source(self.db).is_extern() { - Ty::new(self.db, TyKind::ExternStruct(nominal)) - } else { - Ty::new(self.db, TyKind::Struct(nominal)) - }; - visible_items.types.insert(data.name, ty); - visible_items.type_declarations.insert(data.name, TypeDeclaration::Struct(loc)); - } - Item::Enum(index) => { - let data = &self.item_tree[index]; - let loc = EnumLocation::new(self.db, self.module, index); - let nominal = - EnumTy::new(self.db, self.module, index.index(), data.name, Vec::new()); - let ty = Ty::new(self.db, TyKind::Enum(nominal)); - visible_items.types.insert(data.name, ty); - visible_items.type_declarations.insert(data.name, TypeDeclaration::Enum(loc)); - } - } - } - - for item in self.item_tree.items() { - let Item::Use(index) = item else { - continue; - }; - self.import_use_item(&mut visible_items, &self.item_tree[index]); - } - - visible_items - } - - fn import_use_item(&self, visible_items: &mut VisibleItems<'db>, use_data: &UseData<'db>) { - if let Some(function) = - resolve_use_value(self.db, self.module, visible_items, use_data.path) - { - visible_items.values.insert(use_data.local_name, function); - } - - if let Some((ty, declaration)) = - resolve_use_type(self.db, self.module, visible_items, use_data.path) - { - visible_items.types.insert(use_data.local_name, ty); - visible_items.type_declarations.insert(use_data.local_name, declaration); - } - - if let Some(module) = resolve_use_module(self.db, self.module, visible_items, use_data.path) - { - visible_items.module_aliases.insert(use_data.local_name, module); - } - } -} - -fn resolve_ast_type_in_scope<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - ty: ast::Type, -) -> Ty<'db> { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - &FxIndexMap::default(), - ty, - ) -} - -fn resolve_ast_type_in_scope_with_type_params<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - type_params: &FxIndexMap, Ty<'db>>, - ty: ast::Type, -) -> Ty<'db> { - match ty { - ast::Type::Path(path) => { - let name = path.path_text().into_symbol(db); - let type_args = path - .type_args() - .into_iter() - .map(|arg| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - arg, - ) - }) - .collect::>(); - - match resolve_type_name_in_scope(db, current_module, visible_items, type_params, name) { - Some(base) if type_args.is_empty() => base, - Some(base) => instantiate_nominal_type(db, base, type_args) - .unwrap_or_else(|| Ty::new(db, TyKind::Unknown)), - None => Ty::new(db, TyKind::Unknown), - } - } - ast::Type::Array(array_type) => { - let item = array_type.item().map_or(Ty::new(db, TyKind::Unknown), |item| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - item, - ) - }); - Ty::new(db, TyKind::Array(item)) - } - ast::Type::Tuple(tuple_type) => { - let items = tuple_type - .types() - .map(|item| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - item, - ) - }) - .collect(); - Ty::new(db, TyKind::Tuple(items)) - } - ast::Type::Function(function_type) => { - let inputs = function_type - .inputs() - .map(|inputs| { - inputs - .types() - .map(|item| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - item, - ) - }) - .collect::>() - }) - .unwrap_or_default(); - let output = function_type.output().map_or(Ty::new(db, TyKind::Unknown), |output| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - output, - ) - }); - Ty::new(db, TyKind::Function { inputs, output }) - } - ast::Type::Union(union_type) => { - let lhs = union_type.lhs().map_or(Ty::new(db, TyKind::Unknown), |lhs| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - lhs, - ) - }); - let rhs = union_type.rhs().map_or(Ty::new(db, TyKind::Unknown), |rhs| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - rhs, - ) - }); - Ty::new(db, TyKind::Union(vec![lhs, rhs])) - } - ast::Type::Inter(inter_type) => { - let lhs = inter_type.lhs().map_or(Ty::new(db, TyKind::Unknown), |lhs| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - lhs, - ) - }); - let rhs = inter_type.rhs().map_or(Ty::new(db, TyKind::Unknown), |rhs| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - rhs, - ) - }); - Ty::new(db, TyKind::Inter(vec![lhs, rhs])) - } - ast::Type::Record(record_type) => { - let fields = record_type - .fields() - .filter_map(|field| { - let name = field.name()?; - let ty = field.ty().map(|ty| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - ty, - ) - })?; - Some((name.as_str().into_symbol(db), ty)) - }) - .collect(); - Ty::new(db, TyKind::Record(fields)) - } - ast::Type::Pointer(pointer_type) => { - let pointee = pointer_type.pointee().map_or(Ty::new(db, TyKind::Unknown), |item| { - resolve_ast_type_in_scope_with_type_params( - db, - current_module, - visible_items, - type_params, - item, - ) - }); - Ty::new(db, TyKind::Pointer { mutable: pointer_type.is_mut(), pointee }) - } - } -} - -fn resolve_type_name_in_scope<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - type_params: &FxIndexMap, Ty<'db>>, - name: Symbol<'db>, -) -> Option> { - if let Some(&ty) = type_params.get(&name) { - return Some(ty); - } - - if let Some(ty) = builtin_type(name.text(db), db) { - return Some(ty); - } - - if let Some((module, last)) = - resolve_segment_path_target(db, current_module, visible_items, name, AccessKind::Type) - { - return module.visible_items(db).get_type(&last); - } - - visible_items.get_type(&name) -} - -pub fn instantiate_nominal_type<'db>( - db: &'db dyn Database, - base: Ty<'db>, - args: Vec>, -) -> Option> { - match base.kind(db) { - TyKind::Struct(struct_ty) => instantiate_struct_type(db, *struct_ty, args) - .map(|nominal| Ty::new(db, TyKind::Struct(nominal))), - TyKind::ExternStruct(struct_ty) => instantiate_struct_type(db, *struct_ty, args) - .map(|nominal| Ty::new(db, TyKind::ExternStruct(nominal))), - TyKind::Enum(enum_ty) => instantiate_enum_type(db, *enum_ty, args) - .map(|nominal| Ty::new(db, TyKind::Enum(nominal))), - _ => None, - } -} - -#[salsa::tracked(returns(ref))] -pub fn struct_type_params<'db>(db: &'db dyn Database, nominal: StructTy<'db>) -> Vec> { - struct_source(db, nominal).type_params().map(|tp| tp.as_str().into_symbol(db)).collect() -} - -#[salsa::tracked(returns(ref))] -pub fn enum_type_params<'db>(db: &'db dyn Database, nominal: EnumTy<'db>) -> Vec> { - enum_source(db, nominal).type_params().map(|tp| tp.as_str().into_symbol(db)).collect() -} - -fn instantiate_struct_type<'db>( - db: &'db dyn Database, - nominal: StructTy<'db>, - args: Vec>, -) -> Option> { - (struct_type_params(db, nominal).len() == args.len()) - .then(|| StructTy::new(db, nominal.module(db), nominal.index(db), nominal.name(db), args)) -} - -fn instantiate_enum_type<'db>( - db: &'db dyn Database, - nominal: EnumTy<'db>, - args: Vec>, -) -> Option> { - (enum_type_params(db, nominal).len() == args.len()) - .then(|| EnumTy::new(db, nominal.module(db), nominal.index(db), nominal.name(db), args)) -} - -fn substitute_type_params<'db>(db: &'db dyn Database, ty: Ty<'db>, args: &[Ty<'db>]) -> Ty<'db> { - match ty.kind(db) { - TyKind::Var(id) => args.get(*id as usize).copied().unwrap_or(ty), - TyKind::Array(item) => Ty::new(db, TyKind::Array(substitute_type_params(db, *item, args))), - TyKind::Tuple(items) => Ty::new( - db, - TyKind::Tuple( - items.iter().map(|&item| substitute_type_params(db, item, args)).collect(), - ), - ), - TyKind::Record(fields) => Ty::new( - db, - TyKind::Record( - fields - .iter() - .map(|(name, ty)| (*name, substitute_type_params(db, *ty, args))) - .collect(), - ), - ), - TyKind::Pointer { mutable, pointee } => Ty::new( - db, - TyKind::Pointer { - mutable: *mutable, - pointee: substitute_type_params(db, *pointee, args), - }, - ), - TyKind::Function { inputs, output } => Ty::new( - db, - TyKind::Function { - inputs: inputs - .iter() - .map(|&input| substitute_type_params(db, input, args)) - .collect(), - output: substitute_type_params(db, *output, args), - }, - ), - TyKind::Union(items) => Ty::new( - db, - TyKind::Union( - items.iter().map(|&item| substitute_type_params(db, item, args)).collect(), - ), - ), - TyKind::Inter(items) => Ty::new( - db, - TyKind::Inter( - items.iter().map(|&item| substitute_type_params(db, item, args)).collect(), - ), - ), - TyKind::Rec(id, body) => { - Ty::new(db, TyKind::Rec(*id, substitute_type_params(db, *body, args))) - } - TyKind::Struct(struct_ty) => Ty::new( - db, - TyKind::Struct(StructTy::new( - db, - struct_ty.module(db), - struct_ty.index(db), - struct_ty.name(db), - struct_ty - .args(db) - .iter() - .map(|&arg| substitute_type_params(db, arg, args)) - .collect::>(), - )), - ), - TyKind::ExternStruct(struct_ty) => Ty::new( - db, - TyKind::ExternStruct(StructTy::new( - db, - struct_ty.module(db), - struct_ty.index(db), - struct_ty.name(db), - struct_ty - .args(db) - .iter() - .map(|&arg| substitute_type_params(db, arg, args)) - .collect::>(), - )), - ), - TyKind::Enum(enum_ty) => Ty::new( - db, - TyKind::Enum(EnumTy::new( - db, - enum_ty.module(db), - enum_ty.index(db), - enum_ty.name(db), - enum_ty - .args(db) - .iter() - .map(|&arg| substitute_type_params(db, arg, args)) - .collect::>(), - )), - ), - TyKind::Bool - | TyKind::Float - | TyKind::Int - | TyKind::ExactInt(_) - | TyKind::String - | TyKind::Char - | TyKind::Unknown => ty, - } -} - -fn nominal_type_param_env<'db>( - db: &'db dyn Database, - type_params: impl Iterator>, -) -> FxIndexMap, Ty<'db>> { - type_params - .enumerate() - .map(|(index, ty_param)| { - let symbol = ty_param.as_str().into_symbol(db); - (symbol, Ty::new(db, TyKind::Var(index as u32))) - }) - .collect() -} - -fn builtin_type<'db>(name: &str, db: &'db dyn Database) -> Option> { - Some(match name { - "bool" => Ty::new(db, TyKind::Bool), - "u8" => Ty::new(db, TyKind::ExactInt(ExactInt::U8)), - "u16" => Ty::new(db, TyKind::ExactInt(ExactInt::U16)), - "u32" => Ty::new(db, TyKind::ExactInt(ExactInt::U32)), - "u64" => Ty::new(db, TyKind::ExactInt(ExactInt::U64)), - "i8" => Ty::new(db, TyKind::ExactInt(ExactInt::I8)), - "i16" => Ty::new(db, TyKind::ExactInt(ExactInt::I16)), - "i32" => Ty::new(db, TyKind::ExactInt(ExactInt::I32)), - "i64" => Ty::new(db, TyKind::ExactInt(ExactInt::I64)), - "int" => Ty::new(db, TyKind::Int), - "float" => Ty::new(db, TyKind::Float), - "str" => Ty::new(db, TyKind::String), - "char" => Ty::new(db, TyKind::Char), - _ => return None, - }) -} - -fn resolve_use_value<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - path: Symbol<'db>, -) -> Option> { - if let Some((module, last)) = - resolve_segment_path_target(db, current_module, visible_items, path, AccessKind::Value) - { - return module.visible_items(db).get_value(&last); - } - - visible_items.get_value(&path) -} - -fn resolve_use_type<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - path: Symbol<'db>, -) -> Option<(Ty<'db>, TypeDeclaration<'db>)> { - if let Some((module, last)) = - resolve_segment_path_target(db, current_module, visible_items, path, AccessKind::Type) - { - let module_visible = module.visible_items(db); - return Some(( - module_visible.get_type(&last)?, - module_visible.get_type_declaration(&last)?, - )); - } - - Some((visible_items.get_type(&path)?, visible_items.get_type_declaration(&path)?)) -} - -fn resolve_use_module<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - path: Symbol<'db>, -) -> Option> { - resolve_segment_module_path(db, current_module, visible_items, path) -} - -#[derive(Clone, Copy)] -enum AccessKind { - Value, - Type, -} - -fn resolve_segment_module_path<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - path: Symbol<'db>, -) -> Option> { - let mut segments = path.text(db).split("::").filter(|segment| !segment.is_empty()); - let first = segments.next()?; - - let mut binding = match first { - "crate" => { - ModuleBinding { module: root_module(db, current_module.package(db)), public: true } - } - "std" => ModuleBinding { module: root_module(db, stdlib_package(db)), public: true }, - _ => visible_items - .get_module_alias(&first.into_symbol(db)) - .or_else(|| visible_items.get_module(&first.into_symbol(db)))?, - }; - - for segment in segments { - let child = binding.module.visible_items(db).get_module(&segment.into_symbol(db))?; - binding = ModuleBinding { module: child.module, public: binding.public && child.public }; - } - - Some(binding) -} - -fn resolve_segment_path_target<'db>( - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &VisibleItems<'db>, - path: Symbol<'db>, - _access: AccessKind, -) -> Option<(ModuleId<'db>, Symbol<'db>)> { - let segments = - path.text(db).split("::").filter(|segment| !segment.is_empty()).collect::>(); - if segments.len() < 2 { - return None; - } - - let last = (*segments.last()?).into_symbol(db); - let module_path = segments[..segments.len() - 1].join("::").into_symbol(db); - let binding = resolve_segment_module_path(db, current_module, visible_items, module_path)?; - - let external = matches!(segments.first().copied(), Some("std")); - if external && !binding.public { - return None; - } - - Some((binding.module, last)) -} - -#[cfg(test)] -mod tests { - use mitki_inputs::File; - use mitki_span::IntoSymbol as _; - use mitki_yellow::ast::HasName as _; - - use super::{Declaration, HasItemDecls as _, HasVisibleItems as _}; - use crate::item::package::{child_module_named, root_module}; - use crate::item::stdlib::{stdlib_module_path, stdlib_package}; - - #[salsa::db] - #[derive(Default)] - struct TestDb { - storage: salsa::Storage, - } - - #[salsa::db] - impl salsa::Database for TestDb {} - - #[test] - fn source_methods_resolve_each_declaration_kind() { - let db = TestDb::default(); - let file = File::new( - &db, - "source_paths.mitki".into(), - r#" -fun main() {} - -struct Point { - x: int, -} - -enum Color { - Red, -} -"# - .to_owned(), - ); - - let scope = file.item_decls(&db); - let mut saw_function = false; - let mut saw_struct = false; - let mut saw_enum = false; - - for declaration in scope.declarations() { - match *declaration { - Declaration::Function(location) => { - let source = location.source(&db); - assert_eq!(source.name().unwrap().as_str(), "main"); - saw_function = true; - } - Declaration::BoundaryInstance(location) => { - let source = location.source(&db); - assert_eq!(source.name().unwrap().as_str(), "main"); - } - Declaration::Struct(location) => { - let source = location.source(&db); - assert_eq!(source.name().unwrap().as_str(), "Point"); - saw_struct = true; - } - Declaration::Enum(location) => { - let source = location.source(&db); - assert_eq!(source.name().unwrap().as_str(), "Color"); - saw_enum = true; - } - } - } - - assert!(saw_function, "expected to resolve function source"); - assert!(saw_struct, "expected to resolve struct source"); - assert!(saw_enum, "expected to resolve enum source"); - } - - #[test] - fn stdlib_modules_expose_module_local_items() { - let db = TestDb::default(); - let std_root = root_module(&db, stdlib_package(&db)); - let io_module = - child_module_named(&db, std_root, "io".to_owned()).expect("expected std::io module"); - - let root_visible = std_root.visible_items(&db); - let io_visible = io_module.visible_items(&db); - let io_binding = root_visible - .get_module(&"io".into_symbol(&db)) - .expect("expected std root to export io"); - let location = io_visible - .get_value(&"print_int".into_symbol(&db)) - .expect("expected print_int to resolve inside std::io"); - - assert!(io_binding.public); - assert_eq!(io_binding.module, io_module); - assert_eq!(location.file(&db).path(&db), stdlib_module_path("io.mitki")); - assert_eq!(location.source(&db).name().unwrap().as_str(), "print_int"); - } -} diff --git a/crates/mitki-lower/src/item/stdlib.rs b/crates/mitki-lower/src/item/stdlib.rs deleted file mode 100644 index 3b5248c..0000000 --- a/crates/mitki-lower/src/item/stdlib.rs +++ /dev/null @@ -1,27 +0,0 @@ -use camino::{Utf8Path, Utf8PathBuf}; -use mitki_inputs::{File, PackageId}; -use salsa::Database; - -fn stdlib_src_dir() -> Utf8PathBuf { - Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../stdlib/src") -} - -pub fn stdlib_module_path(relative: &str) -> Utf8PathBuf { - stdlib_src_dir().join(relative) -} - -fn load_stdlib_file(db: &dyn Database, path: Utf8PathBuf) -> File { - let text = std::fs::read_to_string(&path) - .unwrap_or_else(|err| panic!("failed to read stdlib file {}: {err}", path)); - File::new(db, path, text) -} - -#[salsa::tracked] -pub fn stdlib_root_file(db: &dyn Database) -> File { - load_stdlib_file(db, stdlib_module_path("lib.mitki")) -} - -#[salsa::tracked] -pub fn stdlib_package(db: &dyn Database) -> PackageId<'_> { - PackageId::new(db, stdlib_root_file(db)) -} diff --git a/crates/mitki-lower/src/item/tree.rs b/crates/mitki-lower/src/item/tree.rs deleted file mode 100644 index 11d6a69..0000000 --- a/crates/mitki-lower/src/item/tree.rs +++ /dev/null @@ -1,227 +0,0 @@ -use std::ops::Index; - -use mitki_hir::arena::{Arena, Key}; -use mitki_inputs::File; -use mitki_parse::FileParse as _; -use mitki_span::{IntoSymbol as _, Symbol}; -use mitki_yellow::SyntaxNodePtr; -use mitki_yellow::ast::{HasName as _, Node as _}; -use salsa::Database; - -use crate::ast_map::HasAstMap as _; - -pub type Function<'db> = Key>; -pub type BoundaryInstance<'db> = Key>; -pub type Module<'db> = Key>; -pub type Use<'db> = Key>; -pub type Struct<'db> = Key>; -pub type Enum<'db> = Key>; - -pub trait HasItemTree { - fn item_tree(self, db: &dyn Database) -> &ItemTree<'_>; -} - -#[salsa::tracked] -impl HasItemTree for File { - #[salsa::tracked(returns(ref), no_eq)] - fn item_tree(self, db: &dyn Database) -> ItemTree<'_> { - let mut item_tree = ItemTree::default(); - let ast_map = self.ast_map(db); - - for item in self.parse(db).tree().items() { - let item = match item { - mitki_yellow::ast::Item::Function(func) => { - let id = ast_map.find_id(func.syntax()); - let Some(name) = func.name().map(|name| name.as_str().into_symbol(db)) else { - continue; - }; - - Item::Function(item_tree.functions.alloc(FunctionData { id, name })) - } - mitki_yellow::ast::Item::Instance(instance) => { - let id = ast_map.find_id(instance.syntax()); - let Some(name) = instance.name().map(|name| name.as_str().into_symbol(db)) - else { - continue; - }; - - Item::BoundaryInstance( - item_tree.boundary_instances.alloc(BoundaryInstanceData { id, name }), - ) - } - mitki_yellow::ast::Item::Module(module) => { - let id = ast_map.find_id(module.syntax()); - let Some(name) = module.name().map(|name| name.as_str().into_symbol(db)) else { - continue; - }; - - Item::Module(item_tree.modules.alloc(ModuleData { - id, - name, - public: module.is_public(), - })) - } - mitki_yellow::ast::Item::Use(use_item) => { - let id = ast_map.find_id(use_item.syntax()); - let Some(path) = - use_item.path().map(|path| path.syntax().text_trimmed().into_symbol(db)) - else { - continue; - }; - let Some(local_name) = use_item - .alias() - .map(|name| name.as_str().into_symbol(db)) - .or_else(|| last_path_segment(db, path)) - else { - continue; - }; - - Item::Use(item_tree.uses.alloc(UseData { id, path, local_name })) - } - mitki_yellow::ast::Item::Struct(s) => { - let id = ast_map.find_id(s.syntax()); - let Some(name) = s.name().map(|name| name.as_str().into_symbol(db)) else { - continue; - }; - let destructor_id = - s.destructor().map(|destructor| SyntaxNodePtr::new(destructor.syntax())); - - Item::Struct(item_tree.structs.alloc(StructData { id, name, destructor_id })) - } - mitki_yellow::ast::Item::Enum(e) => { - let id = ast_map.find_id(e.syntax()); - let Some(name) = e.name().map(|name| name.as_str().into_symbol(db)) else { - continue; - }; - let destructor_id = - e.destructor().map(|destructor| SyntaxNodePtr::new(destructor.syntax())); - - Item::Enum(item_tree.enums.alloc(EnumData { id, name, destructor_id })) - } - }; - - item_tree.items.push(item); - } - - item_tree - } -} - -#[derive(Debug, Default, salsa::Update)] -pub struct ItemTree<'db> { - items: Vec>, - functions: Arena>, - boundary_instances: Arena>, - modules: Arena>, - uses: Arena>, - structs: Arena>, - enums: Arena>, -} - -impl<'db> Index> for ItemTree<'db> { - type Output = FunctionData<'db>; - - fn index(&self, index: Function<'db>) -> &Self::Output { - &self.functions[index] - } -} - -impl<'db> Index> for ItemTree<'db> { - type Output = StructData<'db>; - - fn index(&self, index: Struct<'db>) -> &Self::Output { - &self.structs[index] - } -} - -impl<'db> Index> for ItemTree<'db> { - type Output = BoundaryInstanceData<'db>; - - fn index(&self, index: BoundaryInstance<'db>) -> &Self::Output { - &self.boundary_instances[index] - } -} - -impl<'db> Index> for ItemTree<'db> { - type Output = ModuleData<'db>; - - fn index(&self, index: Module<'db>) -> &Self::Output { - &self.modules[index] - } -} - -impl<'db> Index> for ItemTree<'db> { - type Output = UseData<'db>; - - fn index(&self, index: Use<'db>) -> &Self::Output { - &self.uses[index] - } -} - -impl<'db> Index> for ItemTree<'db> { - type Output = EnumData<'db>; - - fn index(&self, index: Enum<'db>) -> &Self::Output { - &self.enums[index] - } -} - -impl<'db> ItemTree<'db> { - pub(crate) fn items(&self) -> impl ExactSizeIterator> + '_ { - self.items.iter().copied() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update)] -pub(crate) enum Item<'db> { - Function(Function<'db>), - BoundaryInstance(BoundaryInstance<'db>), - Module(Module<'db>), - Use(Use<'db>), - Struct(Struct<'db>), - Enum(Enum<'db>), -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct FunctionData<'db> { - pub id: Key, - pub name: Symbol<'db>, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct BoundaryInstanceData<'db> { - pub id: Key, - pub name: Symbol<'db>, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct ModuleData<'db> { - pub id: Key, - pub name: Symbol<'db>, - pub public: bool, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct UseData<'db> { - pub id: Key, - pub path: Symbol<'db>, - pub local_name: Symbol<'db>, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct StructData<'db> { - pub id: Key, - pub name: Symbol<'db>, - pub destructor_id: Option, -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct EnumData<'db> { - pub id: Key, - pub name: Symbol<'db>, - pub destructor_id: Option, -} - -fn last_path_segment<'db>(db: &'db dyn Database, path: Symbol<'db>) -> Option> { - path.text(db).rsplit("::").next().map(|segment| segment.into_symbol(db)) -} diff --git a/crates/mitki-lower/src/lib.rs b/crates/mitki-lower/src/lib.rs deleted file mode 100644 index 8f16854..0000000 --- a/crates/mitki-lower/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod ast_map; -pub mod hir; -pub mod item; - -pub use ast_map::HasAstMap; -pub use hir::HasFunction; -pub use item::scope::{HasItemDecls, HasPackageDecls, HasVisibleItems}; -pub use item::tree::HasItemTree; diff --git a/crates/mitki-lsp-server/Cargo.toml b/crates/mitki-lsp-server/Cargo.toml deleted file mode 100644 index a1749a2..0000000 --- a/crates/mitki-lsp-server/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "mitki-lsp-server" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -lsp-server = "0.7" -lsp-types = "0.97" -mitki-db.workspace = true -mitki-ide.workspace = true -mitki-inputs.workspace = true -salsa.workspace = true -serde = "1.0" -serde_json = "1.0" -text-size.workspace = true diff --git a/crates/mitki-lsp-server/src/api.rs b/crates/mitki-lsp-server/src/api.rs deleted file mode 100644 index 8228da7..0000000 --- a/crates/mitki-lsp-server/src/api.rs +++ /dev/null @@ -1,326 +0,0 @@ -use std::collections::hash_map::Entry; -use std::fs; - -use anyhow::Result; -use lsp_types::notification::Notification as _; -use mitki_ide::SemanticTokenKind; -use mitki_inputs::File; -use salsa::Setter as _; -use text_size::{TextRange, TextSize}; - -use super::notifications::NotificationDispatcher; -use super::requests::RequestDispatcher; -use super::{Server, file_position}; - -pub(crate) fn request(server: &mut Server, request: lsp_server::Request) { - RequestDispatcher::new(request, server) - .on::(handle_goto_definition) - .on::(handle_hover) - .on::(handle_document_diagnostic) - .on::(handle_semantic_tokens_full) - .on::(handle_semantic_tokens_full_delta) - .on::(handle_semantic_tokens_range) - .on::(handle_inlay_hint) - .finish(); -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_goto_definition( - server: &mut Server, - params: lsp_types::GotoDefinitionParams, -) -> Result> { - let file_position = file_position(server, ¶ms.text_document_position_params); - let line_index = file_position.file.line_index(server.analysis.db()); - - match server.analysis.goto_definition(file_position) { - Some((origin_selection_range, target_range)) => { - Ok(Some(lsp_types::GotoDefinitionResponse::Link(vec![lsp_types::LocationLink { - origin_selection_range: to_lsp_range(line_index, origin_selection_range).into(), - target_uri: params.text_document_position_params.text_document.uri.clone(), - target_range: to_lsp_range(line_index, target_range), - target_selection_range: to_lsp_range(line_index, target_range), - }]))) - } - None => Ok(None), - } -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_hover( - server: &mut Server, - params: lsp_types::HoverParams, -) -> Result> { - let file_position = file_position(server, ¶ms.text_document_position_params); - let line_index = file_position.file.line_index(server.analysis.db()); - - match server.analysis.hover(file_position) { - Some(result) => Ok(Some(lsp_types::Hover { - contents: lsp_types::HoverContents::Markup(lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: result.contents, - }), - range: Some(to_lsp_range(line_index, result.range)), - })), - None => Ok(None), - } -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_document_diagnostic( - server: &mut Server, - params: lsp_types::DocumentDiagnosticParams, -) -> Result { - let file = ensure_file(server, ¶ms.text_document.uri)?; - let diagnostics = diagnostics_for_file(server, file); - - Ok(lsp_types::DocumentDiagnosticReport::Full(lsp_types::RelatedFullDocumentDiagnosticReport { - related_documents: None, - full_document_diagnostic_report: lsp_types::FullDocumentDiagnosticReport { - result_id: None, - items: diagnostics, - }, - }) - .into()) -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_semantic_tokens_full( - server: &mut Server, - params: lsp_types::SemanticTokensParams, -) -> Result> { - let file = ensure_file(server, ¶ms.text_document.uri)?; - Ok(Some(lsp_types::SemanticTokensResult::Tokens(semantic_tokens_for_file(server, file, None)))) -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_semantic_tokens_range( - server: &mut Server, - params: lsp_types::SemanticTokensRangeParams, -) -> Result> { - let file = ensure_file(server, ¶ms.text_document.uri)?; - let line_index = file.line_index(server.analysis.db()); - let start = position_to_offset(line_index, params.range.start); - let end = position_to_offset(line_index, params.range.end); - Ok(Some(lsp_types::SemanticTokensRangeResult::Tokens(semantic_tokens_for_file( - server, - file, - Some(TextRange::new(start, end)), - )))) -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_semantic_tokens_full_delta( - server: &mut Server, - params: lsp_types::SemanticTokensDeltaParams, -) -> Result> { - let file = ensure_file(server, ¶ms.text_document.uri)?; - Ok(Some(lsp_types::SemanticTokensFullDeltaResult::Tokens(semantic_tokens_for_file( - server, file, None, - )))) -} - -#[expect(clippy::needless_pass_by_value)] -fn handle_inlay_hint( - server: &mut Server, - params: lsp_types::InlayHintParams, -) -> Result>> { - let file = server.file(¶ms.text_document.uri); - let line_index = file.line_index(server.analysis.db()); - - let start = position_to_offset(line_index, params.range.start); - let end = position_to_offset(line_index, params.range.end); - let range = TextRange::new(start, end); - - let hints = server - .analysis - .inlay_hints(file, range) - .into_iter() - .map(|hint| { - let position = line_index.line_col(hint.offset); - lsp_types::InlayHint { - position: lsp_types::Position::new(position.line, position.col), - label: lsp_types::InlayHintLabel::String(hint.label), - kind: Some(lsp_types::InlayHintKind::TYPE), - text_edits: None, - tooltip: None, - padding_left: None, - padding_right: None, - data: None, - } - }) - .collect(); - - Ok(Some(hints)) -} - -pub(crate) fn notification(server: &mut Server, notification: lsp_server::Notification) { - NotificationDispatcher::new(notification, server) - .on::(handle_did_open_text_document) - .on::(handle_did_change_text_document) - .on::(handle_did_save_text_document) - .finish(); -} - -fn handle_did_open_text_document( - server: &mut Server, - params: lsp_types::DidOpenTextDocumentParams, -) -> Result<()> { - let lsp_types::TextDocumentItem { uri, language_id: _, version: _, text } = - params.text_document; - - let file = match server.files.entry(uri.clone()) { - Entry::Occupied(occupied) => { - let file = *occupied.get(); - file.set_text(server.analysis.db_mut()).to(text); - file - } - Entry::Vacant(vacant) => { - *vacant.insert(File::new(server.analysis.db(), uri.path().as_str().into(), text)) - } - }; - - publish_diagnostics(server, uri, file)?; - - Ok(()) -} - -fn handle_did_change_text_document( - server: &mut Server, - params: lsp_types::DidChangeTextDocumentParams, -) -> Result<()> { - let uri = params.text_document.uri; - let file = ensure_file(server, &uri)?; - let change = params.content_changes.into_iter().next().unwrap(); - file.set_text(server.analysis.db_mut()).to(change.text); - publish_diagnostics(server, uri, file)?; - Ok(()) -} - -fn handle_did_save_text_document( - server: &mut Server, - params: lsp_types::DidSaveTextDocumentParams, -) -> Result<()> { - let uri = params.text_document.uri; - let file = ensure_file(server, &uri)?; - publish_diagnostics(server, uri, file)?; - Ok(()) -} - -fn ensure_file(server: &mut Server, uri: &lsp_types::Uri) -> Result { - if let Some(file) = server.files.get(uri).copied() { - return Ok(file); - } - - let mut path = uri.path().as_str(); - if path.starts_with('/') - && path.as_bytes().get(2) == Some(&b':') - && path.as_bytes().get(1).is_some_and(|b| b.is_ascii_alphabetic()) - { - path = &path[1..]; - } - let text = fs::read_to_string(path).unwrap_or_default(); - let file = File::new(server.analysis.db(), uri.path().as_str().into(), text); - server.files.insert(uri.clone(), file); - Ok(file) -} - -fn diagnostics_for_file(server: &Server, file: File) -> Vec { - let line_index = file.line_index(server.analysis.db()); - mitki_db::check_file(server.analysis.db(), file) - .iter() - .map(|diagnostic: &mitki_db::Diagnostic| { - lsp_types::Diagnostic::new( - to_lsp_range(line_index, diagnostic.range()), - Some(match diagnostic.level() { - mitki_db::Level::Error => lsp_types::DiagnosticSeverity::ERROR, - mitki_db::Level::Warning => lsp_types::DiagnosticSeverity::WARNING, - mitki_db::Level::Info => lsp_types::DiagnosticSeverity::INFORMATION, - mitki_db::Level::Note | mitki_db::Level::Help => { - lsp_types::DiagnosticSeverity::HINT - } - }), - None, - Some("mitki".to_string()), - diagnostic.message().to_string(), - None, - None, - ) - }) - .collect() -} - -fn semantic_tokens_for_file( - server: &Server, - file: File, - range: Option, -) -> lsp_types::SemanticTokens { - let line_index = file.line_index(server.analysis.db()); - let mut data = Vec::new(); - let mut prev_line = 0; - let mut prev_start = 0; - - for token in server.analysis.semantic_tokens(file, range) { - let start = line_index.line_col(token.range.start()); - let end = line_index.line_col(token.range.end()); - let delta_line = start.line.saturating_sub(prev_line); - let delta_start = - if delta_line == 0 { start.col.saturating_sub(prev_start) } else { start.col }; - let length = if start.line == end.line { end.col.saturating_sub(start.col) } else { 0 }; - - data.push(lsp_types::SemanticToken { - delta_line, - delta_start, - length, - token_type: semantic_token_type_index(token.kind), - token_modifiers_bitset: 0, - }); - - prev_line = start.line; - prev_start = start.col; - } - - lsp_types::SemanticTokens { result_id: None, data } -} - -fn semantic_token_type_index(kind: SemanticTokenKind) -> u32 { - match kind { - SemanticTokenKind::Function => 0, - SemanticTokenKind::Parameter => 1, - SemanticTokenKind::Variable => 2, - SemanticTokenKind::Type => 3, - SemanticTokenKind::EnumMember => 4, - SemanticTokenKind::BuiltinType => 5, - SemanticTokenKind::BuiltinFunction => 6, - } -} - -fn publish_diagnostics(server: &Server, uri: lsp_types::Uri, file: File) -> Result<()> { - let diagnostics = diagnostics_for_file(server, file); - let params = lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None }; - let notification = lsp_server::Notification::new( - lsp_types::notification::PublishDiagnostics::METHOD.to_string(), - serde_json::to_value(params)?, - ); - server.connection.sender.send(notification.into())?; - Ok(()) -} - -fn to_lsp_range(line_index: &mitki_inputs::LineIndex, range: TextRange) -> lsp_types::Range { - let start = line_index.line_col(range.start()); - let end = line_index.line_col(range.end()); - - lsp_types::Range { - start: lsp_types::Position::new(start.line, start.col), - end: lsp_types::Position::new(end.line, end.col), - } -} - -fn position_to_offset( - line_index: &mitki_inputs::LineIndex, - position: lsp_types::Position, -) -> TextSize { - let line_range = line_index.line(position.line).unwrap(); - let col = TextSize::from(position.character); - let clamped_len = col.min(line_range.len()); - line_range.start() + clamped_len -} diff --git a/crates/mitki-lsp-server/src/lib.rs b/crates/mitki-lsp-server/src/lib.rs deleted file mode 100644 index d17b0c4..0000000 --- a/crates/mitki-lsp-server/src/lib.rs +++ /dev/null @@ -1,158 +0,0 @@ -mod api; -mod notifications; -mod requests; - -use std::collections::HashMap; - -use anyhow::Result; -use mitki_ide::{Analysis, FilePosition}; -use mitki_inputs::File; -use text_size::TextSize; - -pub struct Server { - connection: lsp_server::Connection, - io_threads: lsp_server::IoThreads, - analysis: Analysis, - files: HashMap, -} - -impl Server { - fn server_capabilities() -> lsp_types::ServerCapabilities { - lsp_types::ServerCapabilities { - diagnostic_provider: Some(lsp_types::DiagnosticServerCapabilities::Options( - lsp_types::DiagnosticOptions { - identifier: Some(env!("CARGO_PKG_NAME").to_string()), - ..Default::default() - }, - )), - text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Options( - lsp_types::TextDocumentSyncOptions { - open_close: Some(true), - change: Some(lsp_types::TextDocumentSyncKind::FULL), - will_save: Some(false), - will_save_wait_until: Some(false), - save: Some(lsp_types::TextDocumentSyncSaveOptions::SaveOptions( - lsp_types::SaveOptions { include_text: Some(true) }, - )), - }, - )), - definition_provider: Some(lsp_types::OneOf::Left(true)), - hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)), - inlay_hint_provider: Some(lsp_types::OneOf::Left(true)), - semantic_tokens_provider: Some( - lsp_types::SemanticTokensOptions { - work_done_progress_options: Default::default(), - legend: lsp_types::SemanticTokensLegend { - token_types: vec![ - lsp_types::SemanticTokenType::FUNCTION, - lsp_types::SemanticTokenType::PARAMETER, - lsp_types::SemanticTokenType::VARIABLE, - lsp_types::SemanticTokenType::TYPE, - lsp_types::SemanticTokenType::ENUM_MEMBER, - lsp_types::SemanticTokenType::new("builtinType"), - lsp_types::SemanticTokenType::new("builtinFunction"), - ], - token_modifiers: vec![], - }, - range: Some(true), - full: Some(lsp_types::SemanticTokensFullOptions::Delta { delta: Some(true) }), - } - .into(), - ), - ..lsp_types::ServerCapabilities::default() - } - } - - pub fn new() -> Result { - let (connection, io_threads) = lsp_server::Connection::stdio(); - - let (initialize_id, _initialize_params) = match connection.initialize_start() { - Ok(it) => it, - Err(protocol_error) => { - if protocol_error.channel_is_disconnected() { - io_threads.join()?; - } - return Err(protocol_error.into()); - } - }; - - let initialize_result = serde_json::to_value(lsp_types::InitializeResult { - capabilities: Self::server_capabilities(), - server_info: Some(lsp_types::ServerInfo { - name: env!("CARGO_PKG_NAME").to_string(), - version: env!("CARGO_PKG_VERSION").to_string().into(), - }), - })?; - - if let Err(protocol_error) = connection.initialize_finish(initialize_id, initialize_result) - { - if protocol_error.channel_is_disconnected() { - io_threads.join()?; - } - return Err(protocol_error.into()); - } - - Ok(Self { - connection, - io_threads, - analysis: Analysis::default(), - files: HashMap::default(), - }) - } - - fn file(&self, uri: &lsp_types::Uri) -> File { - *self.files.get(uri).unwrap() - } - - fn respond(&mut self, response: lsp_server::Response) { - self.connection.sender.send(response.into()).unwrap(); - } - - pub fn run(mut self) -> Result<()> { - let receiver = self.connection.receiver.clone(); - for message in &receiver { - match message { - lsp_server::Message::Request(request) => api::request(&mut self, request), - lsp_server::Message::Response(_response) => {} - lsp_server::Message::Notification(notification) => { - api::notification(&mut self, notification) - } - } - } - self.io_threads.join().map_err(Into::into) - } -} - -fn from_json( - what: &'static str, - json: &serde_json::Value, -) -> Result { - serde_json::from_value(json.clone()) - .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}")) -} - -fn result_to_response( - id: lsp_server::RequestId, - result: Result, -) -> lsp_server::Response -where - R: lsp_types::request::Request, -{ - match result { - Ok(resp) => lsp_server::Response::new_ok(id, &resp), - Err(error) => lsp_server::Response::new_err(id, -32603, error.to_string()), - } -} - -fn file_position(server: &Server, tdpp: &lsp_types::TextDocumentPositionParams) -> FilePosition { - let file = server.file(&tdpp.text_document.uri); - let offset = { - let line_index = file.line_index(server.analysis.db()); - let line_range = line_index.line(tdpp.position.line).unwrap(); - let col = TextSize::from(tdpp.position.character); - let clamped_len = col.min(line_range.len()); - line_range.start() + clamped_len - }; - - FilePosition { file, offset } -} diff --git a/crates/mitki-lsp-server/src/notifications.rs b/crates/mitki-lsp-server/src/notifications.rs deleted file mode 100644 index 24d0818..0000000 --- a/crates/mitki-lsp-server/src/notifications.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Result; - -use super::Server; - -pub(crate) struct NotificationDispatcher<'me> { - notification: Option, - server: &'me mut Server, -} - -impl<'me> NotificationDispatcher<'me> { - pub(crate) fn new(notification: lsp_server::Notification, server: &'me mut Server) -> Self { - Self { notification: notification.into(), server } - } - - pub(crate) fn on(&mut self, f: fn(&mut Server, N::Params) -> Result<()>) -> &mut Self - where - N: lsp_types::notification::Notification, - { - let Some(notification) = self.notification.take() else { - return self; - }; - - let params = match notification.extract::(N::METHOD) { - Ok(it) => it, - Err(lsp_server::ExtractError::JsonError { method, error }) => { - panic!("Invalid request\nMethod: {method}\n error: {error}",) - } - Err(lsp_server::ExtractError::MethodMismatch(notification)) => { - self.notification = Some(notification); - return self; - } - }; - - if let Err(error) = f(self.server, params) { - eprintln!("{}: {}", N::METHOD, error); - } - - self - } - - pub(crate) fn finish(&mut self) { - if let Some(not) = &self.notification - && !not.method.starts_with("$/") - { - eprintln!("unhandled notification: {not:?}"); - } - } -} diff --git a/crates/mitki-lsp-server/src/requests.rs b/crates/mitki-lsp-server/src/requests.rs deleted file mode 100644 index 06115f6..0000000 --- a/crates/mitki-lsp-server/src/requests.rs +++ /dev/null @@ -1,55 +0,0 @@ -use anyhow::Result; - -pub(crate) struct RequestDispatcher<'me> { - request: Option, - server: &'me mut crate::Server, -} - -impl<'me> RequestDispatcher<'me> { - pub(crate) fn new(request: lsp_server::Request, server: &'me mut crate::Server) -> Self { - Self { request: request.into(), server } - } - - fn parse(&mut self) -> Option<(lsp_server::Request, R::Params)> - where - R: lsp_types::request::Request, - { - let request = self.request.take_if(|request| request.method == R::METHOD)?; - match crate::from_json(R::METHOD, &request.params) { - Ok(params) => Some((request, params)), - Err(error) => { - self.server.respond(lsp_server::Response::new_err( - request.id, - lsp_server::ErrorCode::InvalidParams as i32, - error.to_string(), - )); - None - } - } - } - - pub(crate) fn on(mut self, f: fn(&mut crate::Server, R::Params) -> Result) -> Self - where - R: lsp_types::request::Request, - { - let Some((request, params)) = self.parse::() else { - return self; - }; - - let result = crate::result_to_response::(request.id, f(self.server, params)); - self.server.respond(result); - - self - } - - pub(crate) fn finish(self) { - if let Some(request) = self.request { - eprintln!("unknown request: {request:?}"); - self.server.respond(lsp_server::Response::new_err( - request.id, - lsp_server::ErrorCode::MethodNotFound as i32, - "unknown request".to_owned(), - )); - } - } -} diff --git a/crates/mitki-parse/Cargo.toml b/crates/mitki-parse/Cargo.toml deleted file mode 100644 index 32f96ae..0000000 --- a/crates/mitki-parse/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "mitki-parse" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -drop_bomb = "0.1" -mitki-errors.workspace = true -mitki-inputs.workspace = true -mitki-tokenizer.workspace = true -mitki-yellow.workspace = true -salsa.workspace = true -text-size.workspace = true - -[dev-dependencies] -expect-test = "1.5" diff --git a/crates/mitki-parse/src/grammar.rs b/crates/mitki-parse/src/grammar.rs deleted file mode 100644 index 24ff99c..0000000 --- a/crates/mitki-parse/src/grammar.rs +++ /dev/null @@ -1,57 +0,0 @@ -use mitki_yellow::SyntaxKind::{self, *}; -use mitki_yellow::SyntaxSet; - -use crate::parser::Parser; - -mod exprs; -pub(crate) mod items; -pub(crate) mod patterns; -mod types; - -pub(crate) fn name(p: &mut Parser, recovery: &SyntaxSet) { - match p.peek_kind() { - NAME | VAR_KW => { - let m = p.start(); - p.advance(); - m.complete(p, IDENT); - } - _ => p.error_recover("expected identifier", recovery), - } -} - -pub(crate) fn delimited( - p: &mut Parser<'_>, - bra: SyntaxKind, - ket: SyntaxKind, - delim: SyntaxKind, - unexpected_delim_message: &'static str, - first_set: &SyntaxSet, - mut parser: impl FnMut(&mut Parser<'_>) -> bool, -) { - debug_assert_eq!(p.peek_kind(), bra); - p.advance(); - - while !p.at(ket) && !p.at(EOF) { - if p.at(delim) { - let m = p.start(); - p.error(unexpected_delim_message); - p.advance(); - m.complete(p, ERROR); - continue; - } - - if !parser(p) { - break; - } - - if !p.eat(delim) { - if first_set.contains(p.peek_kind()) { - p.expect(delim); - } else { - break; - } - } - } - - p.expect(ket); -} diff --git a/crates/mitki-parse/src/grammar/exprs.rs b/crates/mitki-parse/src/grammar/exprs.rs deleted file mode 100644 index ac91f51..0000000 --- a/crates/mitki-parse/src/grammar/exprs.rs +++ /dev/null @@ -1,521 +0,0 @@ -use mitki_yellow::SyntaxKind::*; -use mitki_yellow::SyntaxSet; -use text_size::TextRange; - -use super::{delimited, name, patterns, types}; -use crate::parser::{CompletedMarker, Parser}; - -pub(crate) fn stmt(p: &mut Parser) -> bool { - match p.peek_kind() { - VAL_KW | VAR_KW => { - let m = p.start(); - p.advance(); - patterns::pattern(p, &SyntaxSet::new([COLON, EQ, SEMICOLON])); - if p.at(COLON) { - types::ascription(p); - } - if p.eat(EQ) { - expr(p); - } - m.complete(p, VAL_STMT); - } - RETURN_KW => { - let m = p.start(); - p.advance(); - expr(p); - m.complete(p, RETURN_STMT); - } - _ => { - let parsed_expr = expr(p); - if p.at(EQ) { - let Some(lhs) = parsed_expr else { - p.error("expected assignment target"); - return false; - }; - let m = lhs.precede(p); - p.advance(); - expr(p); - m.complete(p, ASSIGN_STMT); - return false; - } - let has_semi = p.eat(SEMICOLON); - if has_semi { - parsed_expr.map(|m| m.precede(p).complete(p, EXPR_STMT)); - } - return has_semi; - } - }; - - false -} - -pub(crate) fn expr(p: &mut Parser) -> Option { - let head = unary_expr(p)?; - - if p.peek_kind() != BINARY_OPERATOR { - return Some(head); - } - - let m = head.precede(p); - - while p.peek_kind() == BINARY_OPERATOR { - p.advance(); - if unary_expr(p).is_none() { - break; - } - } - - Some(m.complete(p, BIN_OP_SEQ)) -} - -pub(crate) fn block(p: &mut Parser<'_>) { - if p.peek_kind() != LEFT_BRACE { - p.error("expected a block"); - return; - } - - let m = p.start(); - p.advance(); - block_contents(p); - p.expect(RIGHT_BRACE); - m.complete(p, STMT_LIST); -} -fn block_contents(parser: &mut Parser) { - let mut prev_had_semicolon = true; - - while !matches!(parser.peek_kind(), RIGHT_BRACE | EOF) { - let consecutive_statements_on_same_line = - !prev_had_semicolon && parser.next_token_on_same_line(); - - let stmt_start = parser.peek_range().start(); - stmt(parser); - - if consecutive_statements_on_same_line { - parser.error_with_range( - "Consecutive statements on the same line must be separated by ';'", - TextRange::new(stmt_start, parser.previous_range().end()), - ); - } - - prev_had_semicolon = parser.eat(SEMICOLON); - } -} - -fn unary_expr(p: &mut Parser) -> Option { - match p.peek_kind() { - NAME if p.peek_text() == "unsafe" && p.nth_kind(1) == LEFT_BRACE => { - let m = p.start(); - p.advance(); - block(p); - m.complete(p, UNSAFE_EXPR).into() - } - LOOP_KW => { - let m = p.start(); - p.advance(); - block(p); - m.complete(p, LOOP_EXPR).into() - } - BREAK_KW => { - let m = p.start(); - p.advance(); - m.complete(p, BREAK_EXPR).into() - } - CONTINUE_KW => { - let m = p.start(); - p.advance(); - m.complete(p, CONTINUE_EXPR).into() - } - MATCH_KW => match_(p), - IF_KW => if_(p), - PREFIX_OPERATOR => { - let m = p.start(); - p.advance(); - unary_expr(p); - m.complete(p, PREFIX_EXPR).into() - } - BINARY_OPERATOR => { - let m = p.start(); - p.error("unary operator cannot be separated from its operand"); - p.advance(); - unary_expr(p); - m.complete(p, PREFIX_OPERATOR).into() - } - _ => postfix_expr(p), - } -} - -fn if_(p: &mut Parser<'_>) -> Option { - debug_assert_eq!(p.peek_kind(), IF_KW); - - let m = p.start(); - p.advance(); - expr(p); - block(p); - if p.at(ELSE_KW) { - p.advance(); - if p.at(IF_KW) { - if_(p); - } else { - block(p); - } - } - m.complete(p, IF_EXPR).into() -} - -fn match_(p: &mut Parser<'_>) -> Option { - debug_assert_eq!(p.peek_kind(), MATCH_KW); - - let m = p.start(); - p.advance(); - expr(p); - - if !p.eat(LEFT_BRACE) { - p.error("expected `{`"); - return Some(m.complete(p, MATCH_EXPR)); - } - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if p.at(COMMA) { - let err = p.start(); - p.error("expected match arm"); - p.advance(); - err.complete(p, ERROR); - continue; - } - - let arm = p.start(); - patterns::match_pattern(p, &SyntaxSet::new([FAT_ARROW, COMMA, RIGHT_BRACE])); - p.expect(FAT_ARROW); - expr(p); - arm.complete(p, MATCH_ARM); - - if !p.eat(COMMA) { - if matches!( - p.peek_kind(), - LEFT_PAREN - | DOT - | NAME - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - Some(m.complete(p, MATCH_EXPR)) -} - -fn postfix_expr(p: &mut Parser) -> Option { - let mut head = primary_expr(p)?; - - loop { - head = match p.peek_kind() { - POSTFIX_OPERATOR => { - p.advance(); - head.precede(p).complete(p, POSTFIX_EXPR) - } - LEFT_PAREN if p.next_token_on_same_line() => { - let m = p.start(); - delimited( - p, - LEFT_PAREN, - RIGHT_PAREN, - COMMA, - "expected expression", - &SyntaxSet::new([ - INT_NUMBER, - FLOAT_NUMBER, - DOT, - NAME, - IF_KW, - MATCH_KW, - LOOP_KW, - BREAK_KW, - CONTINUE_KW, - NAME, - LEFT_PAREN, - LEFT_BRACE, - LEFT_BRACKET, - PREFIX_OPERATOR, - ]), - |p| expr(p).is_some(), - ); - m.complete(p, ARG_LIST); - head.precede(p).complete(p, CALL_EXPR) - } - LEFT_BRACE - if p.next_token_on_same_line() - && head.kind() == PATH_EXPR - && looks_like_struct_expr_field_list(p, true) => - { - struct_expr_field_list(p); - head.precede(p).complete(p, STRUCT_EXPR) - } - - DOT if p.next_token_on_same_line() => { - p.advance(); - let name = p.start(); - p.expect(NAME); - name.complete(p, NAME_REF); - head.precede(p).complete(p, FIELD_EXPR) - } - - _ => break, - } - } - - head.into() -} - -fn struct_expr_field_list(p: &mut Parser) { - debug_assert_eq!(p.peek_kind(), LEFT_BRACE); - let m = p.start(); - p.advance(); - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if p.at(COMMA) { - let err = p.start(); - p.error("expected field"); - p.advance(); - err.complete(p, ERROR); - continue; - } - - let field = p.start(); - name(p, &SyntaxSet::new([COLON, COMMA, RIGHT_BRACE])); - if p.eat(COLON) { - expr(p); - } - field.complete(p, STRUCT_EXPR_FIELD); - - if !p.eat(COMMA) { - if p.at(NAME) { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - m.complete(p, STRUCT_EXPR_FIELD_LIST); -} - -fn primary_expr(p: &mut Parser) -> Option { - match p.peek_kind() { - INT_NUMBER | FLOAT_NUMBER | STRING | CHAR | TRUE_KW | FALSE_KW => { - let m = p.start(); - p.advance(); - m.complete(p, LITERAL).into() - } - LEFT_PAREN => { - let m = p.start(); - p.advance(); - - let mut saw_comma = false; - let mut saw_expr = false; - - if p.eat(COMMA) { - p.error("expected expression"); - saw_comma = true; - } - - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - saw_expr = true; - - if expr(p).is_none() { - break; - } - - if !p.at(RIGHT_PAREN) { - saw_comma = true; - p.expect(COMMA); - } - } - - p.expect(RIGHT_PAREN); - m.complete(p, if saw_expr && !saw_comma { PAREN_EXPR } else { TUPLE_EXPR }).into() - } - LEFT_BRACKET => { - let m = p.start(); - p.advance(); - - let mut n_exprs = 0u32; - let mut has_semi = false; - - while !matches!(p.peek_kind(), RIGHT_BRACKET | EOF) { - n_exprs += 1; - - if expr(p).is_none() { - break; - } - - if n_exprs == 1 && p.eat(SEMICOLON) { - has_semi = true; - continue; - } - - if has_semi || !p.at(RIGHT_BRACKET) && !p.expect(COMMA) { - break; - } - } - - p.expect(RIGHT_BRACKET); - m.complete(p, ARRAY_EXPR).into() - } - NAME => { - let m = p.start(); - path_segments(p); - - m.complete(p, PATH_EXPR).into() - } - DOT => { - let m = p.start(); - p.advance(); - let name = p.start(); - p.expect(NAME); - name.complete(p, NAME_REF); - m.complete(p, FIELD_EXPR).into() - } - LEFT_BRACE => { - if looks_like_struct_expr_field_list(p, false) { - return anonymous_struct_expr(p); - } - - let closure = p.start(); - p.advance(); - - p.try_parse(|lookahead| { - let m = lookahead.start(); - while matches!( - lookahead.peek_kind(), - LEFT_PAREN - | DOT - | NAME - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - let m = lookahead.start(); - patterns::pattern(lookahead, &SyntaxSet::new([COMMA, IN_KW, RIGHT_BRACE])); - m.complete(lookahead, PARAM); - - if !lookahead.eat(COMMA) { - if matches!( - lookahead.peek_kind(), - LEFT_PAREN - | DOT - | NAME - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - lookahead.expect(COMMA); - } else { - break; - } - } - } - m.complete(lookahead, PARAM_LIST); - lookahead.eat(IN_KW) - }); - - let stmts = p.start(); - if !p.at(RIGHT_BRACE) { - block_contents(p); - } - stmts.complete(p, STMT_LIST); - - p.expect(RIGHT_BRACE); - closure.complete(p, CLOSURE_EXPR).into() - } - _ => { - p.error_and_bump("expected expression"); - None - } - } -} - -pub(crate) fn path_segments(p: &mut Parser) { - let name = p.start(); - if matches!(p.peek_kind(), NAME | VAR_KW) { - p.advance(); - } else { - p.error("expected identifier"); - } - name.complete(p, NAME_REF); - - while p.at(DOUBLE_COLON) { - p.advance(); - let name = p.start(); - if matches!(p.peek_kind(), NAME | VAR_KW) { - p.advance(); - } else { - p.error("expected identifier"); - } - name.complete(p, NAME_REF); - } -} - -fn anonymous_struct_expr(p: &mut Parser) -> Option { - let m = p.start(); - struct_expr_field_list(p); - Some(m.complete(p, STRUCT_EXPR)) -} - -fn looks_like_struct_expr_field_list(p: &mut Parser, allow_empty: bool) -> bool { - let mut is_struct = false; - p.try_parse(|lookahead| { - is_struct = try_parse_struct_expr_field_list_lookahead(lookahead, allow_empty); - false - }); - is_struct -} - -fn try_parse_struct_expr_field_list_lookahead(p: &mut Parser, allow_empty: bool) -> bool { - if !p.eat(LEFT_BRACE) { - return false; - } - if p.eat(RIGHT_BRACE) { - return allow_empty; - } - if !p.at(NAME) { - return false; - } - - loop { - if !p.eat(NAME) { - return false; - } - if p.eat(COLON) { - if expr(p).is_none() { - return false; - } - } else if p.at(RIGHT_BRACE) { - return false; - } - if p.eat(COMMA) { - if p.at(RIGHT_BRACE) { - break; - } - continue; - } - break; - } - - p.eat(RIGHT_BRACE) -} diff --git a/crates/mitki-parse/src/grammar/items.rs b/crates/mitki-parse/src/grammar/items.rs deleted file mode 100644 index e4d5945..0000000 --- a/crates/mitki-parse/src/grammar/items.rs +++ /dev/null @@ -1,499 +0,0 @@ -use mitki_yellow::SyntaxKind::*; -use mitki_yellow::SyntaxSet; - -use super::{delimited, exprs, name, patterns, types}; -use crate::parser::Parser; - -pub(crate) fn module(p: &mut Parser) { - let m = p.start(); - - while p.peek_kind() != EOF { - item(p); - } - - p.expect(EOF); - m.complete(p, MODULE); -} - -fn item(p: &mut Parser) { - match p.peek_kind() { - NAME if matches!(p.peek_text(), "export" | "import") - && p.nth_kind(1) == NAME - && p.nth_text(1) == "instance" => - { - instance_item(p); - } - NAME if looks_like_function_item_start(p) => function(p), - NAME if p.peek_text() == "extern" && p.nth_kind(1) == STRUCT_KW => { - struct_def(p); - } - FUN_KW => { - function(p); - } - STRUCT_KW => { - struct_def(p); - } - ENUM_KW => { - let m = p.start(); - p.advance(); - - name(p, &SyntaxSet::new([LEFT_BRACE, SEMICOLON])); - generic_param_list(p); - - if p.at(LEFT_BRACE) { - enum_variant_list(p); - } else { - p.error("expected `{`"); - } - - m.complete(p, ENUM_DEF); - } - MOD_KW => { - module_item(p); - } - PUB_KW if p.nth_kind(1) == MOD_KW => { - module_item(p); - } - USE_KW => { - use_item(p); - } - SEMICOLON => p.error_and_bump("expected item, found `;`"), - _ => { - let m = p.start(); - p.error("expected an item"); - while !matches!( - p.peek_kind(), - EOF | FUN_KW | VAL_KW | STRUCT_KW | ENUM_KW | MOD_KW | PUB_KW | USE_KW - ) { - p.advance(); - } - m.complete(p, ERROR); - } - } -} - -fn looks_like_function_item_start(p: &mut Parser) -> bool { - let mut index = 0usize; - - loop { - match p.nth_kind(index) { - NAME if matches!(p.nth_text(index), "comptime" | "export" | "unsafe") => { - index += 1; - } - NAME if p.nth_text(index) == "import" => { - index += 1; - if p.nth_kind(index) != STRING { - return false; - } - index += 1; - } - FUN_KW => return true, - _ => return false, - } - } -} - -fn struct_def(p: &mut Parser) { - let m = p.start(); - if p.peek_kind() == NAME && p.peek_text() == "extern" { - p.advance(); - } - p.expect(STRUCT_KW); - - name(p, &SyntaxSet::new([LEFT_BRACE, SEMICOLON])); - generic_param_list(p); - - if p.at(LEFT_BRACE) { - struct_field_list(p); - } else { - p.error("expected `{`"); - } - - m.complete(p, STRUCT_DEF); -} - -fn module_item(p: &mut Parser) { - let m = p.start(); - p.eat(PUB_KW); - p.expect(MOD_KW); - name(p, &SyntaxSet::new([SEMICOLON])); - p.expect(SEMICOLON); - m.complete(p, MOD_ITEM); -} - -fn use_item(p: &mut Parser) { - let m = p.start(); - p.expect(USE_KW); - - let path = p.start(); - exprs::path_segments(p); - path.complete(p, PATH_EXPR); - - if p.eat(AS_KW) { - name(p, &SyntaxSet::new([SEMICOLON])); - } - - p.expect(SEMICOLON); - m.complete(p, USE_ITEM); -} - -fn instance_item(p: &mut Parser) { - let m = p.start(); - if p.peek_kind() == NAME && matches!(p.peek_text(), "export" | "import") { - p.advance(); - } else { - p.error("expected `export` or `import`"); - } - - if p.peek_kind() == NAME && p.peek_text() == "instance" { - p.advance(); - } else { - p.error("expected `instance`"); - } - - name(p, &SyntaxSet::new([LEFT_BRACKET, SEMICOLON])); - generic_arg_list(p); - p.expect(SEMICOLON); - m.complete(p, INSTANCE_ITEM); -} - -#[derive(Clone, Copy, Default, PartialEq, Eq)] -struct FunctionModifiers { - import: bool, -} - -fn function(p: &mut Parser) { - let m = p.start(); - let modifiers = function_modifiers(p); - - p.expect(FUN_KW); - - name(p, &SyntaxSet::new([FUN_KW, SEMICOLON])); - generic_param_list(p); - - if p.at(LEFT_PAREN) { - param_list(p); - } else { - p.error("expected function parameters"); - } - - if p.at(COLON) { - let m = p.start(); - types::ascription(p); - m.complete(p, RETURN_TYPE); - } - - if modifiers.import { - if p.at(LEFT_BRACE) { - exprs::block(p); - } else { - p.expect(SEMICOLON); - } - } else if p.at(LEFT_BRACE) { - exprs::block(p); - } else if !p.eat(SEMICOLON) { - p.error("expected function body"); - } - - m.complete(p, FN); -} - -fn function_modifiers(p: &mut Parser) -> FunctionModifiers { - let mut modifiers = FunctionModifiers::default(); - - while p.peek_kind() == NAME { - match p.peek_text() { - "comptime" | "export" | "unsafe" => { - p.advance(); - } - "import" => { - p.advance(); - if p.at(STRING) { - p.advance(); - modifiers.import = true; - } else { - p.error("expected import module string"); - } - } - _ => break, - } - } - - modifiers -} - -fn generic_param_list(p: &mut Parser) { - if p.peek_kind() != LEFT_BRACKET { - return; - } - - delimited( - p, - LEFT_BRACKET, - RIGHT_BRACKET, - COMMA, - "expected generic parameter", - &SyntaxSet::new([NAME]), - generic_param, - ); -} - -fn generic_arg_list(p: &mut Parser) { - if p.peek_kind() != LEFT_BRACKET { - p.error("expected concrete type arguments"); - return; - } - - let m = p.start(); - p.advance(); - - if p.at(COMMA) { - p.error("expected a type"); - } - - while !matches!(p.peek_kind(), RIGHT_BRACKET | EOF) { - types::type_(p); - - if !p.eat(COMMA) { - if p.at(RIGHT_BRACKET) { - break; - } - p.expect(COMMA); - break; - } - } - - p.expect(RIGHT_BRACKET); - m.complete(p, GENERIC_ARG_LIST); -} - -fn generic_param(p: &mut Parser) -> bool { - match p.peek_kind() { - NAME => { - let m = p.start(); - p.advance(); - m.complete(p, TYPE_PARAM); - true - } - _ => false, - } -} - -fn param_list(p: &mut Parser) { - let m = p.start(); - p.advance(); - - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - if !matches!( - p.peek_kind(), - VAR_KW - | NAME - | DOT - | LEFT_PAREN - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - p.error("expected parameter pattern"); - if p.eat(COMMA) { - continue; - } - break; - } - - param(p); - - if !p.eat(COMMA) { - if matches!( - p.peek_kind(), - VAR_KW - | NAME - | DOT - | LEFT_PAREN - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_PAREN); - m.complete(p, PARAM_LIST); -} - -fn param(p: &mut Parser) { - let m = p.start(); - p.eat(VAR_KW); - patterns::pattern(p, &SyntaxSet::new([COLON, COMMA, RIGHT_PAREN])); - - if p.at(COLON) { - types::ascription(p); - } - - m.complete(p, PARAM); -} - -fn struct_field_list(p: &mut Parser) { - let m = p.start(); - p.advance(); // eat LEFT_BRACE - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if looks_like_destructor_member(p) { - destructor_def(p); - if !p.eat(COMMA) { - break; - } - continue; - } - - if p.peek_kind() != NAME { - p.error("expected field name"); - if p.eat(COMMA) { - continue; - } - break; - } - - let field = p.start(); - name(p, &SyntaxSet::EMPTY); - - if p.at(COLON) { - types::ascription(p); - } else { - p.error("missing type for struct field"); - } - field.complete(p, STRUCT_FIELD); - - if !p.eat(COMMA) { - if p.peek_kind() == NAME { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - m.complete(p, STRUCT_FIELD_LIST); -} - -fn enum_variant_list(p: &mut Parser) { - let m = p.start(); - p.advance(); // eat LEFT_BRACE - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if looks_like_destructor_member(p) { - destructor_def(p); - if !p.eat(COMMA) { - break; - } - continue; - } - - if p.peek_kind() != NAME { - p.error("expected variant name"); - if p.eat(COMMA) { - continue; - } - break; - } - - let variant = p.start(); - name(p, &SyntaxSet::EMPTY); - - if p.at(LEFT_PAREN) { - let tl = p.start(); - p.advance(); - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - types::type_(p); - if !p.at(RIGHT_PAREN) { - p.expect(COMMA); - } - } - p.expect(RIGHT_PAREN); - tl.complete(p, TUPLE_TYPE); - } - - variant.complete(p, ENUM_VARIANT); - - if !p.eat(COMMA) { - if p.peek_kind() == NAME { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - m.complete(p, ENUM_VARIANT_LIST); -} - -fn destructor_def(p: &mut Parser) { - let m = p.start(); - if p.peek_kind() == NAME && p.peek_text() == "drop" { - p.advance(); - } else { - p.error("expected `drop`"); - } - - if p.at(LEFT_PAREN) { - param_list(p); - } else { - p.error("expected destructor parameters"); - } - - if p.at(LEFT_BRACE) { - exprs::block(p); - } else { - p.error("expected destructor body"); - } - - m.complete(p, DESTRUCTOR_DEF); -} - -fn looks_like_destructor_member(p: &mut Parser) -> bool { - let mut result = false; - p.try_parse(|lookahead| { - if lookahead.peek_kind() != NAME || lookahead.peek_text() != "drop" { - return false; - } - lookahead.advance(); - if lookahead.peek_kind() != LEFT_PAREN { - return false; - } - - let mut depth = 0usize; - while !matches!(lookahead.peek_kind(), EOF) { - match lookahead.peek_kind() { - LEFT_PAREN => depth += 1, - RIGHT_PAREN => { - if depth == 0 { - break; - } - depth -= 1; - if depth == 0 { - lookahead.advance(); - break; - } - } - _ => {} - } - lookahead.advance(); - } - - result = depth == 0 && lookahead.peek_kind() == LEFT_BRACE; - false - }); - result -} diff --git a/crates/mitki-parse/src/grammar/patterns.rs b/crates/mitki-parse/src/grammar/patterns.rs deleted file mode 100644 index c10e7ff..0000000 --- a/crates/mitki-parse/src/grammar/patterns.rs +++ /dev/null @@ -1,250 +0,0 @@ -use mitki_yellow::SyntaxKind::*; -use mitki_yellow::SyntaxSet; - -use super::{name, types}; -use crate::parser::{CompletedMarker, Parser}; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(crate) enum PatternMode { - Default, - Match, -} - -pub(crate) fn pattern(p: &mut Parser<'_>, recovery: &SyntaxSet) -> bool { - pattern_with_mode(p, recovery, PatternMode::Default) -} - -pub(crate) fn match_pattern(p: &mut Parser<'_>, recovery: &SyntaxSet) -> bool { - pattern_with_mode(p, recovery, PatternMode::Match) -} - -fn pattern_with_mode(p: &mut Parser<'_>, recovery: &SyntaxSet, mode: PatternMode) -> bool { - pattern_marker(p, recovery, mode).is_some() -} - -fn pattern_marker( - p: &mut Parser<'_>, - recovery: &SyntaxSet, - mode: PatternMode, -) -> Option { - let pattern = match p.peek_kind() { - LEFT_PAREN => tuple_or_group_pattern(p, mode), - DOT => variant_pattern(p, mode), - NAME if p.peek_text() == "_" => { - let m = p.start(); - p.advance(); - Some(m.complete(p, WILDCARD_PATTERN)) - } - NAME if looks_like_variant_pattern(p) => variant_pattern(p, mode), - NAME if looks_like_struct_pattern(p) => struct_pattern(p, mode), - NAME => { - let m = p.start(); - name(p, recovery); - Some(m.complete(p, BINDING_PATTERN)) - } - INT_NUMBER | FLOAT_NUMBER | STRING | CHAR | TRUE_KW | FALSE_KW => { - let m = p.start(); - p.advance(); - Some(m.complete(p, LITERAL_PATTERN)) - } - _ => { - p.error_recover("expected pattern", recovery); - None - } - }?; - - if mode == PatternMode::Match - && p.at(COLON) - && matches!(pattern.kind(), BINDING_PATTERN | WILDCARD_PATTERN) - { - let m = pattern.precede(p); - types::ascription(p); - return Some(m.complete(p, TYPED_PATTERN)); - } - - Some(pattern) -} - -fn tuple_or_group_pattern(p: &mut Parser<'_>, mode: PatternMode) -> Option { - debug_assert_eq!(p.peek_kind(), LEFT_PAREN); - - let m = p.start(); - p.advance(); - - let mut count = 0usize; - let mut saw_comma = false; - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - count += 1; - if !pattern_with_mode( - p, - &SyntaxSet::new([COMMA, RIGHT_PAREN, FAT_ARROW, EQ, COLON, LEFT_BRACE]), - mode, - ) { - break; - } - - if p.eat(COMMA) { - saw_comma = true; - if p.at(RIGHT_PAREN) { - break; - } - continue; - } - break; - } - - p.expect(RIGHT_PAREN); - - let kind = if count == 1 && !saw_comma { PAREN_PATTERN } else { TUPLE_PATTERN }; - Some(m.complete(p, kind)) -} - -fn struct_pattern(p: &mut Parser<'_>, mode: PatternMode) -> Option { - debug_assert_eq!(p.peek_kind(), NAME); - - let m = p.start(); - path_pattern(p)?; - p.expect(LEFT_BRACE); - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if p.at(COMMA) { - let err = p.start(); - p.error("expected struct pattern field"); - p.advance(); - err.complete(p, ERROR); - continue; - } - - struct_pattern_field(p, mode); - - if !p.eat(COMMA) { - if p.at(NAME) { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - Some(m.complete(p, STRUCT_PATTERN)) -} - -fn struct_pattern_field(p: &mut Parser<'_>, mode: PatternMode) { - let m = p.start(); - name(p, &SyntaxSet::new([COLON, COMMA, RIGHT_BRACE])); - if p.eat(COLON) { - pattern_with_mode(p, &SyntaxSet::new([COMMA, RIGHT_BRACE]), mode); - } - m.complete(p, STRUCT_PATTERN_FIELD); -} - -fn variant_pattern(p: &mut Parser<'_>, mode: PatternMode) -> Option { - let head = path_head_pattern(p)?; - let m = head.precede(p); - - if p.at(LEFT_PAREN) { - p.advance(); - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - if p.at(COMMA) { - let err = p.start(); - p.error("expected pattern"); - p.advance(); - err.complete(p, ERROR); - continue; - } - - if !pattern_with_mode(p, &SyntaxSet::new([COMMA, RIGHT_PAREN]), mode) { - break; - } - - if !p.eat(COMMA) { - if matches!( - p.peek_kind(), - LEFT_PAREN - | DOT - | NAME - | INT_NUMBER - | FLOAT_NUMBER - | STRING - | CHAR - | TRUE_KW - | FALSE_KW - ) { - p.expect(COMMA); - } else { - break; - } - } - } - p.expect(RIGHT_PAREN); - } - - Some(m.complete(p, VARIANT_PATTERN)) -} - -fn path_pattern(p: &mut Parser<'_>) -> Option { - if p.peek_kind() != NAME { - p.error("expected identifier"); - return None; - } - - let m = p.start(); - name(p, &SyntaxSet::new([DOT, LEFT_BRACE])); - Some(m.complete(p, PATH_PATTERN)) -} - -fn path_head_pattern(p: &mut Parser<'_>) -> Option { - match p.peek_kind() { - DOT => { - let m = p.start(); - p.advance(); - name(p, &SyntaxSet::new([LEFT_PAREN, FAT_ARROW, COMMA, RIGHT_BRACE])); - Some(m.complete(p, FIELD_PATTERN)) - } - NAME => { - let head = path_pattern(p)?; - if p.eat(DOT) { - let m = head.precede(p); - name(p, &SyntaxSet::new([LEFT_PAREN, FAT_ARROW, COMMA, RIGHT_BRACE])); - Some(m.complete(p, FIELD_PATTERN)) - } else { - Some(head) - } - } - _ => { - p.error("expected variant path"); - None - } - } -} - -fn looks_like_variant_pattern(p: &mut Parser<'_>) -> bool { - if p.peek_kind() == DOT { - return true; - } - - let mut result = false; - p.try_parse(|lookahead| { - if lookahead.peek_kind() != NAME { - return false; - } - lookahead.advance(); - result = lookahead.eat(DOT) && lookahead.peek_kind() == NAME; - false - }); - result -} - -fn looks_like_struct_pattern(p: &mut Parser<'_>) -> bool { - let mut result = false; - p.try_parse(|lookahead| { - if lookahead.peek_kind() != NAME { - return false; - } - lookahead.advance(); - result = lookahead.peek_kind() == LEFT_BRACE; - false - }); - result -} diff --git a/crates/mitki-parse/src/grammar/types.rs b/crates/mitki-parse/src/grammar/types.rs deleted file mode 100644 index 774f196..0000000 --- a/crates/mitki-parse/src/grammar/types.rs +++ /dev/null @@ -1,238 +0,0 @@ -use mitki_yellow::SyntaxKind::*; -use mitki_yellow::SyntaxSet; - -use super::name; -use crate::parser::{CompletedMarker, Parser}; - -pub(crate) fn type_(p: &mut Parser) { - if union_type(p).is_none() && !matches!(p.peek_kind(), RIGHT_PAREN | RIGHT_BRACE | EOF) { - p.advance(); - } -} - -pub(crate) fn ascription(p: &mut Parser) { - debug_assert_eq!(p.peek_kind(), COLON); - p.advance(); - type_(p); -} - -fn union_type(p: &mut Parser) -> Option { - let mut head = inter_type(p)?; - - while p.at_binary_op("|") { - let m = head.precede(p); - p.advance(); - if inter_type(p).is_none() { - head = m.complete(p, UNION_TYPE); - break; - } - head = m.complete(p, UNION_TYPE); - } - - Some(head) -} - -fn inter_type(p: &mut Parser) -> Option { - let mut head = type_atom(p)?; - - while p.at_binary_op("&") { - let m = head.precede(p); - p.advance(); - if type_atom(p).is_none() { - head = m.complete(p, INTER_TYPE); - break; - } - head = m.complete(p, INTER_TYPE); - } - - Some(head) -} - -fn type_atom(p: &mut Parser) -> Option { - match p.peek_kind() { - NAME => { - let m = p.start(); - p.advance(); - while p.at(DOUBLE_COLON) { - p.advance(); - p.expect(NAME); - } - generic_arg_list(p); - Some(m.complete(p, PATH_TYPE)) - } - PREFIX_OPERATOR if p.peek_text() == "*" => pointer_type(p), - LEFT_BRACKET => array_type(p), - LEFT_PAREN => tuple_type(p), - LEFT_BRACE => record_type(p), - FUN_KW => function_type(p), - _ => { - p.error("expected a type"); - None - } - } -} - -fn generic_arg_list(p: &mut Parser) { - if p.peek_kind() != LEFT_BRACKET { - return; - } - - let m = p.start(); - p.advance(); - - if p.at(COMMA) { - p.error("expected a type"); - } - - while !matches!(p.peek_kind(), RIGHT_BRACKET | EOF) { - type_(p); - - if !p.eat(COMMA) { - if p.at(RIGHT_BRACKET) { - break; - } - p.expect(COMMA); - break; - } - } - - p.expect(RIGHT_BRACKET); - m.complete(p, GENERIC_ARG_LIST); -} - -fn pointer_type(p: &mut Parser) -> Option { - if p.peek_kind() != PREFIX_OPERATOR || p.peek_text() != "*" { - p.error("expected `*`"); - return None; - } - - let m = p.start(); - p.advance(); - - if !(p.peek_kind() == NAME && matches!(p.peek_text(), "const" | "mut")) { - p.error("expected `const` or `mut`"); - } else { - p.advance(); - } - - type_(p); - - Some(m.complete(p, POINTER_TYPE)) -} - -fn array_type(p: &mut Parser) -> Option { - if p.peek_kind() != LEFT_BRACKET { - p.error("expected `[`"); - return None; - } - - let m = p.start(); - p.advance(); - type_(p); - p.expect(RIGHT_BRACKET); - Some(m.complete(p, ARRAY_TYPE)) -} - -fn tuple_type(p: &mut Parser) -> Option { - if p.peek_kind() != LEFT_PAREN { - p.error("expected `(`"); - return None; - } - - let m = p.start(); - p.advance(); - - if p.eat(COMMA) { - p.error("expected a type"); - } - - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - type_(p); - - if !p.eat(COMMA) { - if p.at(RIGHT_PAREN) { - break; - } - p.expect(COMMA); - break; - } - } - - p.expect(RIGHT_PAREN); - Some(m.complete(p, TUPLE_TYPE)) -} - -fn function_type(p: &mut Parser) -> Option { - if p.peek_kind() != FUN_KW { - p.error("expected `fun`"); - return None; - } - - let m = p.start(); - p.advance(); - - let input_tuple = p.start(); - if p.at(LEFT_PAREN) { - p.advance(); - while !matches!(p.peek_kind(), RIGHT_PAREN | EOF) { - type_(p); - - if !p.eat(COMMA) { - if p.at(RIGHT_PAREN) { - break; - } - p.expect(COMMA); - break; - } - } - p.expect(RIGHT_PAREN); - } else { - p.error("expected `(`"); - } - input_tuple.complete(p, TUPLE_TYPE); - - if p.at_binary_op("->") { - p.advance(); - } else { - p.error("expected `->`"); - } - - type_(p); - - Some(m.complete(p, FUNCTION_TYPE)) -} - -fn record_type(p: &mut Parser) -> Option { - if p.peek_kind() != LEFT_BRACE { - p.error("expected `{`"); - return None; - } - - let m = p.start(); - p.advance(); - - while !matches!(p.peek_kind(), RIGHT_BRACE | EOF) { - if p.at(COMMA) { - p.error("expected field name"); - p.advance(); - continue; - } - - let field = p.start(); - name(p, &SyntaxSet::new([COLON, COMMA, RIGHT_BRACE])); - p.expect(COLON); - type_(p); - field.complete(p, STRUCT_FIELD); - - if !p.eat(COMMA) { - if p.at(NAME) { - p.expect(COMMA); - } else { - break; - } - } - } - - p.expect(RIGHT_BRACE); - Some(m.complete(p, RECORD_TYPE)) -} diff --git a/crates/mitki-parse/src/lib.rs b/crates/mitki-parse/src/lib.rs deleted file mode 100644 index b52703d..0000000 --- a/crates/mitki-parse/src/lib.rs +++ /dev/null @@ -1,58 +0,0 @@ -use mitki_errors::Diagnostic; -use mitki_inputs::File; -use mitki_yellow::ast::{self, Node as _}; -use mitki_yellow::{SyntaxNode, SyntaxTree}; - -mod grammar; -mod parser; -#[cfg(test)] -mod tests; - -#[derive(salsa::Update)] -pub struct Parsed { - root: SyntaxTree, - diagnostics: Vec, -} - -impl std::fmt::Debug for Parsed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Parsed").field("root", &self.root).finish_non_exhaustive() - } -} - -impl PartialEq for Parsed { - fn eq(&self, other: &Self) -> bool { - self.root.text() == other.root.text() - } -} - -impl Eq for Parsed {} - -impl Parsed { - pub fn syntax_node(&self) -> SyntaxNode<'_> { - self.root.root() - } - - pub fn tree(&self) -> ast::Module<'_> { - ast::Module::cast(self.syntax_node()).unwrap() - } - - pub fn diagnostics(&self) -> &[Diagnostic] { - &self.diagnostics - } -} - -pub trait FileParse { - fn parse(self, db: &dyn salsa::Database) -> &Parsed; -} - -#[salsa::tracked] -impl FileParse for File { - #[salsa::tracked(returns(ref))] - fn parse(self, db: &dyn salsa::Database) -> Parsed { - let mut parser = parser::Parser::new(self.text(db)); - grammar::items::module(&mut parser); - let (root, diagnostics) = parser.build_tree(); - Parsed { root, diagnostics } - } -} diff --git a/crates/mitki-parse/src/parser.rs b/crates/mitki-parse/src/parser.rs deleted file mode 100644 index 8c4a441..0000000 --- a/crates/mitki-parse/src/parser.rs +++ /dev/null @@ -1,345 +0,0 @@ -#[cfg(test)] -use std::fmt::Write as _; - -use drop_bomb::DropBomb; -use mitki_errors::Diagnostic; -use mitki_tokenizer::{TokenIndex, Tokenizer}; -use mitki_yellow::SyntaxKind::{self, *}; -use mitki_yellow::{Builder, SyntaxSet, SyntaxTree}; -use text_size::TextRange; - -pub(crate) struct Parser<'text> { - text: &'text str, - tokenizer: Tokenizer, - events: Vec, - diagnostics: Vec, - previous_range: TextRange, -} - -impl<'text> Parser<'text> { - pub(crate) fn new(text: &'text str) -> Self { - Self { - text, - tokenizer: Tokenizer::new(text), - events: Vec::new(), - diagnostics: Vec::new(), - previous_range: TextRange::default(), - } - } - - pub(crate) fn try_parse(&mut self, parser: impl FnOnce(&mut Self) -> bool) { - let prev_events = self.events.len(); - let prev_diagnostics = self.diagnostics.len(); - let prev_tokenizer = self.tokenizer.clone(); - let prev_range = self.previous_range; - - if !parser(self) { - self.events.truncate(prev_events); - self.diagnostics.truncate(prev_diagnostics); - self.tokenizer = prev_tokenizer; - self.previous_range = prev_range; - } - } - - pub(crate) fn peek_kind(&self) -> SyntaxKind { - self.tokenizer.peek().kind - } - - pub(crate) fn peek_range(&self) -> TextRange { - self.tokenizer.peek().kind_range - } - - pub(crate) fn next_token_on_same_line(&self) -> bool { - self.tokenizer.peek().on_same_line() - } - - pub(crate) fn peek_text(&self) -> &'text str { - &self.text[self.peek_range()] - } - - pub(crate) fn nth_kind(&self, n: usize) -> SyntaxKind { - let mut tokenizer = self.tokenizer.clone(); - for _ in 0..n { - let _ = tokenizer.next_token(); - } - tokenizer.peek().kind - } - - pub(crate) fn nth_text(&self, n: usize) -> &'text str { - let mut tokenizer = self.tokenizer.clone(); - for _ in 0..n { - let _ = tokenizer.next_token(); - } - let range = tokenizer.peek().kind_range; - &self.text[range] - } - - pub(crate) fn at_binary_op(&self, op: &str) -> bool { - self.peek_kind() == BINARY_OPERATOR && self.peek_text() == op - } - - pub(crate) fn advance(&mut self) { - if self.peek_kind() == EOF { - return; - } - - let token_index = self.tokenizer.next_token_index(); - let token = self.tokenizer.token(token_index); - self.previous_range = token.kind_range; - self.events.push(Event::Token(token_index)); - } - - pub(crate) fn at(&mut self, kind: SyntaxKind) -> bool { - self.peek_kind() == kind - } - - pub(crate) fn eat(&mut self, kind: SyntaxKind) -> bool { - let is_present = self.at(kind); - - if is_present { - self.advance(); - } - - is_present - } - - pub(crate) fn expect(&mut self, kind: SyntaxKind) -> bool { - let is_present = self.at(kind); - - if self.at(kind) { - self.advance(); - } else { - self.error(&format!("expected {kind}")); - } - - is_present - } - - pub(crate) fn error_and_bump(&mut self, message: &str) { - self.error_recover(message, &SyntaxSet::EMPTY); - } - - pub(crate) fn error_recover(&mut self, message: &str, recovery: &SyntaxSet) { - if recovery.contains(self.peek_kind()) { - self.error(message); - return; - } - - let m = self.start(); - self.error(message); - self.advance(); - m.complete(self, ERROR); - } - - pub(crate) fn start(&mut self) -> Marker { - let pos = self.events.len() as u32; - self.events.push(Event::Start { kind: TOMBSTONE, forward_parent: None }); - Marker::new(pos) - } - - pub(crate) fn error_with_range(&mut self, message: &str, range: TextRange) { - if self.diagnostics.last().is_some_and(|last| last.range().start() == range.start()) { - return; - } - - self.diagnostics.push(Diagnostic::error(message, range)); - } - - pub(crate) fn error(&mut self, message: &str) { - let range = self.tokenizer.peek().kind_range; - self.error_with_range(message, range); - } - - pub(crate) fn build_tree(self) -> (SyntaxTree, Vec) { - let Parser { text, tokenizer, mut events, mut diagnostics, .. } = self; - let mut builder = Builder::new(text); - let mut forward_parents = Vec::new(); - - for i in 0..events.len() { - match std::mem::replace(&mut events[i], Event::TOMBSTONE) { - Event::Start { kind, forward_parent } => { - if kind == TOMBSTONE { - continue; - } - - collect_forward_parents( - &mut events, - i, - kind, - forward_parent, - &mut forward_parents, - ); - - for kind in forward_parents.drain(..).rev() { - builder.start_node(kind); - } - } - Event::Finish => builder.finish_node(), - Event::Token(token_index) => { - let token = tokenizer.token(token_index); - let leading = tokenizer.leading_trivia(token_index); - let trailing = tokenizer.trailing_trivia(token_index); - builder.token( - leading.iter().copied(), - token.kind, - token.kind_range.len(), - trailing.iter().copied(), - ); - } - } - } - - extend_with_tokenizer_diagnostics(&mut diagnostics, &tokenizer); - - (builder.finish(), diagnostics) - } - - pub(crate) fn previous_range(&self) -> TextRange { - self.previous_range - } - - #[cfg(test)] - pub(crate) fn debug_tree(self) -> (String, Vec) { - let Parser { text, tokenizer, mut events, mut diagnostics, .. } = self; - let mut output = String::new(); - let mut indent = 0usize; - let mut forward_parents = Vec::new(); - - for i in 0..events.len() { - match std::mem::replace(&mut events[i], Event::TOMBSTONE) { - Event::Start { kind, forward_parent } => { - if kind == TOMBSTONE { - continue; - } - - collect_forward_parents( - &mut events, - i, - kind, - forward_parent, - &mut forward_parents, - ); - - for kind in forward_parents.drain(..).rev() { - let indent_str = " ".repeat(indent); - let _ = writeln!(output, "{indent_str}{kind:?}"); - indent += 1; - } - } - Event::Finish => { - indent -= 1; - } - Event::Token(token_index) => { - let token = tokenizer.token(token_index); - if token.kind.is_trivia() { - continue; - } - let token_text = &text[token.kind_range]; - let indent_str = " ".repeat(indent); - let _ = writeln!(output, "{indent_str}{:?}: {:?}", token.kind, token_text); - } - } - } - - extend_with_tokenizer_diagnostics(&mut diagnostics, &tokenizer); - - (output, diagnostics) - } -} - -fn collect_forward_parents( - events: &mut [Event], - start_index: usize, - kind: SyntaxKind, - forward_parent: Option, - forward_parents: &mut Vec, -) { - forward_parents.push(kind); - let mut idx = start_index; - let mut fp = forward_parent; - - while let Some(fwd) = fp { - idx += fwd as usize; - - fp = match std::mem::replace(&mut events[idx], Event::TOMBSTONE) { - Event::Start { kind, forward_parent, .. } => { - if kind != TOMBSTONE { - forward_parents.push(kind); - } - forward_parent - } - _ => unreachable!(), - }; - } -} - -fn extend_with_tokenizer_diagnostics(diagnostics: &mut Vec, tokenizer: &Tokenizer) { - diagnostics.extend(tokenizer.diagnostics().iter().map(|diagnostic| match diagnostic { - mitki_tokenizer::Diagnostic::InconsistentWhitespaceAroundEqual(range) => { - Diagnostic::error("Consistent whitespace required around '='", *range) - } - })); -} - -enum Event { - Start { kind: SyntaxKind, forward_parent: Option }, - Token(TokenIndex), - Finish, -} - -impl Event { - const TOMBSTONE: Self = Self::Start { kind: TOMBSTONE, forward_parent: None }; -} - -pub(crate) struct Marker { - position: u32, - bomb: DropBomb, -} - -impl Marker { - fn new(pos: u32) -> Self { - Self { position: pos, bomb: DropBomb::new("Marker must be either completed or abandoned") } - } - - pub(crate) fn complete(mut self, p: &mut Parser<'_>, kind: SyntaxKind) -> CompletedMarker { - self.bomb.defuse(); - - match &mut p.events[self.position as usize] { - Event::Start { kind: slot, .. } => { - *slot = kind; - } - _ => unreachable!(), - } - - p.events.push(Event::Finish); - CompletedMarker::new(self.position, kind) - } -} - -pub(crate) struct CompletedMarker { - pos: u32, - kind: SyntaxKind, -} - -impl CompletedMarker { - fn new(pos: u32, kind: SyntaxKind) -> Self { - Self { pos, kind } - } - - pub(crate) fn kind(&self) -> SyntaxKind { - self.kind - } - - pub(crate) fn precede(self, p: &mut Parser<'_>) -> Marker { - let new_pos = p.start(); - - match &mut p.events[self.pos as usize] { - Event::Start { forward_parent, .. } => { - *forward_parent = Some(new_pos.position - self.pos); - } - _ => unreachable!(), - } - - new_pos - } -} diff --git a/crates/mitki-parse/src/tests.rs b/crates/mitki-parse/src/tests.rs deleted file mode 100644 index 0e19421..0000000 --- a/crates/mitki-parse/src/tests.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; - -use expect_test::expect_file; -use mitki_tokenizer::Tokenizer; -use mitki_yellow::SyntaxKind; - -use crate::grammar; -use crate::parser::Parser; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct TestCase { - input: PathBuf, - expected: PathBuf, - text: String, -} - -impl TestCase { - fn list() -> Vec { - let test_data_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data"); - - let mut cases = fs::read_dir(&test_data_dir) - .unwrap_or_else(|err| { - panic!("Cannot read directory {}: {err}", test_data_dir.display()) - }) - .filter_map(|entry| { - let path = entry.ok()?.path(); - if path.extension()? == "mitki" { - let expected = path.with_extension("ir"); - let text = fs::read_to_string(&path).ok()?; - Some(Self { input: path, expected, text }) - } else { - None - } - }) - .collect::>(); - - cases.sort(); - cases - } -} - -#[test] -fn parse() { - let test_cases = TestCase::list(); - - for case in test_cases { - let mut parser = Parser::new(&case.text); - grammar::items::module(&mut parser); - let (tree, diagnostics) = parser.debug_tree(); - let diagnostics = - diagnostics.iter().map(|d| format!(" {}", d.message())).collect::>().join("\n"); - - let actual = format!("{tree}\nErrors:\n{diagnostics}"); - expect_file![&case.expected].assert_eq(&actual); - } -} - -#[test] -fn trivia_tokens_are_materialized() { - let text = r#" -fun main() { - val x = 1 // comment -} -"#; - let mut saw_trivia = false; - let mut saw_non_trivia = false; - let mut tokenizer = Tokenizer::new(text); - - loop { - let token_index = tokenizer.next_token_index(); - let token = tokenizer.token(token_index); - - if token.kind != SyntaxKind::EOF && !token.kind.is_trivia() { - saw_non_trivia = true; - } - - if !tokenizer.leading_trivia(token_index).is_empty() - || !tokenizer.trailing_trivia(token_index).is_empty() - { - saw_trivia = true; - } - - if token.kind == SyntaxKind::EOF { - break; - } - } - - assert!(saw_trivia, "expected trivia pieces in the tokenizer"); - assert!(saw_non_trivia, "expected non-trivia tokens in the stream"); -} diff --git a/crates/mitki-parse/test_data/anon_record_expr.ir b/crates/mitki-parse/test_data/anon_record_expr.ir deleted file mode 100644 index 1be5ae0..0000000 --- a/crates/mitki-parse/test_data/anon_record_expr.ir +++ /dev/null @@ -1,30 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - STRUCT_EXPR - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_EXPR_FIELD - IDENT - NAME: "x" - COLON: ":" - LITERAL - INT_NUMBER: "1" - COMMA: "," - STRUCT_EXPR_FIELD - IDENT - NAME: "y" - COLON: ":" - LITERAL - INT_NUMBER: "2" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/anon_record_expr.mitki b/crates/mitki-parse/test_data/anon_record_expr.mitki deleted file mode 100644 index f1bbfb1..0000000 --- a/crates/mitki-parse/test_data/anon_record_expr.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - { x: 1, y: 2 } -} diff --git a/crates/mitki-parse/test_data/anon_record_single_field.ir b/crates/mitki-parse/test_data/anon_record_single_field.ir deleted file mode 100644 index 71ef858..0000000 --- a/crates/mitki-parse/test_data/anon_record_single_field.ir +++ /dev/null @@ -1,23 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - STRUCT_EXPR - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_EXPR_FIELD - IDENT - NAME: "x" - COLON: ":" - LITERAL - INT_NUMBER: "1" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/anon_record_single_field.mitki b/crates/mitki-parse/test_data/anon_record_single_field.mitki deleted file mode 100644 index 711c8af..0000000 --- a/crates/mitki-parse/test_data/anon_record_single_field.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - { x: 1 } -} diff --git a/crates/mitki-parse/test_data/array_expr.ir b/crates/mitki-parse/test_data/array_expr.ir deleted file mode 100644 index e1a44c8..0000000 --- a/crates/mitki-parse/test_data/array_expr.ir +++ /dev/null @@ -1,38 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - ARRAY_EXPR - LEFT_BRACKET: "[" - RIGHT_BRACKET: "]" - ARRAY_EXPR - LEFT_BRACKET: "[" - LITERAL - INT_NUMBER: "1" - RIGHT_BRACKET: "]" - ARRAY_EXPR - LEFT_BRACKET: "[" - LITERAL - INT_NUMBER: "1" - COMMA: "," - LITERAL - INT_NUMBER: "2" - COMMA: "," - RIGHT_BRACKET: "]" - ARRAY_EXPR - LEFT_BRACKET: "[" - LITERAL - INT_NUMBER: "1" - SEMICOLON: ";" - LITERAL - INT_NUMBER: "2" - RIGHT_BRACKET: "]" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/array_expr.mitki b/crates/mitki-parse/test_data/array_expr.mitki deleted file mode 100644 index 4d136c3..0000000 --- a/crates/mitki-parse/test_data/array_expr.mitki +++ /dev/null @@ -1,6 +0,0 @@ -fun foo() { - [] - [1] - [1, 2,] - [1; 2] -} diff --git a/crates/mitki-parse/test_data/call_expr.ir b/crates/mitki-parse/test_data/call_expr.ir deleted file mode 100644 index cc39f40..0000000 --- a/crates/mitki-parse/test_data/call_expr.ir +++ /dev/null @@ -1,43 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "f" - ARG_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - CALL_EXPR - CALL_EXPR - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "f" - ARG_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - ARG_LIST - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "1" - RIGHT_PAREN: ")" - ARG_LIST - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "1" - COMMA: "," - LITERAL - INT_NUMBER: "2" - COMMA: "," - RIGHT_PAREN: ")" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/call_expr.mitki b/crates/mitki-parse/test_data/call_expr.mitki deleted file mode 100644 index 32c32e8..0000000 --- a/crates/mitki-parse/test_data/call_expr.mitki +++ /dev/null @@ -1,4 +0,0 @@ -fun main() { - f() - f()(1)(1, 2,) -} diff --git a/crates/mitki-parse/test_data/char_literal.ir b/crates/mitki-parse/test_data/char_literal.ir deleted file mode 100644 index f37001f..0000000 --- a/crates/mitki-parse/test_data/char_literal.ir +++ /dev/null @@ -1,15 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - LITERAL - CHAR: "'a'" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/char_literal.mitki b/crates/mitki-parse/test_data/char_literal.mitki deleted file mode 100644 index 5d688cb..0000000 --- a/crates/mitki-parse/test_data/char_literal.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - 'a' -} diff --git a/crates/mitki-parse/test_data/closure_params.ir b/crates/mitki-parse/test_data/closure_params.ir deleted file mode 100644 index a3e4617..0000000 --- a/crates/mitki-parse/test_data/closure_params.ir +++ /dev/null @@ -1,39 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "foo" - EQ: "=" - CLOSURE_EXPR - LEFT_BRACE: "{" - PARAM_LIST - PARAM - BINDING_PATTERN - IDENT - NAME: "bar" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "baz" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "qux" - IN_KW: "in" - STMT_LIST - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/closure_params.mitki b/crates/mitki-parse/test_data/closure_params.mitki deleted file mode 100644 index 0ea7e9e..0000000 --- a/crates/mitki-parse/test_data/closure_params.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - val foo = { bar, baz, qux in } -} diff --git a/crates/mitki-parse/test_data/comptime_function.ir b/crates/mitki-parse/test_data/comptime_function.ir deleted file mode 100644 index 25e4e3d..0000000 --- a/crates/mitki-parse/test_data/comptime_function.ir +++ /dev/null @@ -1,28 +0,0 @@ -MODULE - FN - NAME: "comptime" - FUN_KW: "fun" - IDENT - NAME: "reflect" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "str" - STMT_LIST - LEFT_BRACE: "{" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "type_name" - ARG_LIST - LEFT_PAREN: "(" - PATH_EXPR - NAME_REF - NAME: "int" - RIGHT_PAREN: ")" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/comptime_function.mitki b/crates/mitki-parse/test_data/comptime_function.mitki deleted file mode 100644 index 28b6b67..0000000 --- a/crates/mitki-parse/test_data/comptime_function.mitki +++ /dev/null @@ -1,3 +0,0 @@ -comptime fun reflect(): str { - type_name(int) -} diff --git a/crates/mitki-parse/test_data/curly_in_params.ir b/crates/mitki-parse/test_data/curly_in_params.ir deleted file mode 100644 index 7da878f..0000000 --- a/crates/mitki-parse/test_data/curly_in_params.ir +++ /dev/null @@ -1,15 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - ERROR - RIGHT_BRACE: "}" - RIGHT_PAREN: ")" - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - -Errors: - expected parameter pattern \ No newline at end of file diff --git a/crates/mitki-parse/test_data/curly_in_params.mitki b/crates/mitki-parse/test_data/curly_in_params.mitki deleted file mode 100644 index 92ddf96..0000000 --- a/crates/mitki-parse/test_data/curly_in_params.mitki +++ /dev/null @@ -1 +0,0 @@ -fun foo(}) {} diff --git a/crates/mitki-parse/test_data/destructor_member.ir b/crates/mitki-parse/test_data/destructor_member.ir deleted file mode 100644 index a4c4773..0000000 --- a/crates/mitki-parse/test_data/destructor_member.ir +++ /dev/null @@ -1,85 +0,0 @@ -MODULE - STRUCT_DEF - STRUCT_KW: "struct" - IDENT - NAME: "Vec" - STRUCT_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_FIELD - IDENT - NAME: "ptr" - COLON: ":" - POINTER_TYPE - PREFIX_OPERATOR: "*" - NAME: "mut" - PATH_TYPE - NAME: "int" - COMMA: "," - STRUCT_FIELD - IDENT - NAME: "cap" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - DESTRUCTOR_DEF - NAME: "drop" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - VAR_KW: "var" - BINDING_PATTERN - IDENT - NAME: "self" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - FIELD_EXPR - PATH_EXPR - NAME_REF - NAME: "self" - DOT: "." - NAME_REF - NAME: "cap" - RIGHT_BRACE: "}" - COMMA: "," - RIGHT_BRACE: "}" - ENUM_DEF - ENUM_KW: "enum" - IDENT - NAME: "Choice" - ENUM_VARIANT_LIST - LEFT_BRACE: "{" - ENUM_VARIANT - IDENT - NAME: "Some" - TUPLE_TYPE - LEFT_PAREN: "(" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - COMMA: "," - ENUM_VARIANT - IDENT - NAME: "None" - COMMA: "," - DESTRUCTOR_DEF - NAME: "drop" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - VAR_KW: "var" - BINDING_PATTERN - IDENT - NAME: "self" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - PATH_EXPR - NAME_REF - NAME: "self" - RIGHT_BRACE: "}" - COMMA: "," - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/destructor_member.mitki b/crates/mitki-parse/test_data/destructor_member.mitki deleted file mode 100644 index 71a820a..0000000 --- a/crates/mitki-parse/test_data/destructor_member.mitki +++ /dev/null @@ -1,15 +0,0 @@ -struct Vec { - ptr: *mut int, - cap: int, - drop(var self) { - self.cap - }, -} - -enum Choice { - Some(int), - None, - drop(var self) { - self - }, -} diff --git a/crates/mitki-parse/test_data/empty.ir b/crates/mitki-parse/test_data/empty.ir deleted file mode 100644 index 6769a9a..0000000 --- a/crates/mitki-parse/test_data/empty.ir +++ /dev/null @@ -1,3 +0,0 @@ -MODULE - -Errors: diff --git a/crates/mitki-parse/test_data/empty.mitki b/crates/mitki-parse/test_data/empty.mitki deleted file mode 100644 index e69de29..0000000 diff --git a/crates/mitki-parse/test_data/empty_braces_closure.ir b/crates/mitki-parse/test_data/empty_braces_closure.ir deleted file mode 100644 index 03c3bf6..0000000 --- a/crates/mitki-parse/test_data/empty_braces_closure.ir +++ /dev/null @@ -1,17 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - CLOSURE_EXPR - LEFT_BRACE: "{" - STMT_LIST - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/empty_braces_closure.mitki b/crates/mitki-parse/test_data/empty_braces_closure.mitki deleted file mode 100644 index d6c9bcb..0000000 --- a/crates/mitki-parse/test_data/empty_braces_closure.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - {} -} diff --git a/crates/mitki-parse/test_data/enum_def.ir b/crates/mitki-parse/test_data/enum_def.ir deleted file mode 100644 index cfb388a..0000000 --- a/crates/mitki-parse/test_data/enum_def.ir +++ /dev/null @@ -1,22 +0,0 @@ -MODULE - ENUM_DEF - ENUM_KW: "enum" - IDENT - NAME: "Color" - ENUM_VARIANT_LIST - LEFT_BRACE: "{" - ENUM_VARIANT - IDENT - NAME: "Red" - COMMA: "," - ENUM_VARIANT - IDENT - NAME: "Green" - COMMA: "," - ENUM_VARIANT - IDENT - NAME: "Blue" - COMMA: "," - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/enum_def.mitki b/crates/mitki-parse/test_data/enum_def.mitki deleted file mode 100644 index b6d6292..0000000 --- a/crates/mitki-parse/test_data/enum_def.mitki +++ /dev/null @@ -1,5 +0,0 @@ -enum Color { - Red, - Green, - Blue, -} diff --git a/crates/mitki-parse/test_data/enum_with_data.ir b/crates/mitki-parse/test_data/enum_with_data.ir deleted file mode 100644 index e0fe0aa..0000000 --- a/crates/mitki-parse/test_data/enum_with_data.ir +++ /dev/null @@ -1,23 +0,0 @@ -MODULE - ENUM_DEF - ENUM_KW: "enum" - IDENT - NAME: "Option" - ENUM_VARIANT_LIST - LEFT_BRACE: "{" - ENUM_VARIANT - IDENT - NAME: "Some" - TUPLE_TYPE - LEFT_PAREN: "(" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - COMMA: "," - ENUM_VARIANT - IDENT - NAME: "None" - COMMA: "," - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/enum_with_data.mitki b/crates/mitki-parse/test_data/enum_with_data.mitki deleted file mode 100644 index dc9af22..0000000 --- a/crates/mitki-parse/test_data/enum_with_data.mitki +++ /dev/null @@ -1,4 +0,0 @@ -enum Option { - Some(int), - None, -} diff --git a/crates/mitki-parse/test_data/field_expr.ir b/crates/mitki-parse/test_data/field_expr.ir deleted file mode 100644 index 435dee9..0000000 --- a/crates/mitki-parse/test_data/field_expr.ir +++ /dev/null @@ -1,27 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "n" - EQ: "=" - FIELD_EXPR - PATH_EXPR - NAME_REF - NAME: "Color" - DOT: "." - NAME_REF - NAME: "Red" - SEMICOLON: ";" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/field_expr.mitki b/crates/mitki-parse/test_data/field_expr.mitki deleted file mode 100644 index 4fb1b90..0000000 --- a/crates/mitki-parse/test_data/field_expr.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - val n = Color.Red; -} diff --git a/crates/mitki-parse/test_data/function.ir b/crates/mitki-parse/test_data/function.ir deleted file mode 100644 index 8daa9f4..0000000 --- a/crates/mitki-parse/test_data/function.ir +++ /dev/null @@ -1,15 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - LITERAL - INT_NUMBER: "42" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/function.mitki b/crates/mitki-parse/test_data/function.mitki deleted file mode 100644 index 4d2c8f0..0000000 --- a/crates/mitki-parse/test_data/function.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - 42 -} diff --git a/crates/mitki-parse/test_data/function_ret_type.ir b/crates/mitki-parse/test_data/function_ret_type.ir deleted file mode 100644 index 9218803..0000000 --- a/crates/mitki-parse/test_data/function_ret_type.ir +++ /dev/null @@ -1,27 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "bar" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "u32" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/function_ret_type.mitki b/crates/mitki-parse/test_data/function_ret_type.mitki deleted file mode 100644 index c828f53..0000000 --- a/crates/mitki-parse/test_data/function_ret_type.mitki +++ /dev/null @@ -1,2 +0,0 @@ -fun foo() {} -fun bar(): u32 {} diff --git a/crates/mitki-parse/test_data/generic_param_list.ir b/crates/mitki-parse/test_data/generic_param_list.ir deleted file mode 100644 index 520a196..0000000 --- a/crates/mitki-parse/test_data/generic_param_list.ir +++ /dev/null @@ -1,44 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - LEFT_BRACKET: "[" - RIGHT_BRACKET: "]" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "main" - LEFT_BRACKET: "[" - TYPE_PARAM - NAME: "T" - RIGHT_BRACKET: "]" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "main" - LEFT_BRACKET: "[" - ERROR - COMMA: "," - RIGHT_BRACKET: "]" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - -Errors: - expected generic parameter \ No newline at end of file diff --git a/crates/mitki-parse/test_data/generic_param_list.mitki b/crates/mitki-parse/test_data/generic_param_list.mitki deleted file mode 100644 index 763de62..0000000 --- a/crates/mitki-parse/test_data/generic_param_list.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main[]() {} -fun main[T]() {} -fun main[,]() {} diff --git a/crates/mitki-parse/test_data/generic_type_path.ir b/crates/mitki-parse/test_data/generic_type_path.ir deleted file mode 100644 index 8642203..0000000 --- a/crates/mitki-parse/test_data/generic_type_path.ir +++ /dev/null @@ -1,58 +0,0 @@ -MODULE - STRUCT_DEF - STRUCT_KW: "struct" - IDENT - NAME: "Vec" - LEFT_BRACKET: "[" - TYPE_PARAM - NAME: "T" - RIGHT_BRACKET: "]" - STRUCT_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_FIELD - IDENT - NAME: "items" - COLON: ":" - ARRAY_TYPE - LEFT_BRACKET: "[" - PATH_TYPE - NAME: "T" - RIGHT_BRACKET: "]" - COMMA: "," - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "take" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "xs" - COLON: ":" - PATH_TYPE - NAME: "Vec" - GENERIC_ARG_LIST - LEFT_BRACKET: "[" - PATH_TYPE - NAME: "int" - RIGHT_BRACKET: "]" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "Vec" - GENERIC_ARG_LIST - LEFT_BRACKET: "[" - PATH_TYPE - NAME: "int" - RIGHT_BRACKET: "]" - STMT_LIST - LEFT_BRACE: "{" - PATH_EXPR - NAME_REF - NAME: "xs" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/generic_type_path.mitki b/crates/mitki-parse/test_data/generic_type_path.mitki deleted file mode 100644 index f3b13f2..0000000 --- a/crates/mitki-parse/test_data/generic_type_path.mitki +++ /dev/null @@ -1,7 +0,0 @@ -struct Vec[T] { - items: [T], -} - -fun take(xs: Vec[int]): Vec[int] { - xs -} diff --git a/crates/mitki-parse/test_data/if_block_after_path.ir b/crates/mitki-parse/test_data/if_block_after_path.ir deleted file mode 100644 index 34493e4..0000000 --- a/crates/mitki-parse/test_data/if_block_after_path.ir +++ /dev/null @@ -1,31 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - IF_EXPR - IF_KW: "if" - PATH_EXPR - NAME_REF - NAME: "b" - STMT_LIST - LEFT_BRACE: "{" - PATH_EXPR - NAME_REF - NAME: "object1" - RIGHT_BRACE: "}" - ELSE_KW: "else" - STMT_LIST - LEFT_BRACE: "{" - PATH_EXPR - NAME_REF - NAME: "object2" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/if_block_after_path.mitki b/crates/mitki-parse/test_data/if_block_after_path.mitki deleted file mode 100644 index 9f8d83e..0000000 --- a/crates/mitki-parse/test_data/if_block_after_path.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - if b { object1 } else { object2 } -} diff --git a/crates/mitki-parse/test_data/if_expr.ir b/crates/mitki-parse/test_data/if_expr.ir deleted file mode 100644 index 43eae6f..0000000 --- a/crates/mitki-parse/test_data/if_expr.ir +++ /dev/null @@ -1,50 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - IF_EXPR - IF_KW: "if" - LITERAL - TRUE_KW: "true" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - IF_EXPR - IF_KW: "if" - LITERAL - TRUE_KW: "true" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - ELSE_KW: "else" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - IF_EXPR - IF_KW: "if" - LITERAL - TRUE_KW: "true" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - ELSE_KW: "else" - IF_EXPR - IF_KW: "if" - LITERAL - FALSE_KW: "false" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - ELSE_KW: "else" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/if_expr.mitki b/crates/mitki-parse/test_data/if_expr.mitki deleted file mode 100644 index f52e5c9..0000000 --- a/crates/mitki-parse/test_data/if_expr.mitki +++ /dev/null @@ -1,5 +0,0 @@ -fun foo() { - if true {} - if true {} else {} - if true {} else if false {} else {} -} diff --git a/crates/mitki-parse/test_data/instance_item.ir b/crates/mitki-parse/test_data/instance_item.ir deleted file mode 100644 index 4881aa1..0000000 --- a/crates/mitki-parse/test_data/instance_item.ir +++ /dev/null @@ -1,25 +0,0 @@ -MODULE - INSTANCE_ITEM - NAME: "export" - NAME: "instance" - IDENT - NAME: "id" - GENERIC_ARG_LIST - LEFT_BRACKET: "[" - PATH_TYPE - NAME: "int" - RIGHT_BRACKET: "]" - SEMICOLON: ";" - INSTANCE_ITEM - NAME: "import" - NAME: "instance" - IDENT - NAME: "round_trip" - GENERIC_ARG_LIST - LEFT_BRACKET: "[" - PATH_TYPE - NAME: "str" - RIGHT_BRACKET: "]" - SEMICOLON: ";" - -Errors: diff --git a/crates/mitki-parse/test_data/instance_item.mitki b/crates/mitki-parse/test_data/instance_item.mitki deleted file mode 100644 index 329cce2..0000000 --- a/crates/mitki-parse/test_data/instance_item.mitki +++ /dev/null @@ -1,2 +0,0 @@ -export instance id[int]; -import instance round_trip[str]; diff --git a/crates/mitki-parse/test_data/loop_control.ir b/crates/mitki-parse/test_data/loop_control.ir deleted file mode 100644 index 68ce67d..0000000 --- a/crates/mitki-parse/test_data/loop_control.ir +++ /dev/null @@ -1,33 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - LOOP_EXPR - LOOP_KW: "loop" - STMT_LIST - LEFT_BRACE: "{" - IF_EXPR - IF_KW: "if" - LITERAL - TRUE_KW: "true" - STMT_LIST - LEFT_BRACE: "{" - CONTINUE_EXPR - CONTINUE_KW: "continue" - RIGHT_BRACE: "}" - ELSE_KW: "else" - STMT_LIST - LEFT_BRACE: "{" - BREAK_EXPR - BREAK_KW: "break" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/loop_control.mitki b/crates/mitki-parse/test_data/loop_control.mitki deleted file mode 100644 index 9b92d3a..0000000 --- a/crates/mitki-parse/test_data/loop_control.mitki +++ /dev/null @@ -1,9 +0,0 @@ -fun main() { - loop { - if true { - continue - } else { - break - } - } -} diff --git a/crates/mitki-parse/test_data/loop_expr.ir b/crates/mitki-parse/test_data/loop_expr.ir deleted file mode 100644 index 016d52d..0000000 --- a/crates/mitki-parse/test_data/loop_expr.ir +++ /dev/null @@ -1,18 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - LOOP_EXPR - LOOP_KW: "loop" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/loop_expr.mitki b/crates/mitki-parse/test_data/loop_expr.mitki deleted file mode 100644 index 2958212..0000000 --- a/crates/mitki-parse/test_data/loop_expr.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun foo() { - loop {} -} diff --git a/crates/mitki-parse/test_data/match_expr.ir b/crates/mitki-parse/test_data/match_expr.ir deleted file mode 100644 index 9ca269a..0000000 --- a/crates/mitki-parse/test_data/match_expr.ir +++ /dev/null @@ -1,185 +0,0 @@ -MODULE - ENUM_DEF - ENUM_KW: "enum" - IDENT - NAME: "Option" - ENUM_VARIANT_LIST - LEFT_BRACE: "{" - ENUM_VARIANT - IDENT - NAME: "Some" - TUPLE_TYPE - LEFT_PAREN: "(" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - COMMA: "," - ENUM_VARIANT - IDENT - NAME: "None" - COMMA: "," - RIGHT_BRACE: "}" - STRUCT_DEF - STRUCT_KW: "struct" - IDENT - NAME: "Point" - STRUCT_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_FIELD - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - STRUCT_FIELD - IDENT - NAME: "y" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "value" - COLON: ":" - PATH_TYPE - NAME: "Option" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "point" - COLON: ":" - PATH_TYPE - NAME: "Point" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "int" - STMT_LIST - LEFT_BRACE: "{" - MATCH_EXPR - MATCH_KW: "match" - PATH_EXPR - NAME_REF - NAME: "value" - LEFT_BRACE: "{" - MATCH_ARM - VARIANT_PATTERN - FIELD_PATTERN - DOT: "." - IDENT - NAME: "Some" - LEFT_PAREN: "(" - PAREN_PATTERN - LEFT_PAREN: "(" - LITERAL_PATTERN - INT_NUMBER: "1" - RIGHT_PAREN: ")" - RIGHT_PAREN: ")" - FAT_ARROW: "=>" - LITERAL - INT_NUMBER: "1" - COMMA: "," - MATCH_ARM - VARIANT_PATTERN - FIELD_PATTERN - PATH_PATTERN - IDENT - NAME: "Option" - DOT: "." - IDENT - NAME: "Some" - LEFT_PAREN: "(" - BINDING_PATTERN - IDENT - NAME: "n" - RIGHT_PAREN: ")" - FAT_ARROW: "=>" - MATCH_EXPR - MATCH_KW: "match" - PATH_EXPR - NAME_REF - NAME: "point" - LEFT_BRACE: "{" - MATCH_ARM - STRUCT_PATTERN - PATH_PATTERN - IDENT - NAME: "Point" - LEFT_BRACE: "{" - STRUCT_PATTERN_FIELD - IDENT - NAME: "x" - COMMA: "," - STRUCT_PATTERN_FIELD - IDENT - NAME: "y" - COLON: ":" - LITERAL_PATTERN - INT_NUMBER: "22" - RIGHT_BRACE: "}" - FAT_ARROW: "=>" - BIN_OP_SEQ - PATH_EXPR - NAME_REF - NAME: "x" - BINARY_OPERATOR: "+" - PATH_EXPR - NAME_REF - NAME: "n" - COMMA: "," - MATCH_ARM - STRUCT_PATTERN - PATH_PATTERN - IDENT - NAME: "Point" - LEFT_BRACE: "{" - STRUCT_PATTERN_FIELD - IDENT - NAME: "x" - COLON: ":" - BINDING_PATTERN - IDENT - NAME: "a" - COMMA: "," - STRUCT_PATTERN_FIELD - IDENT - NAME: "y" - RIGHT_BRACE: "}" - FAT_ARROW: "=>" - BIN_OP_SEQ - PATH_EXPR - NAME_REF - NAME: "a" - BINARY_OPERATOR: "+" - PATH_EXPR - NAME_REF - NAME: "y" - COMMA: "," - RIGHT_BRACE: "}" - COMMA: "," - MATCH_ARM - VARIANT_PATTERN - FIELD_PATTERN - DOT: "." - IDENT - NAME: "None" - FAT_ARROW: "=>" - LITERAL - INT_NUMBER: "0" - COMMA: "," - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/match_expr.mitki b/crates/mitki-parse/test_data/match_expr.mitki deleted file mode 100644 index 3719566..0000000 --- a/crates/mitki-parse/test_data/match_expr.mitki +++ /dev/null @@ -1,20 +0,0 @@ -enum Option { - Some(int), - None, -} - -struct Point { - x: int, - y: int, -} - -fun main(value: Option, point: Point): int { - match value { - .Some((1)) => 1, - Option.Some(n) => match point { - Point { x, y: 22 } => x + n, - Point { x: a, y } => a + y, - }, - .None => 0, - } -} diff --git a/crates/mitki-parse/test_data/match_malformed.ir b/crates/mitki-parse/test_data/match_malformed.ir deleted file mode 100644 index af31a9f..0000000 --- a/crates/mitki-parse/test_data/match_malformed.ir +++ /dev/null @@ -1,49 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "value" - COLON: ":" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "int" - STMT_LIST - LEFT_BRACE: "{" - MATCH_EXPR - MATCH_KW: "match" - PATH_EXPR - NAME_REF - NAME: "value" - LEFT_BRACE: "{" - MATCH_ARM - FAT_ARROW: "=>" - LITERAL - INT_NUMBER: "0" - COMMA: "," - MATCH_ARM - TUPLE_PATTERN - LEFT_PAREN: "(" - BINDING_PATTERN - IDENT - NAME: "x" - COMMA: "," - RIGHT_PAREN: ")" - LITERAL - INT_NUMBER: "1" - COMMA: "," - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: - expected pattern - expected `=>` \ No newline at end of file diff --git a/crates/mitki-parse/test_data/match_malformed.mitki b/crates/mitki-parse/test_data/match_malformed.mitki deleted file mode 100644 index 001aab5..0000000 --- a/crates/mitki-parse/test_data/match_malformed.mitki +++ /dev/null @@ -1,6 +0,0 @@ -fun main(value: int): int { - match value { - => 0, - (x,) 1, - } -} diff --git a/crates/mitki-parse/test_data/match_typed_pattern.ir b/crates/mitki-parse/test_data/match_typed_pattern.ir deleted file mode 100644 index 4c4635c..0000000 --- a/crates/mitki-parse/test_data/match_typed_pattern.ir +++ /dev/null @@ -1,119 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "value" - COLON: ":" - UNION_TYPE - PATH_TYPE - NAME: "int" - BINARY_OPERATOR: "|" - PATH_TYPE - NAME: "str" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "pair" - COLON: ":" - TUPLE_TYPE - LEFT_PAREN: "(" - UNION_TYPE - PATH_TYPE - NAME: "int" - BINARY_OPERATOR: "|" - PATH_TYPE - NAME: "str" - COMMA: "," - UNION_TYPE - PATH_TYPE - NAME: "int" - BINARY_OPERATOR: "|" - PATH_TYPE - NAME: "str" - RIGHT_PAREN: ")" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "str" - STMT_LIST - LEFT_BRACE: "{" - EXPR_STMT - MATCH_EXPR - MATCH_KW: "match" - PATH_EXPR - NAME_REF - NAME: "value" - LEFT_BRACE: "{" - MATCH_ARM - TYPED_PATTERN - BINDING_PATTERN - IDENT - NAME: "s" - COLON: ":" - PATH_TYPE - NAME: "str" - FAT_ARROW: "=>" - PATH_EXPR - NAME_REF - NAME: "s" - COMMA: "," - MATCH_ARM - TYPED_PATTERN - WILDCARD_PATTERN - NAME: "_" - COLON: ":" - PATH_TYPE - NAME: "int" - FAT_ARROW: "=>" - LITERAL - STRING: "\"int\"" - COMMA: "," - RIGHT_BRACE: "}" - SEMICOLON: ";" - MATCH_EXPR - MATCH_KW: "match" - PATH_EXPR - NAME_REF - NAME: "pair" - LEFT_BRACE: "{" - MATCH_ARM - TUPLE_PATTERN - LEFT_PAREN: "(" - TYPED_PATTERN - BINDING_PATTERN - IDENT - NAME: "n" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - TYPED_PATTERN - WILDCARD_PATTERN - NAME: "_" - COLON: ":" - PATH_TYPE - NAME: "str" - RIGHT_PAREN: ")" - FAT_ARROW: "=>" - LITERAL - STRING: "\"pair\"" - COMMA: "," - MATCH_ARM - WILDCARD_PATTERN - NAME: "_" - FAT_ARROW: "=>" - LITERAL - STRING: "\"other\"" - COMMA: "," - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/match_typed_pattern.mitki b/crates/mitki-parse/test_data/match_typed_pattern.mitki deleted file mode 100644 index c9dc3b1..0000000 --- a/crates/mitki-parse/test_data/match_typed_pattern.mitki +++ /dev/null @@ -1,10 +0,0 @@ -fun main(value: int | str, pair: (int | str, int | str)): str { - match value { - s: str => s, - _: int => "int", - }; - match pair { - (n: int, _: str) => "pair", - _ => "other", - } -} diff --git a/crates/mitki-parse/test_data/missing_fn_param_type.ir b/crates/mitki-parse/test_data/missing_fn_param_type.ir deleted file mode 100644 index 365f6a9..0000000 --- a/crates/mitki-parse/test_data/missing_fn_param_type.ir +++ /dev/null @@ -1,38 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "f" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "x" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "y" - COLON: ":" - PATH_TYPE - NAME: "i32" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "z" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "t" - COLON: ":" - PATH_TYPE - NAME: "i32" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/missing_fn_param_type.mitki b/crates/mitki-parse/test_data/missing_fn_param_type.mitki deleted file mode 100644 index 2f7c1e9..0000000 --- a/crates/mitki-parse/test_data/missing_fn_param_type.mitki +++ /dev/null @@ -1 +0,0 @@ -fun f(x, y: i32, z, t: i32) {} diff --git a/crates/mitki-parse/test_data/module_item.ir b/crates/mitki-parse/test_data/module_item.ir deleted file mode 100644 index 2437cb3..0000000 --- a/crates/mitki-parse/test_data/module_item.ir +++ /dev/null @@ -1,8 +0,0 @@ -MODULE - MOD_ITEM - MOD_KW: "mod" - IDENT - NAME: "foo" - SEMICOLON: ";" - -Errors: diff --git a/crates/mitki-parse/test_data/module_item.mitki b/crates/mitki-parse/test_data/module_item.mitki deleted file mode 100644 index f4ad3bf..0000000 --- a/crates/mitki-parse/test_data/module_item.mitki +++ /dev/null @@ -1 +0,0 @@ -mod foo; diff --git a/crates/mitki-parse/test_data/module_syntax.ir b/crates/mitki-parse/test_data/module_syntax.ir deleted file mode 100644 index ff4731b..0000000 --- a/crates/mitki-parse/test_data/module_syntax.ir +++ /dev/null @@ -1,33 +0,0 @@ -MODULE - MOD_ITEM - PUB_KW: "pub" - MOD_KW: "mod" - IDENT - NAME: "io" - SEMICOLON: ";" - USE_ITEM - USE_KW: "use" - PATH_EXPR - NAME_REF - NAME: "std" - DOUBLE_COLON: "::" - NAME_REF - NAME: "io" - DOUBLE_COLON: "::" - NAME_REF - NAME: "print_int" - SEMICOLON: ";" - USE_ITEM - USE_KW: "use" - PATH_EXPR - NAME_REF - NAME: "std" - DOUBLE_COLON: "::" - NAME_REF - NAME: "io" - AS_KW: "as" - IDENT - NAME: "io" - SEMICOLON: ";" - -Errors: diff --git a/crates/mitki-parse/test_data/module_syntax.mitki b/crates/mitki-parse/test_data/module_syntax.mitki deleted file mode 100644 index 3d23989..0000000 --- a/crates/mitki-parse/test_data/module_syntax.mitki +++ /dev/null @@ -1,3 +0,0 @@ -pub mod io; -use std::io::print_int; -use std::io as io; diff --git a/crates/mitki-parse/test_data/mutable_assignment.ir b/crates/mitki-parse/test_data/mutable_assignment.ir deleted file mode 100644 index 6beeb31..0000000 --- a/crates/mitki-parse/test_data/mutable_assignment.ir +++ /dev/null @@ -1,50 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "bump" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - VAR_KW: "var" - BINDING_PATTERN - IDENT - NAME: "counter" - COLON: ":" - PATH_TYPE - NAME: "Counter" - COMMA: "," - PARAM - BINDING_PATTERN - IDENT - NAME: "delta" - COLON: ":" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - ASSIGN_STMT - FIELD_EXPR - PATH_EXPR - NAME_REF - NAME: "counter" - DOT: "." - NAME_REF - NAME: "value" - EQ: "=" - BIN_OP_SEQ - FIELD_EXPR - PATH_EXPR - NAME_REF - NAME: "counter" - DOT: "." - NAME_REF - NAME: "value" - BINARY_OPERATOR: "+" - PATH_EXPR - NAME_REF - NAME: "delta" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/mutable_assignment.mitki b/crates/mitki-parse/test_data/mutable_assignment.mitki deleted file mode 100644 index 6920193..0000000 --- a/crates/mitki-parse/test_data/mutable_assignment.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun bump(var counter: Counter, delta: int) { - counter.value = counter.value + delta -} diff --git a/crates/mitki-parse/test_data/mutable_local.ir b/crates/mitki-parse/test_data/mutable_local.ir deleted file mode 100644 index edcaf5d..0000000 --- a/crates/mitki-parse/test_data/mutable_local.ir +++ /dev/null @@ -1,24 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAR_KW: "var" - BINDING_PATTERN - IDENT - NAME: "xs" - EQ: "=" - LITERAL - INT_NUMBER: "1" - PATH_EXPR - NAME_REF - NAME: "xs" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/mutable_local.mitki b/crates/mitki-parse/test_data/mutable_local.mitki deleted file mode 100644 index 02e2c68..0000000 --- a/crates/mitki-parse/test_data/mutable_local.mitki +++ /dev/null @@ -1,4 +0,0 @@ -fun main() { - var xs = 1 - xs -} diff --git a/crates/mitki-parse/test_data/named_struct_empty_fields.ir b/crates/mitki-parse/test_data/named_struct_empty_fields.ir deleted file mode 100644 index f85e1b4..0000000 --- a/crates/mitki-parse/test_data/named_struct_empty_fields.ir +++ /dev/null @@ -1,20 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - STRUCT_EXPR - PATH_EXPR - NAME_REF - NAME: "Point" - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/named_struct_empty_fields.mitki b/crates/mitki-parse/test_data/named_struct_empty_fields.mitki deleted file mode 100644 index 6fa6f6b..0000000 --- a/crates/mitki-parse/test_data/named_struct_empty_fields.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - Point {} -} diff --git a/crates/mitki-parse/test_data/param_list.ir b/crates/mitki-parse/test_data/param_list.ir deleted file mode 100644 index a3be790..0000000 --- a/crates/mitki-parse/test_data/param_list.ir +++ /dev/null @@ -1,48 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "a" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "b" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "i32" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "c" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "i32" - COMMA: "," - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/param_list.mitki b/crates/mitki-parse/test_data/param_list.mitki deleted file mode 100644 index 24851f0..0000000 --- a/crates/mitki-parse/test_data/param_list.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun a() {} -fun b(x: i32) {} -fun c(x: i32, ) {} diff --git a/crates/mitki-parse/test_data/pattern_params.ir b/crates/mitki-parse/test_data/pattern_params.ir deleted file mode 100644 index 6cef089..0000000 --- a/crates/mitki-parse/test_data/pattern_params.ir +++ /dev/null @@ -1,140 +0,0 @@ -MODULE - STRUCT_DEF - STRUCT_KW: "struct" - IDENT - NAME: "Point" - STRUCT_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_FIELD - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - STRUCT_FIELD - IDENT - NAME: "y" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - RIGHT_BRACE: "}" - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - TUPLE_PATTERN - LEFT_PAREN: "(" - BINDING_PATTERN - IDENT - NAME: "lhs" - COMMA: "," - BINDING_PATTERN - IDENT - NAME: "rhs" - RIGHT_PAREN: ")" - COLON: ":" - TUPLE_TYPE - LEFT_PAREN: "(" - PATH_TYPE - NAME: "int" - COMMA: "," - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - COMMA: "," - PARAM - STRUCT_PATTERN - PATH_PATTERN - IDENT - NAME: "Point" - LEFT_BRACE: "{" - STRUCT_PATTERN_FIELD - IDENT - NAME: "x" - COMMA: "," - STRUCT_PATTERN_FIELD - IDENT - NAME: "y" - RIGHT_BRACE: "}" - COLON: ":" - PATH_TYPE - NAME: "Point" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - TUPLE_PATTERN - LEFT_PAREN: "(" - BINDING_PATTERN - IDENT - NAME: "a" - COMMA: "," - WILDCARD_PATTERN - NAME: "_" - RIGHT_PAREN: ")" - EQ: "=" - TUPLE_EXPR - LEFT_PAREN: "(" - PATH_EXPR - NAME_REF - NAME: "lhs" - COMMA: "," - PATH_EXPR - NAME_REF - NAME: "rhs" - RIGHT_PAREN: ")" - VAL_STMT - VAL_KW: "val" - STRUCT_PATTERN - PATH_PATTERN - IDENT - NAME: "Point" - LEFT_BRACE: "{" - STRUCT_PATTERN_FIELD - IDENT - NAME: "x" - COLON: ":" - BINDING_PATTERN - IDENT - NAME: "px" - COMMA: "," - STRUCT_PATTERN_FIELD - IDENT - NAME: "y" - COLON: ":" - BINDING_PATTERN - IDENT - NAME: "py" - RIGHT_BRACE: "}" - EQ: "=" - STRUCT_EXPR - PATH_EXPR - NAME_REF - NAME: "Point" - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_EXPR_FIELD - IDENT - NAME: "x" - COLON: ":" - PATH_EXPR - NAME_REF - NAME: "x" - COMMA: "," - STRUCT_EXPR_FIELD - IDENT - NAME: "y" - COLON: ":" - PATH_EXPR - NAME_REF - NAME: "y" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/pattern_params.mitki b/crates/mitki-parse/test_data/pattern_params.mitki deleted file mode 100644 index 19ba00a..0000000 --- a/crates/mitki-parse/test_data/pattern_params.mitki +++ /dev/null @@ -1,9 +0,0 @@ -struct Point { - x: int, - y: int, -} - -fun main((lhs, rhs): (int, int), Point { x, y }: Point) { - val (a, _) = (lhs, rhs) - val Point { x: px, y: py } = Point { x: x, y: y } -} diff --git a/crates/mitki-parse/test_data/qualified_path.ir b/crates/mitki-parse/test_data/qualified_path.ir deleted file mode 100644 index 3856532..0000000 --- a/crates/mitki-parse/test_data/qualified_path.ir +++ /dev/null @@ -1,43 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "arg" - COLON: ":" - PATH_TYPE - NAME: "crate" - DOUBLE_COLON: "::" - NAME: "Point" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "crate" - DOUBLE_COLON: "::" - NAME: "Result" - STMT_LIST - LEFT_BRACE: "{" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "std" - DOUBLE_COLON: "::" - NAME_REF - NAME: "io" - DOUBLE_COLON: "::" - NAME_REF - NAME: "print_str" - ARG_LIST - LEFT_PAREN: "(" - LITERAL - STRING: "\"hello\"" - RIGHT_PAREN: ")" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/qualified_path.mitki b/crates/mitki-parse/test_data/qualified_path.mitki deleted file mode 100644 index 56a61b7..0000000 --- a/crates/mitki-parse/test_data/qualified_path.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main(arg: crate::Point): crate::Result { - std::io::print_str("hello") -} diff --git a/crates/mitki-parse/test_data/return.ir b/crates/mitki-parse/test_data/return.ir deleted file mode 100644 index 8542974..0000000 --- a/crates/mitki-parse/test_data/return.ir +++ /dev/null @@ -1,17 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - RETURN_STMT - RETURN_KW: "return" - LITERAL - INT_NUMBER: "42" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/return.mitki b/crates/mitki-parse/test_data/return.mitki deleted file mode 100644 index e0e3233..0000000 --- a/crates/mitki-parse/test_data/return.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun foo() { - return 42 -} diff --git a/crates/mitki-parse/test_data/std_alloc_module.ir b/crates/mitki-parse/test_data/std_alloc_module.ir deleted file mode 100644 index 0103c69..0000000 --- a/crates/mitki-parse/test_data/std_alloc_module.ir +++ /dev/null @@ -1,137 +0,0 @@ -MODULE - USE_ITEM - USE_KW: "use" - PATH_EXPR - NAME_REF - NAME: "std" - DOUBLE_COLON: "::" - NAME_REF - NAME: "alloc" - AS_KW: "as" - IDENT - NAME: "alloc" - SEMICOLON: ";" - USE_ITEM - USE_KW: "use" - PATH_EXPR - NAME_REF - NAME: "std" - DOUBLE_COLON: "::" - NAME_REF - NAME: "alloc" - DOUBLE_COLON: "::" - NAME_REF - NAME: "int" - AS_KW: "as" - IDENT - NAME: "int_alloc" - SEMICOLON: ";" - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - UNSAFE_EXPR - NAME: "unsafe" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "bytes" - COLON: ":" - POINTER_TYPE - PREFIX_OPERATOR: "*" - NAME: "mut" - PATH_TYPE - NAME: "u8" - EQ: "=" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "alloc" - DOUBLE_COLON: "::" - NAME_REF - NAME: "alloc" - ARG_LIST - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "4" - COMMA: "," - LITERAL - INT_NUMBER: "1" - RIGHT_PAREN: ")" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "alloc" - DOUBLE_COLON: "::" - NAME_REF - NAME: "dealloc" - ARG_LIST - LEFT_PAREN: "(" - PATH_EXPR - NAME_REF - NAME: "bytes" - COMMA: "," - LITERAL - INT_NUMBER: "4" - COMMA: "," - LITERAL - INT_NUMBER: "1" - RIGHT_PAREN: ")" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "ints" - COLON: ":" - POINTER_TYPE - PREFIX_OPERATOR: "*" - NAME: "mut" - PATH_TYPE - NAME: "int" - EQ: "=" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "int_alloc" - DOUBLE_COLON: "::" - NAME_REF - NAME: "alloc" - ARG_LIST - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "4" - COMMA: "," - LITERAL - INT_NUMBER: "4" - RIGHT_PAREN: ")" - CALL_EXPR - PATH_EXPR - NAME_REF - NAME: "int_alloc" - DOUBLE_COLON: "::" - NAME_REF - NAME: "dealloc" - ARG_LIST - LEFT_PAREN: "(" - PATH_EXPR - NAME_REF - NAME: "ints" - COMMA: "," - LITERAL - INT_NUMBER: "4" - COMMA: "," - LITERAL - INT_NUMBER: "4" - RIGHT_PAREN: ")" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/std_alloc_module.mitki b/crates/mitki-parse/test_data/std_alloc_module.mitki deleted file mode 100644 index f843ef0..0000000 --- a/crates/mitki-parse/test_data/std_alloc_module.mitki +++ /dev/null @@ -1,12 +0,0 @@ -use std::alloc as alloc; -use std::alloc::int as int_alloc; - -fun main() { - unsafe { - val bytes: *mut u8 = alloc::alloc(4, 1) - alloc::dealloc(bytes, 4, 1) - - val ints: *mut int = int_alloc::alloc(4, 4) - int_alloc::dealloc(ints, 4, 4) - } -} diff --git a/crates/mitki-parse/test_data/string_literal.ir b/crates/mitki-parse/test_data/string_literal.ir deleted file mode 100644 index 1d72890..0000000 --- a/crates/mitki-parse/test_data/string_literal.ir +++ /dev/null @@ -1,15 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - LITERAL - STRING: "\"hello\"" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/string_literal.mitki b/crates/mitki-parse/test_data/string_literal.mitki deleted file mode 100644 index 0707032..0000000 --- a/crates/mitki-parse/test_data/string_literal.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - "hello" -} diff --git a/crates/mitki-parse/test_data/struct_def.ir b/crates/mitki-parse/test_data/struct_def.ir deleted file mode 100644 index cfc489c..0000000 --- a/crates/mitki-parse/test_data/struct_def.ir +++ /dev/null @@ -1,24 +0,0 @@ -MODULE - STRUCT_DEF - STRUCT_KW: "struct" - IDENT - NAME: "Point" - STRUCT_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_FIELD - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - STRUCT_FIELD - IDENT - NAME: "y" - COLON: ":" - PATH_TYPE - NAME: "int" - COMMA: "," - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/struct_def.mitki b/crates/mitki-parse/test_data/struct_def.mitki deleted file mode 100644 index aa9f288..0000000 --- a/crates/mitki-parse/test_data/struct_def.mitki +++ /dev/null @@ -1,4 +0,0 @@ -struct Point { - x: int, - y: int, -} diff --git a/crates/mitki-parse/test_data/struct_expr.ir b/crates/mitki-parse/test_data/struct_expr.ir deleted file mode 100644 index 0ce7115..0000000 --- a/crates/mitki-parse/test_data/struct_expr.ir +++ /dev/null @@ -1,33 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - STRUCT_EXPR - PATH_EXPR - NAME_REF - NAME: "Point" - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_EXPR_FIELD - IDENT - NAME: "x" - COLON: ":" - LITERAL - INT_NUMBER: "1" - COMMA: "," - STRUCT_EXPR_FIELD - IDENT - NAME: "y" - COLON: ":" - LITERAL - INT_NUMBER: "2" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/struct_expr.mitki b/crates/mitki-parse/test_data/struct_expr.mitki deleted file mode 100644 index 7dc0727..0000000 --- a/crates/mitki-parse/test_data/struct_expr.mitki +++ /dev/null @@ -1,3 +0,0 @@ -fun main() { - Point { x: 1, y: 2 } -} diff --git a/crates/mitki-parse/test_data/struct_expr_shorthand.ir b/crates/mitki-parse/test_data/struct_expr_shorthand.ir deleted file mode 100644 index 115765c..0000000 --- a/crates/mitki-parse/test_data/struct_expr_shorthand.ir +++ /dev/null @@ -1,38 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "x" - EQ: "=" - LITERAL - INT_NUMBER: "1" - STRUCT_EXPR - PATH_EXPR - NAME_REF - NAME: "Point" - STRUCT_EXPR_FIELD_LIST - LEFT_BRACE: "{" - STRUCT_EXPR_FIELD - IDENT - NAME: "x" - COMMA: "," - STRUCT_EXPR_FIELD - IDENT - NAME: "y" - COLON: ":" - LITERAL - INT_NUMBER: "2" - RIGHT_BRACE: "}" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/struct_expr_shorthand.mitki b/crates/mitki-parse/test_data/struct_expr_shorthand.mitki deleted file mode 100644 index 48a6779..0000000 --- a/crates/mitki-parse/test_data/struct_expr_shorthand.mitki +++ /dev/null @@ -1,4 +0,0 @@ -fun main() { - val x = 1 - Point { x, y: 2 } -} diff --git a/crates/mitki-parse/test_data/tuple_expr.ir b/crates/mitki-parse/test_data/tuple_expr.ir deleted file mode 100644 index 0903771..0000000 --- a/crates/mitki-parse/test_data/tuple_expr.ir +++ /dev/null @@ -1,32 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "foo" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - TUPLE_EXPR - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - PAREN_EXPR - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "1" - RIGHT_PAREN: ")" - TUPLE_EXPR - LEFT_PAREN: "(" - LITERAL - INT_NUMBER: "1" - COMMA: "," - RIGHT_PAREN: ")" - TUPLE_EXPR - LEFT_PAREN: "(" - COMMA: "," - RIGHT_PAREN: ")" - RIGHT_BRACE: "}" - -Errors: - expected expression \ No newline at end of file diff --git a/crates/mitki-parse/test_data/tuple_expr.mitki b/crates/mitki-parse/test_data/tuple_expr.mitki deleted file mode 100644 index c0072a5..0000000 --- a/crates/mitki-parse/test_data/tuple_expr.mitki +++ /dev/null @@ -1,6 +0,0 @@ -fun foo() { - () - (1) - (1,) - (,) -} diff --git a/crates/mitki-parse/test_data/unary_expr.ir b/crates/mitki-parse/test_data/unary_expr.ir deleted file mode 100644 index 6dae98d..0000000 --- a/crates/mitki-parse/test_data/unary_expr.ir +++ /dev/null @@ -1,61 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - EXPR_STMT - PREFIX_EXPR - PREFIX_OPERATOR: "^^" - PAREN_EXPR - LEFT_PAREN: "(" - PREFIX_EXPR - PREFIX_OPERATOR: "^^" - PATH_EXPR - NAME_REF - NAME: "x" - RIGHT_PAREN: ")" - SEMICOLON: ";" - EXPR_STMT - PREFIX_EXPR - PREFIX_OPERATOR: "*" - PATH_EXPR - NAME_REF - NAME: "x" - SEMICOLON: ";" - EXPR_STMT - POSTFIX_EXPR - PATH_EXPR - NAME_REF - NAME: "x" - POSTFIX_OPERATOR: "*" - SEMICOLON: ";" - EXPR_STMT - PREFIX_EXPR - PREFIX_OPERATOR: "+" - PAREN_EXPR - LEFT_PAREN: "(" - PREFIX_EXPR - PREFIX_OPERATOR: "-" - PATH_EXPR - NAME_REF - NAME: "x" - RIGHT_PAREN: ")" - SEMICOLON: ";" - EXPR_STMT - PREFIX_OPERATOR - BINARY_OPERATOR: "+" - PREFIX_EXPR - PREFIX_OPERATOR: "-" - PATH_EXPR - NAME_REF - NAME: "x" - SEMICOLON: ";" - RIGHT_BRACE: "}" - -Errors: - unary operator cannot be separated from its operand \ No newline at end of file diff --git a/crates/mitki-parse/test_data/unary_expr.mitki b/crates/mitki-parse/test_data/unary_expr.mitki deleted file mode 100644 index 67c29cf..0000000 --- a/crates/mitki-parse/test_data/unary_expr.mitki +++ /dev/null @@ -1,7 +0,0 @@ -fun main() { - ^^(^^x); - *x; - x*; - +(-x); - + -x; -} diff --git a/crates/mitki-parse/test_data/val_stmt.ir b/crates/mitki-parse/test_data/val_stmt.ir deleted file mode 100644 index 7e1815b..0000000 --- a/crates/mitki-parse/test_data/val_stmt.ir +++ /dev/null @@ -1,32 +0,0 @@ -MODULE - FN - FUN_KW: "fun" - IDENT - NAME: "main" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - STMT_LIST - LEFT_BRACE: "{" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "x" - EQ: "=" - LITERAL - INT_NUMBER: "42" - VAL_STMT - VAL_KW: "val" - BINDING_PATTERN - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "i32" - EQ: "=" - LITERAL - INT_NUMBER: "42" - RIGHT_BRACE: "}" - -Errors: diff --git a/crates/mitki-parse/test_data/val_stmt.mitki b/crates/mitki-parse/test_data/val_stmt.mitki deleted file mode 100644 index 28f475c..0000000 --- a/crates/mitki-parse/test_data/val_stmt.mitki +++ /dev/null @@ -1,4 +0,0 @@ -fun main() { - val x = 42 - val x: i32 = 42 -} \ No newline at end of file diff --git a/crates/mitki-parse/test_data/wasm_import_export_function.ir b/crates/mitki-parse/test_data/wasm_import_export_function.ir deleted file mode 100644 index 5267a5a..0000000 --- a/crates/mitki-parse/test_data/wasm_import_export_function.ir +++ /dev/null @@ -1,41 +0,0 @@ -MODULE - FN - NAME: "export" - FUN_KW: "fun" - IDENT - NAME: "answer" - PARAM_LIST - LEFT_PAREN: "(" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "int" - STMT_LIST - LEFT_BRACE: "{" - LITERAL - INT_NUMBER: "42" - RIGHT_BRACE: "}" - FN - NAME: "import" - STRING: "\"env\"" - FUN_KW: "fun" - IDENT - NAME: "host" - PARAM_LIST - LEFT_PAREN: "(" - PARAM - BINDING_PATTERN - IDENT - NAME: "x" - COLON: ":" - PATH_TYPE - NAME: "int" - RIGHT_PAREN: ")" - RETURN_TYPE - COLON: ":" - PATH_TYPE - NAME: "bool" - SEMICOLON: ";" - -Errors: diff --git a/crates/mitki-parse/test_data/wasm_import_export_function.mitki b/crates/mitki-parse/test_data/wasm_import_export_function.mitki deleted file mode 100644 index 7d95742..0000000 --- a/crates/mitki-parse/test_data/wasm_import_export_function.mitki +++ /dev/null @@ -1,5 +0,0 @@ -export fun answer(): int { - 42 -} - -import "env" fun host(x: int): bool; diff --git a/crates/mitki-resolve/Cargo.toml b/crates/mitki-resolve/Cargo.toml deleted file mode 100644 index 4bfa22a..0000000 --- a/crates/mitki-resolve/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "mitki-resolve" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-span.workspace = true -rustc-hash = "2.0" -salsa.workspace = true diff --git a/crates/mitki-resolve/src/compiler_intrinsics.rs b/crates/mitki-resolve/src/compiler_intrinsics.rs deleted file mode 100644 index 8010e65..0000000 --- a/crates/mitki-resolve/src/compiler_intrinsics.rs +++ /dev/null @@ -1,93 +0,0 @@ -use mitki_span::Symbol; -use salsa::Database; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum CompilerIntrinsic { - Comptime, - TypeName, - FieldCount, - FieldName, - VariantCount, - VariantName, - FunctionParamCount, - FunctionParamTypeName, - FunctionReturnTypeName, - StackAlloc, - PtrRead, - PtrWrite, - PtrAdd, - StrBytes, - StrFromUtf8Unchecked, - ArrayMutBytes, -} - -const COMPILER_INTRINSICS: [(CompilerIntrinsic, &str); 16] = [ - (CompilerIntrinsic::Comptime, "comptime"), - (CompilerIntrinsic::TypeName, "type_name"), - (CompilerIntrinsic::FieldCount, "field_count"), - (CompilerIntrinsic::FieldName, "field_name"), - (CompilerIntrinsic::VariantCount, "variant_count"), - (CompilerIntrinsic::VariantName, "variant_name"), - (CompilerIntrinsic::FunctionParamCount, "function_param_count"), - (CompilerIntrinsic::FunctionParamTypeName, "function_param_type_name"), - (CompilerIntrinsic::FunctionReturnTypeName, "function_return_type_name"), - (CompilerIntrinsic::StackAlloc, "stack_alloc"), - (CompilerIntrinsic::PtrRead, "ptr_read"), - (CompilerIntrinsic::PtrWrite, "ptr_write"), - (CompilerIntrinsic::PtrAdd, "ptr_add"), - (CompilerIntrinsic::StrBytes, "str_bytes"), - (CompilerIntrinsic::StrFromUtf8Unchecked, "str_from_utf8_unchecked"), - (CompilerIntrinsic::ArrayMutBytes, "array_mut_bytes"), -]; - -impl CompilerIntrinsic { - pub fn source_name(self) -> &'static str { - COMPILER_INTRINSICS - .iter() - .find(|(intrinsic, _)| *intrinsic == self) - .map(|(_, name)| *name) - .expect("compiler intrinsic metadata should exist") - } - - pub fn is_reflection(self) -> bool { - matches!( - self, - Self::TypeName - | Self::FieldCount - | Self::FieldName - | Self::VariantCount - | Self::VariantName - | Self::FunctionParamCount - | Self::FunctionParamTypeName - | Self::FunctionReturnTypeName - ) - } - - pub fn requires_unsafe(self) -> bool { - matches!( - self, - Self::StackAlloc - | Self::PtrRead - | Self::PtrWrite - | Self::PtrAdd - | Self::StrFromUtf8Unchecked - | Self::ArrayMutBytes - ) - } -} - -pub fn compiler_intrinsics() -> impl Iterator { - COMPILER_INTRINSICS.into_iter().map(|(intrinsic, _)| intrinsic) -} - -pub fn lookup_compiler_intrinsic( - db: &dyn Database, - symbol: Symbol<'_>, -) -> Option { - let text = symbol.text(db); - COMPILER_INTRINSICS.iter().find(|(_, name)| *name == text).map(|(intrinsic, _)| *intrinsic) -} - -pub fn is_reserved_compiler_name(db: &dyn Database, symbol: Symbol<'_>) -> bool { - lookup_compiler_intrinsic(db, symbol).is_some() -} diff --git a/crates/mitki-resolve/src/lib.rs b/crates/mitki-resolve/src/lib.rs deleted file mode 100644 index 3c04365..0000000 --- a/crates/mitki-resolve/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod compiler_intrinsics; -mod resolver; -mod runtime; -pub mod scope; -mod signature; - -pub use compiler_intrinsics::{ - CompilerIntrinsic, compiler_intrinsics, is_reserved_compiler_name, lookup_compiler_intrinsic, -}; -pub use resolver::{ - BindingId, MethodResolution, Namespace, Resolution, ResolveStatus, Resolver, TargetId, - VisibleBinding, lookup_builtin_type, resolve_method_for_receiver, -}; -pub use runtime::{ - RuntimeFunction, RuntimeFunctionInfo, RuntimeTy, is_reserved_runtime_name, - lookup_runtime_function, runtime_functions, -}; -pub use signature::{SignatureTypeResolutionError, SignatureTypeResolver}; diff --git a/crates/mitki-resolve/src/resolver.rs b/crates/mitki-resolve/src/resolver.rs deleted file mode 100644 index 968deff..0000000 --- a/crates/mitki-resolve/src/resolver.rs +++ /dev/null @@ -1,517 +0,0 @@ -use mitki_hir::hir::{ExprId, NameId, TyId}; -use mitki_hir::ty::{EnumTy, ExactInt, StructTy, Ty, TyKind}; -use mitki_inputs::ModuleId; -use mitki_lower::item::package::root_module; -use mitki_lower::item::scope::{ - EnumLocation, EnumVariantLocation, FunctionLocation, HasItemDecls as _, HasVisibleItems as _, - ItemDecls, ModuleBinding, StructLocation, TypeDeclaration, VisibleItems, -}; -use mitki_lower::item::stdlib::stdlib_package; -use mitki_lower::item::tree::HasItemTree as _; -use mitki_span::{IntoSymbol as _, Symbol}; -use rustc_hash::FxHashMap; -use salsa::Database; - -use crate::scope::{ExprScopes, HasExprScopes as _, Scope}; -use crate::{ - CompilerIntrinsic, RuntimeFunction, SignatureTypeResolver, lookup_compiler_intrinsic, - lookup_runtime_function, -}; - -#[salsa::tracked(returns(ref))] -fn builtin_scope(db: &dyn Database) -> FxHashMap, Ty<'_>> { - FxHashMap::from_iter([ - ("bool".into_symbol(db), Ty::new(db, TyKind::Bool)), - ("char".into_symbol(db), Ty::new(db, TyKind::Char)), - ("float".into_symbol(db), Ty::new(db, TyKind::Float)), - ("u8".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::U8))), - ("u16".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::U16))), - ("u32".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::U32))), - ("u64".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::U64))), - ("i8".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::I8))), - ("i16".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::I16))), - ("i32".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::I32))), - ("i64".into_symbol(db), Ty::new(db, TyKind::ExactInt(ExactInt::I64))), - ("int".into_symbol(db), Ty::new(db, TyKind::Int)), - ("str".into_symbol(db), Ty::new(db, TyKind::String)), - ]) -} - -pub fn lookup_builtin_type<'db>(db: &'db dyn Database, name: Symbol<'db>) -> Option> { - builtin_scope(db).get(&name).copied() -} - -#[derive(Debug, Clone)] -pub struct MethodResolution<'db> { - pub function: FunctionLocation<'db>, - pub inputs: Vec>, - pub output: Ty<'db>, -} - -pub fn resolve_method_for_receiver<'db>( - db: &'db dyn Database, - receiver_ty: Ty<'db>, - method_name: Symbol<'db>, -) -> Option> { - let method_module = match receiver_ty.kind(db) { - TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty) => struct_ty.module(db), - TyKind::Enum(enum_ty) => enum_ty.module(db), - _ => return None, - }; - - let function = method_module.visible_items(db).get_value(&method_name)?; - let signature = function.signature(db); - let resolved = SignatureTypeResolver::new(db, function, signature); - let params = signature.params(db); - let (&receiver_param, method_params) = params.split_first()?; - let (_, receiver_param_ty) = signature.nodes(db).param(receiver_param); - let resolved_receiver = resolved.resolve(receiver_param_ty).ok()?; - if resolved_receiver != receiver_ty { - return None; - } - - let inputs = method_params - .iter() - .map(|¶m| { - let (_, ty) = signature.nodes(db).param(param); - resolved.resolve(ty).ok() - }) - .collect::>>()?; - let output = resolved.resolve(signature.ret_type(db)).ok()?; - - Some(MethodResolution { function, inputs, output }) -} - -#[derive(Clone)] -pub struct Resolver<'db> { - db: &'db dyn Database, - current_module: ModuleId<'db>, - visible_items: &'db VisibleItems<'db>, - item_decls: &'db ItemDecls<'db>, - expr_scopes: &'db ExprScopes<'db>, - scopes: Vec>, - builtin_scope: &'db FxHashMap, Ty<'db>>, -} - -impl<'db> Resolver<'db> { - pub fn new(db: &'db dyn Database, function: FunctionLocation<'db>) -> Self { - let module = function.module(db); - - Self { - db, - current_module: module, - visible_items: module.visible_items(db), - item_decls: module.item_decls(db), - expr_scopes: function.expr_scopes(db), - scopes: Vec::new(), - builtin_scope: builtin_scope(db), - } - } - - fn scopes(&self) -> impl ExactSizeIterator> + '_ { - self.scopes.iter().rev().copied() - } - - pub fn scopes_for_node(&mut self, node: ExprId) -> Guard { - let start = self.scopes.len(); - - let innermost_scope = self.scopes().next(); - let scope_for_expr = self.expr_scopes.scope_for(node); - - let scopes = self.expr_scopes.chain(scope_for_expr); - if let Some(scope) = innermost_scope { - self.scopes.extend(scopes.take_while(|&it| it != scope)); - } else { - self.scopes.extend(scopes); - } - - self.scopes[start..].reverse(); - - Guard(start) - } - - pub fn scopes_for_type(&mut self, ty: TyId) -> Guard { - let start = self.scopes.len(); - - let innermost_scope = self.scopes().next(); - let scope_for_ty = self.expr_scopes.scope_for_ty(ty); - - let scopes = self.expr_scopes.chain(scope_for_ty); - if let Some(scope) = innermost_scope { - self.scopes.extend(scopes.take_while(|&it| it != scope)); - } else { - self.scopes.extend(scopes); - } - - self.scopes[start..].reverse(); - - Guard(start) - } - - pub fn reset(&mut self, Guard(start): Guard) { - self.scopes.truncate(start); - } - - pub fn resolve_name(&self, path: Symbol<'db>, namespace: Namespace) -> Resolution<'db> { - match namespace { - Namespace::Value => self.resolve_value(path), - Namespace::Type => self.resolve_type(path), - } - } - - pub fn resolve_value_binding(&self, path: Symbol<'db>) -> Option> { - self.resolve_name(path, Namespace::Value).binding - } - - pub fn resolve_type_binding(&self, path: Symbol<'db>) -> Option> { - self.resolve_name(path, Namespace::Type).binding - } - - pub fn ty_for_binding(&self, binding: BindingId<'db>) -> Option> { - match binding { - BindingId::Struct(location) => { - let file = location.file(self.db); - let name = file.item_tree(self.db)[location.index(self.db)].name; - let nominal = StructTy::new( - self.db, - location.module(self.db), - location.index(self.db).index(), - name, - Vec::new(), - ); - Some(if location.source(self.db).is_extern() { - Ty::new(self.db, TyKind::ExternStruct(nominal)) - } else { - Ty::new(self.db, TyKind::Struct(nominal)) - }) - } - BindingId::Enum(location) => { - let file = location.file(self.db); - let name = file.item_tree(self.db)[location.index(self.db)].name; - let nominal = EnumTy::new( - self.db, - location.module(self.db), - location.index(self.db).index(), - name, - Vec::new(), - ); - Some(Ty::new(self.db, TyKind::Enum(nominal))) - } - BindingId::BuiltinType(ty) => Some(ty), - _ => None, - } - } - - fn resolve_value(&self, path: Symbol<'db>) -> Resolution<'db> { - if let Some(binding) = self.resolve_segment_path(path, Namespace::Value) { - return Resolution::resolved(binding, Namespace::Value); - } - if let Some(item) = self.visible_items.get_value(&path) { - return Resolution::resolved(BindingId::Function(item), Namespace::Value); - } - if path.text(self.db).contains("::") { - return Resolution::unresolved(Namespace::Value); - } - - for scope in self.scopes() { - if let Some(binding) = self.expr_scopes.lookup(scope, path) { - return Resolution::resolved(binding, Namespace::Value); - } - } - - if let Some(runtime) = lookup_runtime_function(self.db, path) { - return Resolution::resolved(BindingId::RuntimeFunction(runtime), Namespace::Value); - } - - if let Some(intrinsic) = lookup_compiler_intrinsic(self.db, path) { - return Resolution::resolved(BindingId::CompilerIntrinsic(intrinsic), Namespace::Value); - } - - if let Some(item) = self.visible_items.get_value(&path) { - return Resolution::resolved(BindingId::Function(item), Namespace::Value); - } - - Resolution::unresolved(Namespace::Value) - } - - fn resolve_type(&self, path: Symbol<'db>) -> Resolution<'db> { - if let Some(binding) = self.resolve_segment_path(path, Namespace::Type) { - return Resolution::resolved(binding, Namespace::Type); - } - if let Some(declaration) = self.visible_items.get_type_declaration(&path) { - let binding = match declaration { - TypeDeclaration::Struct(location) => BindingId::Struct(location), - TypeDeclaration::Enum(location) => BindingId::Enum(location), - }; - return Resolution::resolved(binding, Namespace::Type); - } - if path.text(self.db).contains("::") { - return Resolution::unresolved(Namespace::Type); - } - - if let Some(declaration) = self.visible_items.get_type_declaration(&path) { - let binding = match declaration { - TypeDeclaration::Struct(location) => BindingId::Struct(location), - TypeDeclaration::Enum(location) => BindingId::Enum(location), - }; - return Resolution::resolved(binding, Namespace::Type); - } - - if let Some(&ty) = self.builtin_scope.get(&path) { - return Resolution::resolved(BindingId::BuiltinType(ty), Namespace::Type); - } - - Resolution::unresolved(Namespace::Type) - } - - pub fn for_scope( - db: &'db dyn Database, - module: ModuleId<'db>, - visible_items: &'db VisibleItems<'db>, - item_decls: &'db ItemDecls<'db>, - expr_scopes: &'db ExprScopes<'db>, - scope: Option>, - ) -> Self { - let mut scopes: Vec<_> = expr_scopes.chain(scope).collect::>().into_iter().collect(); - scopes.reverse(); - - Resolver { - db, - current_module: module, - visible_items, - item_decls, - scopes, - expr_scopes, - builtin_scope: builtin_scope(db), - } - } - - fn resolve_segment_path( - &self, - path: Symbol<'db>, - namespace: Namespace, - ) -> Option> { - let segments = path - .text(self.db) - .split("::") - .filter(|segment| !segment.is_empty()) - .collect::>(); - if segments.len() < 2 { - return None; - } - - let binding = self.resolve_module_path(&segments[..segments.len() - 1])?; - if matches!(segments.first().copied(), Some("std")) && !binding.public { - return None; - } - - let last = segments.last()?.into_symbol(self.db); - match namespace { - Namespace::Value => binding - .module - .visible_items(self.db) - .get_value(&last) - .map(BindingId::Function) - .or_else(|| { - if matches!(segments.first().copied(), Some("std")) { - lookup_runtime_function(self.db, path).map(BindingId::RuntimeFunction) - } else { - None - } - }), - Namespace::Type => { - binding.module.visible_items(self.db).get_type_declaration(&last).map( - |declaration| match declaration { - TypeDeclaration::Struct(location) => BindingId::Struct(location), - TypeDeclaration::Enum(location) => BindingId::Enum(location), - }, - ) - } - } - } - - fn resolve_module_path(&self, segments: &[&str]) -> Option> { - let (first, rest) = segments.split_first()?; - let mut binding = match *first { - "crate" => ModuleBinding { - module: root_module(self.db, self.current_module.package(self.db)), - public: true, - }, - "std" => ModuleBinding { - module: root_module(self.db, stdlib_package(self.db)), - public: true, - }, - _ => self - .visible_items - .get_module_alias(&first.into_symbol(self.db)) - .or_else(|| self.visible_items.get_module(&first.into_symbol(self.db)))?, - }; - - for segment in rest { - let child = - binding.module.visible_items(self.db).get_module(&segment.into_symbol(self.db))?; - binding = - ModuleBinding { module: child.module, public: binding.public && child.public }; - } - - Some(binding) - } - - pub fn resolve_enum_variant_binding(&self, variant: Symbol<'db>) -> Resolution<'db> { - let variants = self.item_decls.enum_variants_by_name(&variant); - match variants { - [] => Resolution::unresolved(Namespace::Value), - [variant] => Resolution::resolved(BindingId::EnumVariant(*variant), Namespace::Value), - _ => Resolution::ambiguous(Namespace::Value), - } - } - - pub fn resolve_enum_variant(&self, variant: Symbol<'db>) -> Option> { - let BindingId::EnumVariant(variant) = self.resolve_enum_variant_binding(variant).binding? - else { - return None; - }; - let enum_location = variant.parent(self.db); - let file = enum_location.file(self.db); - let enum_name = file.item_tree(self.db)[enum_location.index(self.db)].name; - self.visible_items.get_type(&enum_name) - } - - pub fn visible_bindings(&self) -> Vec> { - let mut bindings = Vec::new(); - - for scope in self.scopes() { - for entry in self.expr_scopes.entries(scope) { - bindings.push(VisibleBinding { - name: entry.name, - binding: entry.binding, - namespace: Namespace::Value, - }); - } - } - - bindings.extend(self.visible_items.values().map(|(name, location)| VisibleBinding { - name: *name, - binding: BindingId::Function(*location), - namespace: Namespace::Value, - })); - - bindings.extend(self.visible_items.types().filter_map(|(name, _)| { - let binding = match self.visible_items.get_type_declaration(name)? { - TypeDeclaration::Struct(location) => BindingId::Struct(location), - TypeDeclaration::Enum(location) => BindingId::Enum(location), - }; - Some(VisibleBinding { name: *name, binding, namespace: Namespace::Type }) - })); - - bindings.extend(self.builtin_scope.iter().map(|(name, ty)| VisibleBinding { - name: *name, - binding: BindingId::BuiltinType(*ty), - namespace: Namespace::Type, - })); - - bindings - } -} - -pub struct Guard(usize); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] -pub enum Namespace { - Value, - Type, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] -pub enum ResolveStatus { - Resolved, - Unresolved, - Ambiguous, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] -pub enum BindingId<'db> { - Local(NameId), - Param(NameId), - CompilerIntrinsic(CompilerIntrinsic), - RuntimeFunction(RuntimeFunction), - Function(FunctionLocation<'db>), - Struct(StructLocation<'db>), - Enum(EnumLocation<'db>), - EnumVariant(EnumVariantLocation<'db>), - BuiltinType(Ty<'db>), -} - -impl<'db> BindingId<'db> { - pub fn namespace(self) -> Namespace { - match self { - BindingId::Local(_) - | BindingId::Param(_) - | BindingId::CompilerIntrinsic(_) - | BindingId::RuntimeFunction(_) - | BindingId::Function(_) - | BindingId::EnumVariant(_) => Namespace::Value, - BindingId::Struct(_) | BindingId::Enum(_) | BindingId::BuiltinType(_) => { - Namespace::Type - } - } - } - - pub fn target(self) -> Option> { - match self { - BindingId::Local(name) => Some(TargetId::Local(name)), - BindingId::Param(name) => Some(TargetId::Param(name)), - BindingId::Function(function) => Some(TargetId::Function(function)), - BindingId::Struct(location) => Some(TargetId::Struct(location)), - BindingId::Enum(location) => Some(TargetId::Enum(location)), - BindingId::EnumVariant(location) => Some(TargetId::EnumVariant(location)), - BindingId::CompilerIntrinsic(_) - | BindingId::RuntimeFunction(_) - | BindingId::BuiltinType(_) => None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct VisibleBinding<'db> { - pub name: Symbol<'db>, - pub binding: BindingId<'db>, - pub namespace: Namespace, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] -pub enum TargetId<'db> { - Local(NameId), - Param(NameId), - Function(FunctionLocation<'db>), - Struct(StructLocation<'db>), - Enum(EnumLocation<'db>), - EnumVariant(EnumVariantLocation<'db>), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update)] -pub struct Resolution<'db> { - pub binding: Option>, - pub target: Option>, - pub namespace: Namespace, - pub status: ResolveStatus, -} - -impl<'db> Resolution<'db> { - fn resolved(binding: BindingId<'db>, namespace: Namespace) -> Self { - Self { - target: binding.target(), - binding: Some(binding), - namespace, - status: ResolveStatus::Resolved, - } - } - - fn unresolved(namespace: Namespace) -> Self { - Self { binding: None, target: None, namespace, status: ResolveStatus::Unresolved } - } - - fn ambiguous(namespace: Namespace) -> Self { - Self { binding: None, target: None, namespace, status: ResolveStatus::Ambiguous } - } -} diff --git a/crates/mitki-resolve/src/runtime.rs b/crates/mitki-resolve/src/runtime.rs deleted file mode 100644 index 55ea2c6..0000000 --- a/crates/mitki-resolve/src/runtime.rs +++ /dev/null @@ -1,151 +0,0 @@ -use mitki_hir::ty::{Ty, TyKind}; -use mitki_span::Symbol; -use salsa::Database; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RuntimeTy { - Int, - Str, - Unit, -} - -impl RuntimeTy { - pub fn display(self) -> &'static str { - match self { - Self::Int => "int", - Self::Str => "str", - Self::Unit => "()", - } - } - - pub fn as_ty<'db>(self, db: &'db dyn Database) -> Ty<'db> { - match self { - Self::Int => Ty::new(db, TyKind::Int), - Self::Str => Ty::new(db, TyKind::String), - Self::Unit => Ty::new(db, TyKind::Tuple(Vec::new())), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RuntimeFunction { - PrintI32, - PrintStr, - Alloc, - Dealloc, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct RuntimeFunctionInfo { - pub function: RuntimeFunction, - pub visible_in_source: bool, - pub source_name: &'static str, - pub import_module: &'static str, - pub import_name: &'static str, - pub params: &'static [RuntimeTy], - pub result: RuntimeTy, -} - -const PRINT_I32_PARAMS: [RuntimeTy; 1] = [RuntimeTy::Int]; -const PRINT_STR_PARAMS: [RuntimeTy; 1] = [RuntimeTy::Str]; -const ALLOC_PARAMS: [RuntimeTy; 2] = [RuntimeTy::Int, RuntimeTy::Int]; -const DEALLOC_PARAMS: [RuntimeTy; 3] = [RuntimeTy::Int, RuntimeTy::Int, RuntimeTy::Int]; - -const RUNTIME_FUNCTIONS: [RuntimeFunctionInfo; 4] = [ - RuntimeFunctionInfo { - function: RuntimeFunction::PrintI32, - visible_in_source: false, - source_name: "std::io::print_int", - import_module: "mitki", - import_name: "print_i32", - params: &PRINT_I32_PARAMS, - result: RuntimeTy::Unit, - }, - RuntimeFunctionInfo { - function: RuntimeFunction::PrintStr, - visible_in_source: true, - source_name: "std::io::print_str", - import_module: "mitki", - import_name: "print_str", - params: &PRINT_STR_PARAMS, - result: RuntimeTy::Unit, - }, - RuntimeFunctionInfo { - function: RuntimeFunction::Alloc, - visible_in_source: false, - source_name: "alloc", - import_module: "mitki", - import_name: "alloc", - params: &ALLOC_PARAMS, - result: RuntimeTy::Int, - }, - RuntimeFunctionInfo { - function: RuntimeFunction::Dealloc, - visible_in_source: false, - source_name: "dealloc", - import_module: "mitki", - import_name: "dealloc", - params: &DEALLOC_PARAMS, - result: RuntimeTy::Unit, - }, -]; - -impl RuntimeFunction { - pub fn info(self) -> &'static RuntimeFunctionInfo { - RUNTIME_FUNCTIONS - .iter() - .find(|info| info.function == self) - .expect("runtime function metadata should exist") - } - - pub fn source_name(self) -> &'static str { - self.info().source_name - } - - pub fn import_module(self) -> &'static str { - self.info().import_module - } - - pub fn import_name(self) -> &'static str { - self.info().import_name - } - - pub fn params(self) -> &'static [RuntimeTy] { - self.info().params - } - - pub fn result(self) -> RuntimeTy { - self.info().result - } - - pub fn function_ty<'db>(self, db: &'db dyn Database) -> Ty<'db> { - let inputs = self.params().iter().map(|ty| ty.as_ty(db)).collect(); - let output = self.result().as_ty(db); - Ty::new(db, TyKind::Function { inputs, output }) - } - - pub fn hover_text(self) -> String { - let params = self.params().iter().map(|ty| ty.display()).collect::>().join(", "); - format!( - "```mitki\nfun {}: fun({params}) -> {}\n```", - self.source_name(), - self.result().display() - ) - } -} - -pub fn runtime_functions() -> &'static [RuntimeFunctionInfo] { - &RUNTIME_FUNCTIONS -} - -pub fn lookup_runtime_function(db: &dyn Database, symbol: Symbol<'_>) -> Option { - let text = symbol.text(db); - runtime_functions() - .iter() - .find(|info| info.visible_in_source && info.source_name == text) - .map(|info| info.function) -} - -pub fn is_reserved_runtime_name(db: &dyn Database, symbol: Symbol<'_>) -> bool { - lookup_runtime_function(db, symbol).is_some() -} diff --git a/crates/mitki-resolve/src/scope.rs b/crates/mitki-resolve/src/scope.rs deleted file mode 100644 index 1549248..0000000 --- a/crates/mitki-resolve/src/scope.rs +++ /dev/null @@ -1,384 +0,0 @@ -use mitki_hir::arena::{Arena, Key, Range}; -use mitki_hir::hir::{ExprId, Function, NameId, NodeKind, ParamId, PatId, StmtId, TyId}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::FunctionLocation; -use mitki_span::Symbol; -use rustc_hash::FxHashMap; -use salsa::Database; - -use crate::resolver::BindingId; - -pub trait HasExprScopes<'db> { - fn expr_scopes(self, db: &'db dyn Database) -> &'db ExprScopes<'db>; -} - -#[salsa::tracked] -impl<'db> HasExprScopes<'db> for FunctionLocation<'db> { - #[salsa::tracked(returns(ref))] - fn expr_scopes(self, db: &'db dyn Database) -> ExprScopes<'db> { - ExprScopesBuilder { - function: self.hir_function(db).function(db), - scopes: ExprScopes::default(), - } - .build() - } -} - -#[derive(Debug, Default, PartialEq, Eq, salsa::Update)] -pub struct ExprScopes<'db> { - scopes: Arena>, - scope_entries: Arena>, - scope_lookup: FxHashMap, FxHashMap, BindingId<'db>>>, - scope_by_node: FxHashMap>, - scope_by_type: FxHashMap>, -} - -impl<'db> ExprScopes<'db> { - pub fn scope_by_node(&self, node: StmtId) -> Option> { - self.scope_by_node.get(&node).copied() - } - - pub(crate) fn chain(&self, scope: Option>) -> impl Iterator> + '_ { - std::iter::successors(scope, move |&scope| self.scopes[scope].parent) - } - - pub(crate) fn entries(&self, scope: Scope<'db>) -> &[ScopeEntry<'db>] { - &self.scope_entries[self.scopes[scope].entries] - } - - pub(crate) fn lookup(&self, scope: Scope<'db>, name: Symbol<'db>) -> Option> { - self.scope_lookup.get(&scope)?.get(&name).copied() - } - - pub(crate) fn scope_for(&self, expr: ExprId) -> Option> { - self.scope_by_node.get(&expr.into()).copied() - } - - pub(crate) fn scope_for_ty(&self, ty: TyId) -> Option> { - self.scope_by_type.get(&ty).copied() - } -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub(crate) struct ScopeEntry<'db> { - pub(crate) name: Symbol<'db>, - pub(crate) binding: BindingId<'db>, -} - -pub type Scope<'db> = Key>; - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct ScopeData<'db> { - parent: Option>, - entries: Range>, -} - -pub(crate) struct ExprScopesBuilder<'func, 'db> { - function: &'func Function<'db>, - scopes: ExprScopes<'db>, -} - -#[derive(Clone, Copy)] -enum LocalBindingKind { - Local, - Param, -} - -fn empty_entries<'db>(idx: usize) -> Range> { - let idx = Key::new(idx as u32); - Range::new(idx, idx) -} - -impl<'db> ExprScopesBuilder<'_, 'db> { - fn root_scope(&mut self) -> Scope<'db> { - self.scope(None) - } - - fn scope(&mut self, parent: impl Into>>) -> Scope<'db> { - let scope = self.scopes.scopes.alloc(ScopeData { - parent: parent.into(), - entries: empty_entries(self.scopes.scope_entries.len()), - }); - self.scopes.scope_lookup.insert(scope, FxHashMap::default()); - scope - } - - #[track_caller] - fn add_binding(&mut self, name: NameId, binding: BindingId<'db>, scope: Key>) { - let symbol = self.function.node_store().name(name); - let entry = self.scopes.scope_entries.alloc(ScopeEntry { name: symbol, binding }); - self.scopes.scopes[scope].entries = - Range::new_inclusive(self.scopes.scopes[scope].entries.start, entry); - self.scopes.scope_lookup.entry(scope).or_default().insert(symbol, binding); - } - - #[track_caller] - fn add_type(&mut self, ty: TyId, scope: Key>) { - if ty != TyId::ZERO { - self.scopes.scope_by_type.insert(ty, scope); - let nodes = self.function.node_store(); - if let Some(array_id) = nodes.as_type_array(ty) { - let (item, _) = nodes.type_array(array_id); - self.add_type(item, scope); - } - if let Some(tuple_id) = nodes.as_type_tuple(ty) { - for item in nodes.type_tuple(tuple_id) { - self.add_type(item, scope); - } - } - if let Some(ptr_id) = nodes.as_type_ptr_const(ty) { - let (item, _) = nodes.type_ptr_const(ptr_id); - self.add_type(item, scope); - } - if let Some(ptr_id) = nodes.as_type_ptr_mut(ty) { - let (item, _) = nodes.type_ptr_mut(ptr_id); - self.add_type(item, scope); - } - } - } - - #[track_caller] - fn build_node_scopes(&mut self, node: StmtId, scope: &mut Scope<'db>) { - let nodes = self.function.node_store(); - self.scopes.scope_by_node.insert(node, *scope); - - match nodes.node_kind(node) { - NodeKind::LocalVar => { - let var_id = nodes.as_local_var(node).expect("LocalVar node mismatch"); - let var = nodes.local_var(var_id); - self.add_type(var.ty, *scope); - if var.initializer != ExprId::ZERO { - self.build_node_scopes(var.initializer.into(), scope); - } - if var.pattern != PatId::ZERO { - self.build_pattern_scopes(var.pattern, *scope); - } - - *scope = self.scope(*scope); - self.add_pattern_bindings(var.pattern, *scope, LocalBindingKind::Local); - } - NodeKind::AssignStmt => { - let (target, value) = - nodes.assign_stmt(nodes.as_assign_stmt(node).expect("AssignStmt mismatch")); - self.build_node_scopes(target.into(), scope); - self.build_node_scopes(value.into(), scope); - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(node).expect("Call node mismatch")); - self.build_node_scopes(callee.into(), scope); - for arg in args.iter() { - self.build_node_scopes(arg.into(), scope); - } - } - NodeKind::Field => { - let (expr, _) = nodes.field(nodes.as_field(node).expect("Field node mismatch")); - if expr != ExprId::ZERO { - self.build_node_scopes(expr.into(), scope); - } - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(node).expect("Binary node mismatch")); - self.build_node_scopes(binary.lhs.into(), scope); - self.build_node_scopes(binary.rhs.into(), scope); - } - NodeKind::Postfix => { - let postfix = nodes.postfix(nodes.as_postfix(node).expect("Postfix node mismatch")); - self.build_node_scopes(postfix.expr.into(), scope); - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(node).expect("Prefix node mismatch")); - self.build_node_scopes(prefix.expr.into(), scope); - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(node).expect("If node mismatch")); - self.build_node_scopes(if_expr.cond.into(), scope); - if if_expr.then_branch != ExprId::ZERO { - self.build_node_scopes(if_expr.then_branch.into(), scope); - } - if if_expr.else_branch != ExprId::ZERO { - self.build_node_scopes(if_expr.else_branch.into(), scope); - } - } - NodeKind::Match => { - let (scrutinee, arms) = nodes.match_expr(nodes.as_match(node).expect("Match node")); - if scrutinee != ExprId::ZERO { - self.build_node_scopes(scrutinee.into(), scope); - } - for arm in arms.iter() { - let (pattern, expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm node mismatch")); - if pattern != PatId::ZERO { - self.build_pattern_scopes(pattern, *scope); - } - let mut arm_scope = self.scope(*scope); - self.add_pattern_bindings(pattern, arm_scope, LocalBindingKind::Local); - if expr != ExprId::ZERO { - self.build_node_scopes(expr.into(), &mut arm_scope); - } - } - } - NodeKind::Closure => { - let (params, body) = - nodes.closure_parts(nodes.as_closure(node).expect("Closure node mismatch")); - let mut closure_scope = self.scope(*scope); - self.add_bindings(params.iter(), closure_scope); - if body != ExprId::ZERO { - self.build_node_scopes(body.into(), &mut closure_scope); - } - } - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(node).expect("Tuple node mismatch")); - for item in tuple.iter() { - self.build_node_scopes(item.into(), scope); - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(node).expect("Array node mismatch")); - for item in array.iter() { - self.build_node_scopes(item.into(), scope); - } - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(node).expect("ArrayRepeat mismatch")); - self.build_node_scopes(value.into(), scope); - self.build_node_scopes(len.into(), scope); - } - NodeKind::StructExpr => { - let struct_expr = nodes - .struct_expr(nodes.as_struct_expr(node).expect("StructExpr node mismatch")); - for item in struct_expr.iter() { - self.build_node_scopes(item.into(), scope); - } - } - NodeKind::Block => { - let scope = &mut self.scope(*scope); - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(node).expect("Block node mismatch")); - - for stmt in stmts.iter() { - self.build_node_scopes(stmt, scope); - } - - if tail != ExprId::ZERO { - self.build_node_scopes(tail.into(), scope); - } - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(node).expect("LoopExpr node mismatch")); - if body != ExprId::ZERO { - let scope = &mut self.scope(*scope); - self.build_node_scopes(body.into(), scope); - } - } - NodeKind::UnsafeBlock => { - let (body, _) = nodes - .unsafe_block(nodes.as_unsafe_block(node).expect("UnsafeBlock node mismatch")); - if body != ExprId::ZERO { - let scope = &mut self.scope(*scope); - self.build_node_scopes(body.into(), scope); - } - } - _ => {} - } - } - - fn build(mut self) -> ExprScopes<'db> { - let mut scope = self.root_scope(); - - self.add_bindings(self.function.params().iter().copied(), scope); - self.add_type(self.function.ret_type(), scope); - if self.function.body() != ExprId::ZERO { - self.build_node_scopes(self.function.body().into(), &mut scope); - } - - self.scopes - } - - #[track_caller] - fn add_bindings(&mut self, params: impl IntoIterator, scope: Scope<'db>) { - let nodes = self.function.node_store(); - for param in params { - let (pattern, ty_id) = nodes.param(param); - self.add_type(ty_id, scope); - if pattern != PatId::ZERO { - self.build_pattern_scopes(pattern, scope); - self.add_pattern_bindings(pattern, scope, LocalBindingKind::Param); - } - } - } - - fn add_pattern_bindings(&mut self, pattern: PatId, scope: Scope<'db>, kind: LocalBindingKind) { - if pattern == PatId::ZERO { - return; - } - for name in self.function.node_store().pattern_binding_names(pattern) { - let binding = match kind { - LocalBindingKind::Local => BindingId::Local(name), - LocalBindingKind::Param => BindingId::Param(name), - }; - self.add_binding(name, binding, scope); - } - } - - fn build_pattern_scopes(&mut self, pattern: PatId, scope: Scope<'db>) { - if pattern == PatId::ZERO { - return; - } - - let nodes = self.function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatTyped => { - let (inner, ty) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - self.add_type(ty, scope); - self.build_pattern_scopes(inner, scope); - } - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.build_pattern_scopes(inner, scope); - } - NodeKind::PatTuple => { - for item in nodes.pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")).iter() { - self.build_pattern_scopes(item, scope); - } - } - NodeKind::PatVariant => { - let (path, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - if path != ExprId::ZERO { - let mut head_scope = scope; - self.build_node_scopes(path.into(), &mut head_scope); - } - for arg in args.iter() { - self.build_pattern_scopes(arg, scope); - } - } - NodeKind::PatStruct => { - let (path, fields) = - nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - if path != ExprId::ZERO { - let mut head_scope = scope; - self.build_node_scopes(path.into(), &mut head_scope); - } - for field in fields.iter() { - let (_, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - if pat != PatId::ZERO { - self.build_pattern_scopes(pat, scope); - } - } - } - NodeKind::PatBinding - | NodeKind::PatWildcard - | NodeKind::PatTrue - | NodeKind::PatFalse - | NodeKind::PatInt - | NodeKind::PatFloat - | NodeKind::PatString - | NodeKind::PatChar => {} - _ => {} - } - } -} diff --git a/crates/mitki-resolve/src/signature.rs b/crates/mitki-resolve/src/signature.rs deleted file mode 100644 index 528bbfa..0000000 --- a/crates/mitki-resolve/src/signature.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::fmt; - -use mitki_hir::hir::TyId; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::item::scope::{FunctionLocation, Signature, instantiate_nominal_type}; -use mitki_span::Symbol; -use rustc_hash::FxHashMap; -use salsa::Database; - -use crate::Resolver; - -pub struct SignatureTypeResolver<'db> { - db: &'db dyn Database, - location: FunctionLocation<'db>, - signature: &'db Signature<'db>, - type_params: FxHashMap, u32>, -} - -impl<'db> SignatureTypeResolver<'db> { - pub fn new( - db: &'db dyn Database, - location: FunctionLocation<'db>, - signature: &'db Signature<'db>, - ) -> Self { - let type_params = signature - .type_params(db) - .iter() - .enumerate() - .map(|(index, &name)| (name, index as u32)) - .collect(); - Self { db, location, signature, type_params } - } - - pub fn resolve(&self, ty: TyId) -> Result, SignatureTypeResolutionError> { - self.resolve_inner(ty) - } - - fn resolve_inner(&self, ty: TyId) -> Result, SignatureTypeResolutionError> { - if ty == TyId::ZERO { - return Ok(Ty::new(self.db, TyKind::Tuple(Vec::new()))); - } - - let nodes = self.signature.nodes(self.db); - - if let Some(tuple_id) = nodes.as_type_tuple(ty) { - let items = nodes - .type_tuple(tuple_id) - .iter() - .map(|item| self.resolve_inner(item)) - .collect::, _>>()?; - return Ok(Ty::new(self.db, TyKind::Tuple(items))); - } - - if let Some(array_id) = nodes.as_type_array(ty) { - let (item_ty, _) = nodes.type_array(array_id); - let item = self.resolve_inner(item_ty)?; - return Ok(Ty::new(self.db, TyKind::Array(item))); - } - - if let Some(ptr_id) = nodes.as_type_ptr_const(ty) { - let (item_ty, _) = nodes.type_ptr_const(ptr_id); - let item = self.resolve_inner(item_ty)?; - return Ok(Ty::new(self.db, TyKind::Pointer { mutable: false, pointee: item })); - } - - if let Some(ptr_id) = nodes.as_type_ptr_mut(ty) { - let (item_ty, _) = nodes.type_ptr_mut(ptr_id); - let item = self.resolve_inner(item_ty)?; - return Ok(Ty::new(self.db, TyKind::Pointer { mutable: true, pointee: item })); - } - - if let Some(function_id) = nodes.as_type_function(ty) { - let (inputs_ty, output_ty) = nodes.type_function(function_id); - let inputs = if let Some(tuple_id) = nodes.as_type_tuple(inputs_ty) { - nodes - .type_tuple(tuple_id) - .iter() - .map(|item| self.resolve_inner(item)) - .collect::, _>>()? - } else if inputs_ty == TyId::ZERO { - Vec::new() - } else { - vec![self.resolve_inner(inputs_ty)?] - }; - let output = self.resolve_inner(output_ty)?; - return Ok(Ty::new(self.db, TyKind::Function { inputs, output })); - } - - if let Some(union_id) = nodes.as_type_union(ty) { - let (lhs_ty, rhs_ty) = nodes.type_union(union_id); - let lhs = self.resolve_inner(lhs_ty)?; - let rhs = self.resolve_inner(rhs_ty)?; - return Ok(Ty::new(self.db, TyKind::Union(vec![lhs, rhs]))); - } - - if let Some(inter_id) = nodes.as_type_inter(ty) { - let (lhs_ty, rhs_ty) = nodes.type_inter(inter_id); - let lhs = self.resolve_inner(lhs_ty)?; - let rhs = self.resolve_inner(rhs_ty)?; - return Ok(Ty::new(self.db, TyKind::Inter(vec![lhs, rhs]))); - } - - if let Some(record_id) = nodes.as_type_record(ty) { - let fields = nodes - .type_record(record_id) - .iter() - .filter_map(|field_ty| { - let field_id = nodes.as_type_field(field_ty)?; - let (name_id, field_ty) = nodes.type_field(field_id); - Some((nodes.name(name_id), field_ty)) - }) - .map(|(name, field_ty)| { - self.resolve_inner(field_ty).map(|field_ty| (name, field_ty)) - }) - .collect::, _>>()?; - return Ok(Ty::new(self.db, TyKind::Record(fields))); - } - - if let Some(type_apply) = nodes.as_type_apply(ty) { - let (path_ty, args_ty) = nodes.type_apply(type_apply); - let base = self.resolve_inner(path_ty)?; - let args = if let Some(tuple_id) = nodes.as_type_tuple(args_ty) { - nodes - .type_tuple(tuple_id) - .iter() - .map(|arg| self.resolve_inner(arg)) - .collect::, _>>()? - } else { - Vec::new() - }; - - return instantiate_nominal_type(self.db, base, args) - .ok_or(SignatureTypeResolutionError::UnsupportedNode); - } - - let Some(path_id) = nodes.as_type_path(ty) else { - return Err(SignatureTypeResolutionError::UnsupportedNode); - }; - let name = nodes.type_ref(path_id); - - if let Some(&id) = self.type_params.get(&name) { - return Ok(Ty::new(self.db, TyKind::Var(id))); - } - - let resolver = Resolver::new(self.db, self.location); - resolver - .resolve_type_binding(name) - .and_then(|binding| resolver.ty_for_binding(binding)) - .ok_or_else(|| SignatureTypeResolutionError::UnresolvedPath(name.text(self.db).into())) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SignatureTypeResolutionError { - UnsupportedNode, - UnresolvedPath(Box), -} - -impl fmt::Display for SignatureTypeResolutionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::UnsupportedNode => write!(f, "unsupported type node in function signature"), - Self::UnresolvedPath(path) => { - write!(f, "could not resolve signature type `{path}`") - } - } - } -} diff --git a/crates/mitki-span/Cargo.toml b/crates/mitki-span/Cargo.toml deleted file mode 100644 index 37014cc..0000000 --- a/crates/mitki-span/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "mitki-span" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -salsa.workspace = true diff --git a/crates/mitki-span/src/lib.rs b/crates/mitki-span/src/lib.rs deleted file mode 100644 index 9749659..0000000 --- a/crates/mitki-span/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod symbol; - -pub use symbol::{IntoSymbol, Symbol}; diff --git a/crates/mitki-span/src/symbol.rs b/crates/mitki-span/src/symbol.rs deleted file mode 100644 index b92d4c3..0000000 --- a/crates/mitki-span/src/symbol.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[salsa::interned(debug)] -pub struct Symbol<'db> { - #[returns(deref)] - pub text: Box, -} - -pub trait IntoSymbol<'db> { - fn into_symbol(self, db: &'db dyn salsa::Database) -> Symbol<'db>; -} - -impl<'db, T> IntoSymbol<'db> for T -where - T: salsa::Lookup> + std::hash::Hash, - Box: salsa::HashEqLike, -{ - fn into_symbol(self, db: &'db dyn salsa::Database) -> Symbol<'db> { - Symbol::new(db, self) - } -} diff --git a/crates/mitki-tokenizer/Cargo.toml b/crates/mitki-tokenizer/Cargo.toml deleted file mode 100644 index 81473b0..0000000 --- a/crates/mitki-tokenizer/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "mitki-tokenizer" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] -ascii = "1.1.0" -memchr = "2.7.4" -mitki-yellow.workspace = true -text-size.workspace = true - -[dev-dependencies] -quickcheck = "1.0" diff --git a/crates/mitki-tokenizer/src/cursor.rs b/crates/mitki-tokenizer/src/cursor.rs deleted file mode 100644 index a6272df..0000000 --- a/crates/mitki-tokenizer/src/cursor.rs +++ /dev/null @@ -1,263 +0,0 @@ -use std::str::Chars; - -use text_size::{TextLen as _, TextSize}; - -pub(crate) const EOF_CHAR: char = '\0'; - -#[derive(Clone)] -pub(crate) struct Cursor<'text> { - chars: Chars<'text>, - len: TextSize, - previous: char, -} - -impl<'text> Cursor<'text> { - pub(crate) fn new(text: &'text str) -> Self { - Self { chars: text.chars(), len: text.text_len(), previous: '\0' } - } - - fn as_str(&self) -> &'text str { - self.chars.as_str() - } - - pub(crate) fn is_eof(&self) -> bool { - self.chars.as_str().is_empty() - } - - pub(crate) fn len(&self) -> TextSize { - TextSize::new(self.chars.as_str().len() as u32) - } - - pub(crate) fn previous(&self) -> char { - self.previous - } - - pub(crate) fn pos_within_token(&self) -> TextSize { - self.len - self.len() - } - - pub(crate) fn reset_pos_within_token(&mut self) { - self.len = self.len(); - } - - pub(crate) fn matches(&self, ch: char) -> bool { - self.peek() == ch - } - - pub(crate) fn peek(&self) -> char { - self.chars.clone().next().unwrap_or(EOF_CHAR) - } - - pub(crate) fn second(&self) -> char { - let mut chars = self.chars.clone(); - chars.next(); - chars.next().unwrap_or(EOF_CHAR) - } - - pub(crate) fn advance(&mut self) -> char { - self.previous = self.chars.next().unwrap_or(EOF_CHAR); - self.previous - } - - pub(crate) fn advance_until(&mut self, ch: ascii::AsciiChar) { - if let Some(index) = memchr::memchr(ch as u8, self.as_str().as_bytes()) { - let (prefix, suffix) = - unsafe { self.chars.as_str().split_at_checked(index).unwrap_unchecked() }; - self.previous = prefix.chars().last().unwrap_or(EOF_CHAR); - self.chars = suffix.chars(); - } else { - let chars = std::mem::replace(&mut self.chars, "".chars()); - self.previous = chars.last().unwrap_or(EOF_CHAR); - } - } - - pub(crate) fn advance_while(&mut self, f: impl Fn(char) -> bool + Copy) { - while !self.is_eof() && f(self.peek()) { - self.advance(); - } - } -} - -#[cfg(test)] -mod tests { - use ascii::AsciiChar; - - use super::*; - - #[test] - fn test_previous_initial() { - let cursor = Cursor::new("a"); - assert_eq!(cursor.previous(), '\0'); - } - - #[test] - fn test_advance_and_previous() { - let mut cursor = Cursor::new("ab"); - let first = cursor.advance(); - assert_eq!(first, 'a'); - assert_eq!(cursor.previous(), 'a'); - assert_eq!(cursor.peek(), 'b'); - - let second = cursor.advance(); - assert_eq!(second, 'b'); - assert_eq!(cursor.previous(), 'b'); - assert_eq!(cursor.peek(), EOF_CHAR); - } - - #[test] - fn test_advance_on_empty_returns_eof() { - let mut empty = Cursor::new(""); - let adv = empty.advance(); - assert_eq!(adv, EOF_CHAR); - assert_eq!(empty.previous(), EOF_CHAR); - assert!(empty.is_eof()); - } - - #[test] - fn test_as_str_after_advance() { - let mut cursor = Cursor::new("rustacean"); - cursor.advance(); - assert_eq!(cursor.as_str(), "ustacean"); - } - - #[test] - fn test_peek_and_matches_and_second() { - let cursor = Cursor::new("foo"); - assert_eq!(cursor.peek(), 'f'); - assert!(cursor.matches('f')); - assert!(!cursor.matches('z')); - assert_eq!(cursor.second(), 'o'); - - let single = Cursor::new("x"); - assert_eq!(single.second(), EOF_CHAR); - } - - #[test] - fn test_second_does_not_consume() { - let cursor = Cursor::new("abc"); - let sec = cursor.second(); - assert_eq!(sec, 'b'); - assert_eq!(cursor.peek(), 'a'); - assert_eq!(cursor.previous(), '\0'); - } - - #[test] - fn test_advance_until_found_mid_string() { - let mut cursor = Cursor::new("hello, world"); - cursor.advance_until(AsciiChar::Comma); - assert_eq!(cursor.peek(), ','); - assert_eq!(cursor.previous(), 'o'); - assert_eq!(cursor.as_str(), ", world"); - } - - #[test] - fn test_advance_until_none_returns_last_char() { - let mut cursor = Cursor::new("abc"); - cursor.advance_until(AsciiChar::z); - assert!(cursor.is_eof()); - assert_eq!(cursor.previous(), 'c'); - } - - #[test] - fn test_advance_until_multiple_occurrences() { - let mut cursor = Cursor::new(",a,b"); - cursor.advance_until(AsciiChar::Comma); - assert_eq!(cursor.peek(), ','); - assert_eq!(cursor.previous(), EOF_CHAR); - cursor.advance(); - cursor.advance_until(AsciiChar::Comma); - assert_eq!(cursor.peek(), ','); - assert_eq!(cursor.previous(), 'a'); - } - - #[test] - fn test_advance_while() { - let mut cursor = Cursor::new("abc123"); - cursor.advance_while(|c| c.is_alphabetic()); - assert_eq!(cursor.peek(), '1'); - assert_eq!(cursor.previous(), 'c'); - - let mut empty = Cursor::new(""); - empty.advance_while(|c| c.is_ascii()); - assert!(empty.is_eof()); - assert_eq!(empty.previous(), EOF_CHAR); - } - - #[test] - fn test_is_eof_initial_and_after() { - let cursor = Cursor::new(""); - assert!(cursor.is_eof()); - - let mut cursor2 = Cursor::new("x"); - assert!(!cursor2.is_eof()); - cursor2.advance(); - assert!(cursor2.is_eof()); - } - - #[test] - fn test_len_pos_within_token_and_reset() { - let mut cursor = Cursor::new("hello"); - assert_eq!(cursor.len(), TextSize::new(5)); - assert_eq!(cursor.pos_within_token(), TextSize::new(0)); - - cursor.advance(); - assert_eq!(cursor.len(), TextSize::new(4)); - assert_eq!(cursor.pos_within_token(), TextSize::new(1)); - - cursor.reset_pos_within_token(); - assert_eq!(cursor.pos_within_token(), TextSize::new(0)); - } - - #[test] - fn test_unicode_handling() { - let s = "💖rust"; - let mut cursor = Cursor::new(s); - assert_eq!(cursor.peek(), '💖'); - let first = cursor.advance(); - assert_eq!(first, '💖'); - assert_eq!(cursor.peek(), 'r'); - assert_eq!(cursor.len(), TextSize::new((s.len() - first.len_utf8()) as u32)); - } - - #[test] - fn test_matches_false_on_empty() { - let cursor = Cursor::new(""); - assert!(!cursor.matches('a')); - } - - #[derive(Clone, Copy, Debug)] - struct Char(AsciiChar); - - impl quickcheck::Arbitrary for Char { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let byte: u8 = u8::arbitrary(g) % 128; - Self(AsciiChar::from_ascii(byte).unwrap()) - } - } - - quickcheck::quickcheck! { - fn prop_len_plus_pos_equals_initial(text: String) -> bool { - let mut cursor = Cursor::new(&text); - let initial_len = cursor.len(); - cursor.advance_while(|c| c != ' '); - let sum = cursor.len() + cursor.pos_within_token(); - sum == initial_len - } - - fn prop_advance_until_preserves_invariants(text: String, c: Char) -> bool { - let mut cursor = Cursor::new(&text); - let initial = cursor.len(); - cursor.advance_until(c.0); - let sum = cursor.len() + cursor.pos_within_token(); - sum == initial - } - - fn prop_clone_does_not_affect_original(text: String) -> bool { - let a = Cursor::new(&text); - let mut b = a.clone(); - let peek_before = a.peek(); - b.advance(); - a.peek() == peek_before - } - } -} diff --git a/crates/mitki-tokenizer/src/lib.rs b/crates/mitki-tokenizer/src/lib.rs deleted file mode 100644 index 1b7e142..0000000 --- a/crates/mitki-tokenizer/src/lib.rs +++ /dev/null @@ -1,937 +0,0 @@ -mod cursor; - -use std::sync::Arc; - -use ascii::AsciiChar; -use cursor::{Cursor, EOF_CHAR}; -pub use mitki_yellow::SyntaxKind; -use mitki_yellow::SyntaxKind::*; -use mitki_yellow::{TriviaPiece, TriviaPieceKind}; -use text_size::{TextRange, TextSize}; - -#[derive(Debug, Clone, Copy)] -pub struct Token { - pub kind: SyntaxKind, - pub kind_range: TextRange, - pub leading: TriviaRange, - pub trailing: TriviaRange, - leading_has_newline: bool, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct TriviaRange { - pub start: u32, - pub len: u32, -} - -impl Token { - pub fn on_same_line(&self) -> bool { - !self.leading_has_newline - } -} - -impl TriviaRange { - fn new(start: usize, len: usize) -> Self { - let start = u32::try_from(start).expect("trivia start overflow"); - let len = u32::try_from(len).expect("trivia len overflow"); - Self { start, len } - } - - fn range(self) -> std::ops::Range { - let start = self.start as usize; - let end = start + self.len as usize; - start..end - } -} - -#[derive(Clone)] -pub enum Diagnostic { - InconsistentWhitespaceAroundEqual(TextRange), -} - -#[derive(PartialEq, Eq, Clone, Copy)] -enum TriviaMode { - Normal, - NoNewlines, -} - -pub type TokenIndex = u32; - -#[derive(Clone)] -pub struct Tokenizer { - kinds: Arc<[SyntaxKind]>, - kind_ranges: Arc<[TextRange]>, - leading_ranges: Arc<[TriviaRange]>, - trailing_ranges: Arc<[TriviaRange]>, - leading_has_newline: Arc<[bool]>, - trivia: Arc<[TriviaPiece]>, - diagnostics: Arc<[Diagnostic]>, - position: TokenIndex, -} - -impl Tokenizer { - pub fn new(text: &str) -> Self { - let mut lexer = Lexer::new(text); - let mut kinds = Vec::new(); - let mut kind_ranges = Vec::new(); - let mut leading_ranges = Vec::new(); - let mut trailing_ranges = Vec::new(); - let mut leading_has_newline = Vec::new(); - - loop { - let token = lexer.next_token(); - let is_eof = token.kind == EOF; - kinds.push(token.kind); - kind_ranges.push(token.kind_range); - leading_ranges.push(token.leading); - trailing_ranges.push(token.trailing); - leading_has_newline.push(token.leading_has_newline); - if is_eof { - break; - } - } - - let Lexer { diagnostics, trivia, .. } = lexer; - - Self { - kinds: Arc::from(kinds), - kind_ranges: Arc::from(kind_ranges), - leading_ranges: Arc::from(leading_ranges), - trailing_ranges: Arc::from(trailing_ranges), - leading_has_newline: Arc::from(leading_has_newline), - trivia: Arc::from(trivia), - diagnostics: Arc::from(diagnostics), - position: 0, - } - } - - pub fn peek(&self) -> Token { - self.token_at(self.position) - } - - pub fn next_token(&mut self) -> Token { - let token = self.peek(); - if token.kind != EOF { - self.position += 1; - } - token - } - - pub fn next_token_index(&mut self) -> TokenIndex { - let index = self.position; - if self.kinds[index as usize] != EOF { - self.position += 1; - } - index - } - - pub fn token(&self, index: TokenIndex) -> Token { - self.token_at(index) - } - - pub fn leading_trivia(&self, index: TokenIndex) -> &[TriviaPiece] { - let range = self.leading_ranges[index as usize]; - self.trivia_range(range) - } - - pub fn trailing_trivia(&self, index: TokenIndex) -> &[TriviaPiece] { - let range = self.trailing_ranges[index as usize]; - self.trivia_range(range) - } - - pub fn diagnostics(&self) -> &[Diagnostic] { - self.diagnostics.as_ref() - } - - fn trivia_range(&self, range: TriviaRange) -> &[TriviaPiece] { - let range = range.range(); - &self.trivia[range] - } - - fn token_at(&self, index: TokenIndex) -> Token { - let index = index as usize; - Token { - kind: self.kinds[index], - kind_range: self.kind_ranges[index], - leading: self.leading_ranges[index], - trailing: self.trailing_ranges[index], - leading_has_newline: self.leading_has_newline[index], - } - } -} - -struct Lexer<'db> { - text: &'db str, - cursor: Cursor<'db>, - trivia: Vec, - diagnostics: Vec, -} - -impl<'db> Lexer<'db> { - fn new(text: &'db str) -> Self { - Self { - text, - cursor: Cursor::new(text), - trivia: Vec::with_capacity(4), - diagnostics: Vec::new(), - } - } - - fn next_token(&mut self) -> Token { - let (leading, leading_has_newline) = self.trivia(TriviaMode::Normal); - let (kind, kind_range) = self.syntax_kind(); - let (trailing, _) = self.trivia(TriviaMode::NoNewlines); - - Token { kind, kind_range, leading, trailing, leading_has_newline } - } - - fn offset(&self) -> TextSize { - TextSize::new(self.text.len() as u32) - self.cursor.len() - } - - fn range(&self) -> TextRange { - let end = self.offset(); - let len = self.cursor.pos_within_token(); - - TextRange::at(end - len, len) - } - - fn text(&self) -> &'db str { - &self.text[self.range()] - } - - fn trivia(&mut self, mode: TriviaMode) -> (TriviaRange, bool) { - let start = self.trivia.len(); - let mut has_newline = false; - loop { - let kind = match self.cursor.peek() { - '/' if self.cursor.second() == '/' => { - self.cursor.advance_until(AsciiChar::LineFeed); - TriviaPieceKind::SingleLineComment - } - '\n' | '\r' if mode == TriviaMode::Normal => { - self.cursor.advance_while(|ch| matches!(ch, '\n' | '\r')); - TriviaPieceKind::Newline - } - first_char => { - if matches!(first_char, ' ' | '\t') { - self.cursor.advance_while(|ch| ch.is_ascii_whitespace()); - TriviaPieceKind::Whitespace - } else { - break; - } - } - }; - - if kind == TriviaPieceKind::Newline { - has_newline = true; - } - - self.trivia.push(TriviaPiece::new(kind, self.cursor.pos_within_token())); - self.cursor.reset_pos_within_token(); - } - - let len = self.trivia.len() - start; - (TriviaRange::new(start, len), has_newline) - } - - fn syntax_kind(&mut self) -> (SyntaxKind, TextRange) { - let previous = self.cursor.previous(); - - let kind = match self.cursor.advance() { - '(' => LEFT_PAREN, - ')' => RIGHT_PAREN, - '[' => LEFT_BRACKET, - ']' => RIGHT_BRACKET, - '{' => LEFT_BRACE, - '}' => RIGHT_BRACE, - ':' => { - if self.cursor.peek() == ':' { - self.cursor.advance(); - DOUBLE_COLON - } else { - COLON - } - } - ',' => COMMA, - ';' => SEMICOLON, - '"' => self.string(), - '\'' => self.char_literal(), - first_char @ '0'..='9' => self.number(first_char), - 'A'..='Z' | 'a'..='z' | '_' => self.identifier_or_keyword(), - EOF_CHAR => EOF, - first_char => { - if is_operator(first_char) { - self.operator(previous) - } else { - UNKNOWN - } - } - }; - - let range = self.range(); - self.cursor.reset_pos_within_token(); - - (kind, range) - } - - fn identifier_or_keyword(&mut self) -> SyntaxKind { - self.cursor.advance_while(|c| c.is_ascii_alphanumeric() || c == '_'); - - match self.text() { - "fun" => FUN_KW, - "if" => IF_KW, - "else" => ELSE_KW, - "loop" => LOOP_KW, - "val" => VAL_KW, - "var" => VAR_KW, - "while" => WHILE_KW, - "return" => RETURN_KW, - "break" => BREAK_KW, - "continue" => CONTINUE_KW, - "pub" => PUB_KW, - "use" => USE_KW, - "as" => AS_KW, - "true" => TRUE_KW, - "false" => FALSE_KW, - "in" => IN_KW, - "match" => MATCH_KW, - "struct" => STRUCT_KW, - "enum" => ENUM_KW, - "mod" => MOD_KW, - _ => NAME, - } - } - - fn operator(&mut self, previous: char) -> SyntaxKind { - self.cursor.advance_while(is_operator); - - let left_bound = match previous { - '(' | '[' | '{' | ',' | ':' | ';' => false, - EOF_CHAR => false, - prev => !prev.is_ascii_whitespace(), - }; - - let right_bound = match self.cursor.peek() { - ')' | ']' | '}' | ',' | ':' | ';' => false, - '.' => !left_bound, - EOF_CHAR => false, - peeked => !peeked.is_ascii_whitespace(), - }; - - match self.text() { - "=>" => FAT_ARROW, - "=" => { - if left_bound != right_bound { - self.diagnostics - .push(Diagnostic::InconsistentWhitespaceAroundEqual(self.range())); - } - - EQ - } - "." => DOT, - _ => { - if left_bound == right_bound { - BINARY_OPERATOR - } else if left_bound { - POSTFIX_OPERATOR - } else { - PREFIX_OPERATOR - } - } - } - } - - fn number(&mut self, c: char) -> SyntaxKind { - if c == '0' { - match self.cursor.peek() { - 'b' | 'o' => { - self.cursor.advance(); - self.digits(false); - } - 'x' => { - self.cursor.advance(); - self.digits(true); - } - '0'..='9' | '_' | '.' | 'e' | 'E' => { - self.digits(false); - } - _ => return INT_NUMBER, - } - } else { - self.digits(false); - } - - if self.cursor.matches('.') && self.cursor.second() != '.' { - self.cursor.advance(); - self.digits(false); - self.float_exponent(); - return FLOAT_NUMBER; - } - - if self.cursor.matches('e') || self.cursor.matches('E') { - self.float_exponent(); - return FLOAT_NUMBER; - } - - INT_NUMBER - } - - fn digits(&mut self, allow_hex: bool) { - loop { - match self.cursor.peek() { - '_' | '0'..='9' => { - self.cursor.advance(); - } - 'a'..='f' | 'A'..='F' if allow_hex => { - self.cursor.advance(); - } - _ => return, - } - } - } - - fn string(&mut self) -> SyntaxKind { - while !self.cursor.is_eof() { - match self.cursor.peek() { - '"' => { - self.cursor.advance(); - break; - } - '\\' => { - self.cursor.advance(); - self.cursor.advance(); - } - _ => { - self.cursor.advance(); - } - } - } - STRING - } - - fn char_literal(&mut self) -> SyntaxKind { - while !self.cursor.is_eof() { - match self.cursor.peek() { - '\'' => { - self.cursor.advance(); - break; - } - '\\' => { - self.cursor.advance(); - self.cursor.advance(); - } - _ => { - self.cursor.advance(); - } - } - } - CHAR - } - - fn float_exponent(&mut self) { - if self.cursor.matches('e') || self.cursor.matches('E') { - self.cursor.advance(); - if self.cursor.matches('-') || self.cursor.matches('+') { - self.cursor.advance(); - } - self.digits(false); - } - } -} - -fn is_operator(c: char) -> bool { - matches!( - c, - '/' | '=' | '-' | '+' | '*' | '%' | '<' | '>' | '!' | '&' | '|' | '^' | '~' | '.' | '?' - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn token_text<'a>(token: &Token, text: &'a str) -> &'a str { - &text[token.kind_range] - } - - #[test] - fn test_integer_literals() { - let inputs = vec![ - ("123", INT_NUMBER), - ("0", INT_NUMBER), - ("0b1010", INT_NUMBER), - ("0o755", INT_NUMBER), - ("0x1f", INT_NUMBER), - ("123_456", INT_NUMBER), - ]; - - for (input, expected_kind) in inputs { - let mut tokenizer = Tokenizer::new(input); - let kind = tokenizer.next_token().kind; - assert_eq!(kind, expected_kind, "Input: '{input}'"); - assert_eq!( - tokenizer.peek().kind, - EOF, - "Tokenizer did not consume all input for '{input}'" - ); - } - } - - #[test] - fn test_float_literals() { - let inputs = vec![ - ("123.456", FLOAT_NUMBER), - ("0.0", FLOAT_NUMBER), - ("1e10", FLOAT_NUMBER), - ("1.0e-5", FLOAT_NUMBER), - ("123_456.789_012", FLOAT_NUMBER), - ]; - - for (input, expected_kind) in inputs { - let mut tokenizer = Tokenizer::new(input); - let kind = tokenizer.next_token().kind; - assert_eq!(kind, expected_kind, "Input: '{input}'"); - assert_eq!( - tokenizer.peek().kind, - EOF, - "Tokenizer did not consume all input for '{input}'" - ); - } - } - - #[test] - fn test_string_literals() { - let inputs = vec![("\"hello\"", STRING), ("\"\"", STRING), ("\"foo\\\"bar\"", STRING)]; - - for (input, expected_kind) in inputs { - let mut tokenizer = Tokenizer::new(input); - let kind = tokenizer.next_token().kind; - assert_eq!(kind, expected_kind, "Input: '{input}'"); - assert_eq!( - tokenizer.peek().kind, - EOF, - "Tokenizer did not consume all input for '{input}'" - ); - } - } - - #[test] - fn test_char_literals() { - let inputs = vec![("'a'", CHAR), ("'\\n'", CHAR), ("'\\''", CHAR)]; - - for (input, expected_kind) in inputs { - let mut tokenizer = Tokenizer::new(input); - let kind = tokenizer.next_token().kind; - assert_eq!(kind, expected_kind, "Input: '{input}'"); - assert_eq!( - tokenizer.peek().kind, - EOF, - "Tokenizer did not consume all input for '{input}'" - ); - } - } - - #[test] - fn test_eq_operator() { - let text = "x = y"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "x"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, EQ); - assert_eq!(token_text(&token, text), "="); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "y"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_eq_operator_without_whitespace() { - let text = "x=y"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "x"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, EQ); - assert_eq!(token_text(&token, text), "="); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "y"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_chained_eq_operator() { - let text = "x = y = z"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "x"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, EQ); - assert_eq!(token_text(&token, text), "="); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "y"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, EQ); - assert_eq!(token_text(&token, text), "="); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "z"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_double_eq_operator() { - let text = "x == y"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "x"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "=="); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "y"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_dot_operator() { - let text = "object.property"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "object"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, DOT); - assert_eq!(token_text(&token, text), "."); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "property"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_binary_operator() { - let text = "a+b"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "+"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_prefix_operator() { - let text = "-a"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_postfix_operator() { - let text = "a++"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, POSTFIX_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_binary_operator_with_whitespace() { - let text = "a + b"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "+"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_binary_operator_with_whitespace_as_prefix() { - let text = "- a"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_binary_operator_with_whitespace_as_postfix() { - let text = "a ++"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_complex_operator_sequences() { - let text = "-a * b++ / ++c - d--"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "*"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, POSTFIX_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "/"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "c"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "d"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, POSTFIX_OPERATOR); - assert_eq!(token_text(&token, text), "--"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_operator_precedence() { - let text = "!a && b || c"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "!"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "&&"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "||"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "c"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_operator_with_parentheses() { - let text = "(-a) + (b++)"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, LEFT_PAREN); - assert_eq!(token_text(&token, text), "("); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, RIGHT_PAREN); - assert_eq!(token_text(&token, text), ")"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "+"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, LEFT_PAREN); - assert_eq!(token_text(&token, text), "("); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, POSTFIX_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, RIGHT_PAREN); - assert_eq!(token_text(&token, text), ")"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } - - #[test] - fn test_mixed_operator_contexts() { - let text = "a * -b + c++ - --d"; - let mut tokenizer = Tokenizer::new(text); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "a"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "*"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "b"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "+"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "c"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, POSTFIX_OPERATOR); - assert_eq!(token_text(&token, text), "++"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, BINARY_OPERATOR); - assert_eq!(token_text(&token, text), "-"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, PREFIX_OPERATOR); - assert_eq!(token_text(&token, text), "--"); - - let token = tokenizer.next_token(); - assert_eq!(token.kind, NAME); - assert_eq!(token_text(&token, text), "d"); - - let eof_token = tokenizer.next_token(); - assert_eq!(eof_token.kind, EOF); - } -} diff --git a/crates/mitki-typeck/Cargo.toml b/crates/mitki-typeck/Cargo.toml deleted file mode 100644 index 5f3330a..0000000 --- a/crates/mitki-typeck/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "mitki-typeck" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -mitki-hir.workspace = true -mitki-lower.workspace = true -mitki-resolve.workspace = true -mitki-span.workspace = true -rustc-hash.workspace = true -salsa.workspace = true diff --git a/crates/mitki-typeck/src/infer.rs b/crates/mitki-typeck/src/infer.rs deleted file mode 100644 index c61118d..0000000 --- a/crates/mitki-typeck/src/infer.rs +++ /dev/null @@ -1,4481 +0,0 @@ -use mitki_hir::hir::{ExprId, Function, NameId, NodeKind, NodeStore, PatId, StmtId, TyId}; -use mitki_hir::ty::{EnumTy, ExactInt, StructTy, Ty, TyKind}; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{ - FunctionLocation, HasVisibleItems as _, TypeDeclaration, enum_variants, - instantiate_nominal_type, struct_fields, -}; -use mitki_resolve::{BindingId, CompilerIntrinsic, Resolver, resolve_method_for_receiver}; -use mitki_span::{IntoSymbol as _, Symbol}; -use rustc_hash::{FxHashMap, FxHashSet}; -use salsa::Database; -use salsa::plumbing::{AsId as _, FromId as _}; - -pub trait Inferable<'db> { - fn infer(self, db: &'db dyn Database) -> &'db Inference<'db>; -} - -fn comptime_result_supported(db: &dyn Database, ty: Ty<'_>) -> bool { - fn inner(db: &dyn Database, ty: Ty<'_>, seen: &mut FxHashSet) -> bool { - let bits = ty.as_id().as_bits(); - if !seen.insert(bits) { - return false; - } - - let supported = match ty.kind(db) { - TyKind::Bool - | TyKind::Float - | TyKind::Int - | TyKind::ExactInt(_) - | TyKind::String - | TyKind::Char => true, - TyKind::Array(item) => inner(db, *item, seen), - TyKind::Tuple(items) => items.iter().all(|&item| inner(db, item, seen)), - TyKind::Record(fields) => fields.iter().all(|(_, field_ty)| inner(db, *field_ty, seen)), - TyKind::Struct(struct_ty) => { - !struct_has_destructor(db, *struct_ty) - && struct_fields(db, *struct_ty) - .iter() - .all(|(_, field_ty)| inner(db, *field_ty, seen)) - } - TyKind::Enum(enum_ty) => { - !enum_has_destructor(db, *enum_ty) - && enum_variants(db, *enum_ty) - .iter() - .flat_map(|(_, fields)| fields.iter()) - .all(|&field_ty| inner(db, field_ty, seen)) - } - TyKind::Unknown - | TyKind::Function { .. } - | TyKind::Pointer { .. } - | TyKind::Var(_) - | TyKind::Union(_) - | TyKind::Inter(_) - | TyKind::ExternStruct(_) - | TyKind::Rec(_, _) => false, - }; - - seen.remove(&bits); - supported - } - - inner(db, ty, &mut FxHashSet::default()) -} - -fn struct_has_destructor(db: &dyn Database, struct_ty: StructTy<'_>) -> bool { - let Some(TypeDeclaration::Struct(location)) = - struct_ty.module(db).visible_items(db).get_type_declaration(&struct_ty.name(db)) - else { - return false; - }; - location.destructor(db).is_some() -} - -fn enum_has_destructor(db: &dyn Database, enum_ty: EnumTy<'_>) -> bool { - let Some(TypeDeclaration::Enum(location)) = - enum_ty.module(db).visible_items(db).get_type_declaration(&enum_ty.name(db)) - else { - return false; - }; - location.destructor(db).is_some() -} - -#[salsa::tracked] -impl<'db> Inferable<'db> for FunctionLocation<'db> { - #[salsa::tracked(returns(ref))] - fn infer(self, db: &'db dyn Database) -> Inference<'db> { - let hir_function = self.hir_function(db); - let function = hir_function.function(db); - let source_map = hir_function.source_map(db); - let resolver = Resolver::new(db, self); - Typer::new(db, function, source_map, resolver).build() - } -} - -#[derive(Debug, Default, PartialEq, Eq, salsa::Update)] -pub struct Inference<'db> { - type_of_node: FxHashMap>, - selected_union_members: FxHashMap>, - matched_typed_patterns: FxHashMap>, - diagnostics: Vec>, -} - -impl<'db> Inference<'db> { - pub fn type_of_node(&self, node: ExprId) -> Option> { - self.type_of_node.get(&node).copied() - } - - pub fn selected_union_member(&self, pattern: PatId) -> Option> { - self.selected_union_members.get(&pattern).copied() - } - - pub fn matched_typed_pattern(&self, pattern: PatId) -> Option> { - self.matched_typed_patterns.get(&pattern).copied() - } - - pub fn diagnostics(&self) -> &[Diagnostic<'db>] { - &self.diagnostics - } -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub struct Diagnostic<'db> { - kind: DiagnosticKind<'db>, - context: Option, -} - -impl<'db> Diagnostic<'db> { - fn new(kind: DiagnosticKind<'db>, context: Option) -> Self { - Self { kind, context } - } - - pub fn kind(&self) -> &DiagnosticKind<'db> { - &self.kind - } - - pub fn context(&self) -> Option { - self.context - } -} - -#[derive(Debug, PartialEq, Eq, salsa::Update)] -pub enum DiagnosticKind<'db> { - UnresolvedIdent(ExprId), - UnresolvedType(TyId, Symbol<'db>), - TypeMismatch(ExprId, Ty<'db>, Ty<'db>), - PatternTypeMismatch(PatId, Ty<'db>, Ty<'db>), - UnknownType(ExprId), - ExpectedValueFoundType(ExprId, Ty<'db>), - CallArityMismatch(ExprId, usize, usize), - CallNonFunction(ExprId, Ty<'db>), - ClosureArityMismatch(ExprId, usize, usize), - InvalidBinaryOp(ExprId, Symbol<'db>, Ty<'db>, Ty<'db>), - InvalidPrefixOp(ExprId, Symbol<'db>, Ty<'db>), - InvalidPostfixOp(ExprId, Symbol<'db>, Ty<'db>), - MissingElseBranch(ExprId), - MissingParameterType(ExprId), - MissingInitializer(ExprId), - TupleArityMismatch(ExprId, usize, usize), - MissingStructField(ExprId, Symbol<'db>), - UnknownStructField(ExprId, Symbol<'db>), - NotAStruct(ExprId, Ty<'db>), - NotAnEnum(ExprId, Ty<'db>), - DuplicatePatternBinding(ExprId), - AmbiguousUnionPattern(PatId, Ty<'db>), - RefutablePattern(ExprId), - UnsupportedFloatPattern(ExprId), - BreakOutsideLoop(ExprId), - ContinueOutsideLoop(ExprId), - CompilerIntrinsicMustBeCalled(ExprId, CompilerIntrinsic), - InvalidComptimeCall(ExprId), - ComptimeTargetMustBeZeroArg(ExprId), - ComptimeTargetMustNotBeGeneric(ExprId), - ComptimeTargetMustReturnSupportedType(ExprId, Ty<'db>), - ReflectionOnlyInComptime(ExprId, CompilerIntrinsic), - InvalidReflectionTarget(ExprId, CompilerIntrinsic, &'static str), - UnsafeOperationRequiresUnsafeContext(ExprId), - InvalidUnsafeIntrinsicArgument(ExprId, CompilerIntrinsic, &'static str), - InvalidUnsafeIntrinsicResult(ExprId, CompilerIntrinsic, &'static str), - InvalidAssignmentTarget(ExprId), - AssignmentRequiresMutable(ExprId), - MutableArgumentRequiresPlace(ExprId), -} - -type VarId = usize; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum InferTy { - Var(VarId), - Function(Vec, Box), - Array(Box), - Tuple(Vec), - Record(Vec<(u64, InferTy)>), - Union(Vec), - Inter(Vec), - /// A solved/interned type leaf, stored as raw salsa Id bits. - Known(u64), - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum NumericKind { - Int, - ExactInt(ExactInt), - Float, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum OrderableKind { - Int, - ExactInt(ExactInt), - Float, - Char, -} - -#[derive(Debug, Clone)] -enum Scheme { - Mono(InferTy), - Poly { level: usize, body: InferTy }, -} - -#[derive(Debug, Clone)] -struct VarState { - level: usize, - lower_bounds: Vec, - upper_bounds: Vec, -} - -#[derive(Debug, Clone)] -struct VariantConstraint<'db> { - enum_var: VarId, - variant: Symbol<'db>, - payload: Vec<(ExprId, InferTy)>, - name_node: ExprId, -} - -#[derive(Debug, Clone)] -struct DeferredCoercion { - node: ExprId, - actual: InferTy, - expected: InferTy, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum Polarity { - Positive, - Negative, -} - -impl Polarity { - fn flip(self) -> Self { - match self { - Polarity::Positive => Polarity::Negative, - Polarity::Negative => Polarity::Positive, - } - } -} - -#[derive(Clone, Copy)] -enum PatternBindingScheme { - Mono, - Poly { level: usize }, -} - -enum UnionPatternSelection { - None, - Unique(InferTy), - Ambiguous, -} - -enum PlaceResolution { - Mutable(InferTy), - Immutable, - Invalid, -} - -struct Typer<'func, 'db> { - db: &'db dyn Database, - function: &'func Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - resolver: Resolver<'db>, - vars: Vec, - env: FxHashMap, - binding_names: FxHashSet, - type_param_env: FxHashMap, InferTy>, - // Internal inference representation. Do not expose directly to users/LSP. - node_types: FxHashMap, - selected_union_members: FxHashMap, - matched_typed_patterns: FxHashMap, - variant_constraints: Vec>, - deferred_coercions: Vec, - missing_param_nodes: FxHashSet, - preserved_type_vars: FxHashSet, - return_ty: Option, - inference: Inference<'db>, - context: Vec, - loop_depth: usize, - unsafe_depth: usize, -} - -impl<'db> Typer<'_, 'db> { - fn new<'func>( - db: &'db dyn Database, - function: &'func Function<'db>, - source_map: &'db mitki_lower::hir::FunctionSourceMap, - resolver: Resolver<'db>, - ) -> Typer<'func, 'db> { - Typer { - db, - function, - source_map, - resolver, - vars: Vec::new(), - env: FxHashMap::default(), - binding_names: FxHashSet::default(), - type_param_env: FxHashMap::default(), - node_types: FxHashMap::default(), - selected_union_members: FxHashMap::default(), - matched_typed_patterns: FxHashMap::default(), - variant_constraints: Vec::new(), - deferred_coercions: Vec::new(), - missing_param_nodes: FxHashSet::default(), - preserved_type_vars: FxHashSet::default(), - return_ty: None, - inference: Inference::default(), - context: Vec::new(), - loop_depth: 0, - unsafe_depth: usize::from(function.is_unsafe()), - } - } - - fn fresh_id(&mut self, level: usize) -> VarId { - let id = self.vars.len(); - self.vars.push(VarState { level, lower_bounds: Vec::new(), upper_bounds: Vec::new() }); - id - } - - fn fresh_var(&mut self, level: usize) -> InferTy { - InferTy::Var(self.fresh_id(level)) - } - - fn instantiate(&mut self, scheme: &Scheme, lvl: usize) -> InferTy { - match scheme { - Scheme::Mono(t) => t.clone(), - Scheme::Poly { level, body } => self.freshen(*level, body, lvl), - } - } - - fn level(&self, ty: &InferTy) -> usize { - match ty { - InferTy::Var(v) => self.vars[*v].level, - InferTy::Known(_) | InferTy::Unknown => 0, - InferTy::Function(inputs, output) => { - let max_input = inputs.iter().map(|t| self.level(t)).max().unwrap_or(0); - max_input.max(self.level(output)) - } - InferTy::Array(item) => self.level(item), - InferTy::Tuple(items) => items.iter().map(|t| self.level(t)).max().unwrap_or(0), - InferTy::Record(fields) => { - fields.iter().map(|(_, ty)| self.level(ty)).max().unwrap_or(0) - } - InferTy::Union(items) | InferTy::Inter(items) => { - items.iter().map(|t| self.level(t)).max().unwrap_or(0) - } - } - } - - fn symbol_to_bits(sym: Symbol<'db>) -> u64 { - sym.as_id().as_bits() - } - - fn symbol_from_bits(bits: u64) -> Symbol<'db> { - Symbol::from_id(salsa::Id::from_bits(bits)) - } - - fn infer_ty_from_ty(ty: Ty<'db>) -> InferTy { - InferTy::Known(ty.as_id().as_bits()) - } - - fn infer_ty_from_kind(&self, kind: TyKind<'db>) -> InferTy { - Self::infer_ty_from_ty(Ty::new(self.db, kind)) - } - - fn known_ty(ty: &InferTy) -> Option> { - match ty { - InferTy::Known(bits) => Some(Ty::from_id(salsa::Id::from_bits(*bits))), - _ => None, - } - } - - fn numeric_kind(&self, ty: &InferTy) -> Option { - let ty = Self::known_ty(ty)?; - match ty.kind(self.db) { - TyKind::Int => Some(NumericKind::Int), - TyKind::ExactInt(int_ty) => Some(NumericKind::ExactInt(*int_ty)), - TyKind::Float => Some(NumericKind::Float), - _ => None, - } - } - - fn orderable_kind(&self, ty: &InferTy) -> Option { - let ty = Self::known_ty(ty)?; - match ty.kind(self.db) { - TyKind::Int => Some(OrderableKind::Int), - TyKind::ExactInt(int_ty) => Some(OrderableKind::ExactInt(*int_ty)), - TyKind::Float => Some(OrderableKind::Float), - TyKind::Char => Some(OrderableKind::Char), - _ => None, - } - } - - fn bool_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::Bool) - } - - fn int_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::Int) - } - - fn float_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::Float) - } - - fn exact_int_infer_ty(&self, int_ty: ExactInt) -> InferTy { - self.infer_ty_from_kind(TyKind::ExactInt(int_ty)) - } - - fn numeric_infer_ty(&self, kind: NumericKind) -> InferTy { - match kind { - NumericKind::Int => self.int_infer_ty(), - NumericKind::ExactInt(int_ty) => self.exact_int_infer_ty(int_ty), - NumericKind::Float => self.float_infer_ty(), - } - } - - fn literal_can_coerce_to_numeric(kind: NumericKind) -> bool { - !matches!(kind, NumericKind::Float) - } - - fn orderable_infer_ty(&self, kind: OrderableKind) -> InferTy { - match kind { - OrderableKind::Int => self.int_infer_ty(), - OrderableKind::ExactInt(int_ty) => self.exact_int_infer_ty(int_ty), - OrderableKind::Float => self.float_infer_ty(), - OrderableKind::Char => self.char_infer_ty(), - } - } - - fn literal_can_coerce_to_orderable(kind: OrderableKind) -> bool { - matches!(kind, OrderableKind::Int | OrderableKind::ExactInt(_)) - } - - fn string_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::String) - } - - fn char_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::Char) - } - - fn unit_infer_ty() -> InferTy { - InferTy::Tuple(Vec::new()) - } - - fn pointer_infer_ty(&self, mutable: bool, pointee: Ty<'db>) -> InferTy { - self.infer_ty_from_kind(TyKind::Pointer { mutable, pointee }) - } - - fn exact_u8_ty(&self) -> Ty<'db> { - Ty::new(self.db, TyKind::ExactInt(ExactInt::U8)) - } - - fn exact_u32_infer_ty(&self) -> InferTy { - self.exact_int_infer_ty(ExactInt::U32) - } - - fn array_u8_infer_ty(&self) -> InferTy { - self.infer_ty_from_kind(TyKind::Array(self.exact_u8_ty())) - } - - fn ptr_field_bits(&self) -> u64 { - Self::symbol_to_bits("ptr".into_symbol(self.db)) - } - - fn len_field_bits(&self) -> u64 { - Self::symbol_to_bits("len".into_symbol(self.db)) - } - - fn byte_view_infer_ty(&self, mutable: bool, expected: Option<&InferTy>) -> InferTy { - if let Some(expected) = expected { - if let Some(ty) = Self::known_ty(expected) - && self.byte_view_ty_matches(ty, mutable) - { - return expected.clone(); - } - if self.byte_view_record_matches(expected, mutable) { - return expected.clone(); - } - } - - let ptr = self.pointer_infer_ty(mutable, self.exact_u8_ty()); - InferTy::Record(vec![ - (self.ptr_field_bits(), ptr), - (self.len_field_bits(), self.exact_u32_infer_ty()), - ]) - } - - fn byte_view_record_matches(&self, ty: &InferTy, mutable: bool) -> bool { - let InferTy::Record(fields) = ty else { - return false; - }; - - let ptr = fields - .iter() - .find(|(name_bits, _)| *name_bits == self.ptr_field_bits()) - .map(|(_, ty)| ty); - let len = fields - .iter() - .find(|(name_bits, _)| *name_bits == self.len_field_bits()) - .map(|(_, ty)| ty); - - matches!( - (ptr, len), - (Some(ptr), Some(len)) - if self.pointer_record_field_matches(ptr, mutable) - && matches!(Self::known_ty(len), Some(ty) if matches!(ty.kind(self.db), TyKind::ExactInt(ExactInt::U32))) - ) - } - - fn pointer_record_field_matches(&self, ty: &InferTy, mutable: bool) -> bool { - matches!( - Self::known_ty(ty).map(|ty| ty.kind(self.db)), - Some(TyKind::Pointer { mutable: actual_mutable, pointee }) - if *actual_mutable == mutable - && matches!(pointee.kind(self.db), TyKind::ExactInt(ExactInt::U8)) - ) - } - - fn byte_view_ty_matches(&self, ty: Ty<'db>, mutable: bool) -> bool { - let TyKind::ExternStruct(struct_ty) = ty.kind(self.db) else { - return false; - }; - - let fields = struct_fields(self.db, *struct_ty); - let mut ptr_matches = false; - let mut len_matches = false; - for (name, field_ty) in fields { - let field_bits = Self::symbol_to_bits(*name); - if field_bits == self.ptr_field_bits() { - ptr_matches = matches!( - field_ty.kind(self.db), - TyKind::Pointer { mutable: actual_mutable, pointee } - if *actual_mutable == mutable - && matches!(pointee.kind(self.db), TyKind::ExactInt(ExactInt::U8)) - ); - } else if field_bits == self.len_field_bits() { - len_matches = matches!(field_ty.kind(self.db), TyKind::ExactInt(ExactInt::U32)); - } - } - ptr_matches && len_matches - } - - fn in_unsafe_context(&self) -> bool { - self.unsafe_depth > 0 - } - - fn with_unsafe_context(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.unsafe_depth += 1; - let output = f(self); - self.unsafe_depth -= 1; - output - } - - fn require_unsafe_context(&mut self, node: ExprId) { - if !self.in_unsafe_context() { - self.emit(DiagnosticKind::UnsafeOperationRequiresUnsafeContext(node)); - } - } - - fn integer_literal_infer_ty(&self, expected: Option<&InferTy>) -> InferTy { - let Some(ty) = expected.and_then(Self::known_ty) else { - return self.int_infer_ty(); - }; - - match ty.kind(self.db) { - TyKind::Int => self.int_infer_ty(), - TyKind::ExactInt(int_ty) => self.exact_int_infer_ty(*int_ty), - _ => self.int_infer_ty(), - } - } - - fn integer_pattern_infer_ty(&self, expected: &InferTy) -> InferTy { - let Some(ty) = Self::known_ty(expected) else { - return self.int_infer_ty(); - }; - - match ty.kind(self.db) { - TyKind::Int => self.int_infer_ty(), - TyKind::ExactInt(int_ty) => self.exact_int_infer_ty(*int_ty), - _ => self.int_infer_ty(), - } - } - - fn flatten_union(ty: InferTy, out: &mut Vec) { - match ty { - InferTy::Union(items) => { - for item in items { - Self::flatten_union(item, out); - } - } - other => out.push(other), - } - } - - fn flatten_inter(ty: InferTy, out: &mut Vec) { - match ty { - InferTy::Inter(items) => { - for item in items { - Self::flatten_inter(item, out); - } - } - other => out.push(other), - } - } - - fn mk_union_many(items: impl IntoIterator) -> InferTy { - let mut flattened = Vec::new(); - for item in items { - Self::flatten_union(item, &mut flattened); - } - - let mut seen: FxHashSet = FxHashSet::default(); - let mut elems = Vec::new(); - for ty in flattened { - if matches!(ty, InferTy::Unknown) { - continue; - } - if seen.insert(ty.clone()) { - elems.push(ty); - } - } - - match elems.len() { - 0 => InferTy::Unknown, - 1 => elems.pop().expect("single element"), - _ => InferTy::Union(elems), - } - } - - fn mk_union(lhs: InferTy, rhs: InferTy) -> InferTy { - Self::mk_union_many([lhs, rhs]) - } - - fn mk_inter_many(items: impl IntoIterator) -> InferTy { - let mut flattened = Vec::new(); - for item in items { - Self::flatten_inter(item, &mut flattened); - } - - let mut seen: FxHashSet = FxHashSet::default(); - let mut elems = Vec::new(); - for ty in flattened { - if matches!(ty, InferTy::Unknown) { - continue; - } - if seen.insert(ty.clone()) { - elems.push(ty); - } - } - - match elems.len() { - 0 => InferTy::Unknown, - 1 => elems.pop().expect("single element"), - _ => InferTy::Inter(elems), - } - } - - fn mk_inter(lhs: InferTy, rhs: InferTy) -> InferTy { - Self::mk_inter_many([lhs, rhs]) - } - - fn emit(&mut self, kind: DiagnosticKind<'db>) { - let context = self.context.last().copied(); - self.inference.diagnostics.push(Diagnostic::new(kind, context)); - } - - fn diagnostic_node(kind: &DiagnosticKind<'db>) -> Option { - match kind { - DiagnosticKind::UnresolvedIdent(node) - | DiagnosticKind::TypeMismatch(node, _, _) - | DiagnosticKind::UnknownType(node) - | DiagnosticKind::ExpectedValueFoundType(node, _) - | DiagnosticKind::CallArityMismatch(node, _, _) - | DiagnosticKind::CallNonFunction(node, _) - | DiagnosticKind::ClosureArityMismatch(node, _, _) - | DiagnosticKind::InvalidBinaryOp(node, _, _, _) - | DiagnosticKind::InvalidPrefixOp(node, _, _) - | DiagnosticKind::InvalidPostfixOp(node, _, _) - | DiagnosticKind::MissingElseBranch(node) - | DiagnosticKind::MissingParameterType(node) - | DiagnosticKind::MissingInitializer(node) - | DiagnosticKind::TupleArityMismatch(node, _, _) - | DiagnosticKind::MissingStructField(node, _) - | DiagnosticKind::UnknownStructField(node, _) - | DiagnosticKind::NotAStruct(node, _) - | DiagnosticKind::NotAnEnum(node, _) - | DiagnosticKind::DuplicatePatternBinding(node) - | DiagnosticKind::RefutablePattern(node) - | DiagnosticKind::UnsupportedFloatPattern(node) - | DiagnosticKind::BreakOutsideLoop(node) - | DiagnosticKind::ContinueOutsideLoop(node) - | DiagnosticKind::CompilerIntrinsicMustBeCalled(node, _) - | DiagnosticKind::InvalidComptimeCall(node) - | DiagnosticKind::ComptimeTargetMustBeZeroArg(node) - | DiagnosticKind::ComptimeTargetMustNotBeGeneric(node) - | DiagnosticKind::ComptimeTargetMustReturnSupportedType(node, _) - | DiagnosticKind::ReflectionOnlyInComptime(node, _) - | DiagnosticKind::InvalidReflectionTarget(node, _, _) - | DiagnosticKind::UnsafeOperationRequiresUnsafeContext(node) - | DiagnosticKind::InvalidUnsafeIntrinsicArgument(node, _, _) - | DiagnosticKind::InvalidUnsafeIntrinsicResult(node, _, _) - | DiagnosticKind::InvalidAssignmentTarget(node) - | DiagnosticKind::AssignmentRequiresMutable(node) - | DiagnosticKind::MutableArgumentRequiresPlace(node) => Some(*node), - DiagnosticKind::UnresolvedType(_, _) - | DiagnosticKind::PatternTypeMismatch(_, _, _) - | DiagnosticKind::AmbiguousUnionPattern(_, _) => None, - } - } - - fn has_diagnostic_at_node(&self, node: ExprId) -> bool { - self.inference - .diagnostics - .iter() - .any(|diag| Self::diagnostic_node(diag.kind()) == Some(node)) - } - - fn emit_unknown_type_errors(&mut self) { - let nodes = self.function.node_store(); - let unknown_nodes: Vec = self - .node_types - .iter() - .filter_map(|(node, ty)| { - let kind = nodes.node_kind(*node); - let eligible_kind = matches!( - kind, - NodeKind::Tuple - | NodeKind::If - | NodeKind::LoopExpr - | NodeKind::Closure - | NodeKind::Call - | NodeKind::Array - | NodeKind::ArrayRepeat - | NodeKind::Field - | NodeKind::Binary - | NodeKind::Postfix - | NodeKind::Prefix - | NodeKind::StructExpr - ); - if matches!(ty, InferTy::Unknown) - && eligible_kind - && !self.binding_names.contains(node) - && !self.has_diagnostic_at_node(*node) - { - Some(*node) - } else { - None - } - }) - .collect(); - - for node in unknown_nodes { - self.emit(DiagnosticKind::UnknownType(node)); - } - } - - fn with_context(&mut self, node: ExprId, f: impl FnOnce(&mut Self) -> T) -> T { - self.context.push(node); - let out = f(self); - self.context.pop(); - out - } - - fn is_context_node(kind: NodeKind) -> bool { - matches!( - kind, - NodeKind::Tuple - | NodeKind::If - | NodeKind::LoopExpr - | NodeKind::Closure - | NodeKind::Call - | NodeKind::StructExpr - ) - } - - fn with_loop_depth(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.loop_depth += 1; - let out = f(self); - self.loop_depth -= 1; - out - } - - fn diagnostic_ty(&self, ty: &InferTy) -> Ty<'db> { - self.present_type(ty, Polarity::Positive) - } - - fn ty_to_infer_ty(&self, ty: Ty<'db>) -> InferTy { - match ty.kind(self.db) { - TyKind::Unknown => InferTy::Unknown, - TyKind::Array(item) => InferTy::Array(Box::new(self.ty_to_infer_ty(*item))), - TyKind::Tuple(items) => { - InferTy::Tuple(items.iter().map(|&t| self.ty_to_infer_ty(t)).collect()) - } - TyKind::Record(fields) => InferTy::Record( - fields - .iter() - .map(|(name, ty)| (Self::symbol_to_bits(*name), self.ty_to_infer_ty(*ty))) - .collect(), - ), - TyKind::Function { inputs, output } => InferTy::Function( - inputs.iter().map(|&t| self.ty_to_infer_ty(t)).collect(), - Box::new(self.ty_to_infer_ty(*output)), - ), - TyKind::Union(items) => { - Self::mk_union_many(items.iter().map(|&t| self.ty_to_infer_ty(t))) - } - TyKind::Inter(items) => { - Self::mk_inter_many(items.iter().map(|&t| self.ty_to_infer_ty(t))) - } - _ => Self::infer_ty_from_ty(ty), - } - } - - fn enum_variant_infer_ty(&self, enum_ty: Ty<'db>, variant: Symbol<'db>) -> Option { - let TyKind::Enum(enum_ty_id) = enum_ty.kind(self.db) else { - return None; - }; - let variants = enum_variants(self.db, *enum_ty_id); - - variants.iter().find(|(name, _)| *name == variant).map(|(_, payload_tys)| { - let enum_infer_ty = Self::infer_ty_from_ty(enum_ty); - if payload_tys.is_empty() { - enum_infer_ty - } else { - let inputs = payload_tys.iter().map(|&t| self.ty_to_infer_ty(t)).collect(); - InferTy::Function(inputs, Box::new(enum_infer_ty)) - } - }) - } - - fn resolve_path_in_node_scope( - &mut self, - node: ExprId, - path: Symbol<'db>, - ) -> Option> { - let guard = self.resolver.scopes_for_node(node); - let resolution = self.resolver.resolve_value_binding(path); - self.resolver.reset(guard); - resolution - } - - fn resolve_type_in_node_scope(&mut self, node: ExprId, path: Symbol<'db>) -> Option> { - let guard = self.resolver.scopes_for_node(node); - let binding = self.resolver.resolve_type_binding(path); - let ty = binding.and_then(|binding| self.resolver.ty_for_binding(binding)); - self.resolver.reset(guard); - ty - } - - fn resolve_place(&mut self, expr: ExprId, lvl: usize) -> PlaceResolution { - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Name => { - let Some(name_id) = nodes.as_name(expr) else { - return PlaceResolution::Invalid; - }; - let Some(binding) = self.resolve_path_in_node_scope(expr, nodes.name(name_id)) - else { - self.emit(DiagnosticKind::UnresolvedIdent(expr)); - return PlaceResolution::Invalid; - }; - match binding { - BindingId::Local(name) | BindingId::Param(name) => { - let ty = self.binding_infer_ty(name, lvl); - self.node_types.insert(expr, ty.clone()); - if self.source_map.is_mutable_binding(name) { - PlaceResolution::Mutable(ty) - } else { - PlaceResolution::Immutable - } - } - _ => PlaceResolution::Invalid, - } - } - NodeKind::Field => { - let field_id = nodes.as_field(expr).expect("Field node mismatch"); - let (base, field_name_expr) = nodes.field(field_id); - if base == ExprId::ZERO { - return PlaceResolution::Invalid; - } - let mutability = match self.resolve_place(base, lvl) { - PlaceResolution::Mutable(_) => true, - PlaceResolution::Immutable => false, - PlaceResolution::Invalid => return PlaceResolution::Invalid, - }; - let field_ty = self.infer_field_expr(expr, base, field_name_expr, None, lvl); - self.node_types.insert(expr, field_ty.clone()); - if mutability { - PlaceResolution::Mutable(field_ty) - } else { - PlaceResolution::Immutable - } - } - _ => PlaceResolution::Invalid, - } - } - - fn bind_pattern_root( - &mut self, - pattern: PatId, - expected: &InferTy, - lvl: usize, - scheme: PatternBindingScheme, - allow_refutable: bool, - fallback: ExprId, - ) { - if pattern == PatId::ZERO { - return; - } - - let mut seen = FxHashSet::default(); - let irrefutable = self.bind_pattern(pattern, expected, lvl, scheme, fallback, &mut seen); - if !allow_refutable && !irrefutable { - self.emit(DiagnosticKind::RefutablePattern( - self.pattern_anchor(pattern).unwrap_or(fallback), - )); - } - } - - fn bind_pattern( - &mut self, - pattern: PatId, - expected: &InferTy, - lvl: usize, - scheme: PatternBindingScheme, - fallback: ExprId, - seen: &mut FxHashSet, - ) -> bool { - let nodes = self.function.node_store(); - let anchor = self.pattern_anchor(pattern).unwrap_or(fallback); - - match self.select_union_member_for_pattern(pattern, expected) { - UnionPatternSelection::Unique(selected) => { - self.selected_union_members.insert(pattern, selected.clone()); - return self.bind_pattern(pattern, &selected, lvl, scheme, fallback, seen); - } - UnionPatternSelection::Ambiguous => { - self.emit(DiagnosticKind::AmbiguousUnionPattern( - pattern, - self.diagnostic_ty(expected), - )); - return false; - } - UnionPatternSelection::None => {} - } - - match nodes.node_kind(pattern) { - NodeKind::PatBinding => { - let (name, _) = - nodes.pat_binding(nodes.as_pat_binding(pattern).expect("PatBinding mismatch")); - self.bind_pattern_name(name, expected.clone(), scheme, seen); - true - } - NodeKind::PatWildcard => true, - NodeKind::PatTyped => { - let (inner, ty_id) = - nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped mismatch")); - let annotation_resolved = self.resolve_type_to_infer_ty_silently(ty_id).is_some(); - let annotated = self.resolve_type_to_infer_ty(ty_id).unwrap_or(InferTy::Unknown); - let matches_annotation = self.pattern_type_matches(expected, &annotated); - if matches_annotation && annotation_resolved { - self.matched_typed_patterns.insert(pattern, expected.clone()); - } - if !matches_annotation { - self.pattern_constrain(pattern, expected, &annotated); - } - let binding_ty = if matches_annotation { expected.clone() } else { annotated }; - self.bind_pattern(inner, &binding_ty, lvl, scheme, fallback, seen) - && matches_annotation - } - NodeKind::PatTrue | NodeKind::PatFalse => { - self.pattern_constrain(pattern, expected, &self.bool_infer_ty()); - false - } - NodeKind::PatInt => { - let int_ty = self.integer_pattern_infer_ty(expected); - self.pattern_constrain(pattern, expected, &int_ty); - false - } - NodeKind::PatFloat => { - self.pattern_constrain(pattern, expected, &self.float_infer_ty()); - self.emit(DiagnosticKind::UnsupportedFloatPattern(anchor)); - false - } - NodeKind::PatString => { - self.pattern_constrain(pattern, expected, &self.string_infer_ty()); - false - } - NodeKind::PatChar => { - self.pattern_constrain(pattern, expected, &self.char_infer_ty()); - false - } - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.bind_pattern(inner, expected, lvl, scheme, fallback, seen) - } - NodeKind::PatTuple => { - let items = nodes.pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")); - let item_ids: Vec<_> = items.iter().collect(); - let item_tys = match expected { - InferTy::Tuple(expected_items) if expected_items.len() == item_ids.len() => { - expected_items.clone() - } - InferTy::Tuple(expected_items) => { - self.emit(DiagnosticKind::TupleArityMismatch( - anchor, - expected_items.len(), - item_ids.len(), - )); - item_ids.iter().map(|_| self.fresh_var(lvl)).collect() - } - _ => item_ids.iter().map(|_| self.fresh_var(lvl)).collect(), - }; - let tuple_ty = InferTy::Tuple(item_tys.clone()); - self.pattern_constrain(pattern, expected, &tuple_ty); - let mut irrefutable = true; - for (item, item_ty) in item_ids.into_iter().zip(item_tys.iter()) { - irrefutable &= self.bind_pattern(item, item_ty, lvl, scheme, fallback, seen); - } - irrefutable - } - NodeKind::PatStruct => { - let (path, fields) = - nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - let Some(struct_ty) = self.resolve_struct_pattern_type(path) else { - for field in fields.iter() { - let (_, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - if pat != PatId::ZERO { - self.bind_pattern(pat, &InferTy::Unknown, lvl, scheme, fallback, seen); - } - } - return false; - }; - - let struct_infer = self.ty_to_infer_ty(struct_ty); - self.pattern_constrain(pattern, expected, &struct_infer); - - let TyKind::Struct(struct_nominal) = struct_ty.kind(self.db) else { - self.emit(DiagnosticKind::NotAStruct(anchor, struct_ty)); - return false; - }; - let field_map: FxHashMap<_, _> = struct_fields(self.db, *struct_nominal) - .iter() - .map(|(name, ty)| (*name, self.ty_to_infer_ty(*ty))) - .collect(); - let mut seen_fields = FxHashSet::default(); - let mut irrefutable = true; - - for field in fields.iter() { - let (name, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - let field_sym = nodes.name(name); - if let Some(field_ty) = field_map.get(&field_sym) { - seen_fields.insert(field_sym); - if pat != PatId::ZERO { - irrefutable &= - self.bind_pattern(pat, field_ty, lvl, scheme, name.into(), seen); - } else { - self.bind_pattern_name(name, field_ty.clone(), scheme, seen); - } - } else { - self.emit(DiagnosticKind::UnknownStructField(name.into(), field_sym)); - irrefutable = false; - } - } - - for field_name in field_map.keys() { - if !seen_fields.contains(field_name) { - self.emit(DiagnosticKind::MissingStructField(anchor, *field_name)); - irrefutable = false; - } - } - - irrefutable - } - NodeKind::PatVariant => { - let (path, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - let Some((enum_ty, payload_tys, variant_name, single_variant)) = - self.resolve_variant_pattern(path, expected) - else { - for arg in args.iter() { - self.bind_pattern(arg, &InferTy::Unknown, lvl, scheme, fallback, seen); - } - return false; - }; - - self.pattern_constrain(pattern, expected, &enum_ty); - let arg_ids: Vec<_> = args.iter().collect(); - if payload_tys.len() != arg_ids.len() { - self.emit(DiagnosticKind::CallArityMismatch( - variant_name, - payload_tys.len(), - arg_ids.len(), - )); - } - - let mut irrefutable = single_variant; - for (index, arg) in arg_ids.into_iter().enumerate() { - let expected_ty = payload_tys.get(index).cloned().unwrap_or(InferTy::Unknown); - irrefutable &= - self.bind_pattern(arg, &expected_ty, lvl, scheme, variant_name, seen); - } - irrefutable - } - _ => true, - } - } - - fn select_union_member_for_pattern( - &mut self, - pattern: PatId, - expected: &InferTy, - ) -> UnionPatternSelection { - if !self.is_member_specific_pattern(pattern) { - return UnionPatternSelection::None; - } - - let InferTy::Union(items) = expected else { - return UnionPatternSelection::None; - }; - - let mut candidates = Vec::new(); - for item in items { - Self::flatten_union(item.clone(), &mut candidates); - } - - let compatible = candidates - .into_iter() - .filter(|candidate| self.pattern_matches_ty(pattern, candidate)) - .collect::>(); - - match compatible.as_slice() { - [] => UnionPatternSelection::None, - [selected] => UnionPatternSelection::Unique(selected.clone()), - _ => UnionPatternSelection::Ambiguous, - } - } - - fn is_member_specific_pattern(&self, pattern: PatId) -> bool { - if pattern == PatId::ZERO { - return false; - } - - let nodes = self.function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatBinding | NodeKind::PatWildcard => false, - NodeKind::PatTyped => true, - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.is_member_specific_pattern(inner) - } - _ => true, - } - } - - fn pattern_matches_ty(&mut self, pattern: PatId, expected: &InferTy) -> bool { - if pattern == PatId::ZERO { - return true; - } - - if let InferTy::Union(items) = expected - && self.is_member_specific_pattern(pattern) - { - return items.iter().any(|item| self.pattern_matches_ty(pattern, item)); - } - - let nodes = self.function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatBinding | NodeKind::PatWildcard => true, - NodeKind::PatTyped => { - let (inner, ty_id) = - nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped mismatch")); - let Some(annotated) = self.resolve_type_to_infer_ty_silently(ty_id) else { - return false; - }; - self.pattern_type_matches(expected, &annotated) - && (inner == PatId::ZERO || self.pattern_matches_ty(inner, expected)) - } - NodeKind::PatTrue | NodeKind::PatFalse => { - Self::literal_pattern_matches_ty(expected, |ty| { - matches!(ty.kind(self.db), TyKind::Bool) - }) - } - NodeKind::PatInt => Self::literal_pattern_matches_ty(expected, |ty| { - matches!(ty.kind(self.db), TyKind::Int | TyKind::ExactInt(_)) - }), - NodeKind::PatFloat => Self::literal_pattern_matches_ty(expected, |ty| { - matches!(ty.kind(self.db), TyKind::Float) - }), - NodeKind::PatString => Self::literal_pattern_matches_ty(expected, |ty| { - matches!(ty.kind(self.db), TyKind::String) - }), - NodeKind::PatChar => Self::literal_pattern_matches_ty(expected, |ty| { - matches!(ty.kind(self.db), TyKind::Char) - }), - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.pattern_matches_ty(inner, expected) - } - NodeKind::PatTuple => { - let items = nodes - .pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")) - .iter() - .collect::>(); - match expected { - InferTy::Tuple(expected_items) if expected_items.len() == items.len() => items - .iter() - .zip(expected_items.iter()) - .all(|(&item, item_ty)| self.pattern_matches_ty(item, item_ty)), - InferTy::Var(_) | InferTy::Unknown => true, - _ => false, - } - } - NodeKind::PatStruct => { - let (path, fields) = - nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - let resolved_path = self.resolve_pattern_type_silently(path); - match expected { - InferTy::Known(bits) => { - let ty = Ty::from_id(salsa::Id::from_bits(*bits)); - match ty.kind(self.db) { - TyKind::Struct(struct_ty) => { - if resolved_path.is_some_and(|path_ty| path_ty != ty) { - return false; - } - - let field_map: FxHashMap<_, _> = struct_fields(self.db, *struct_ty) - .iter() - .map(|(name, ty)| { - (Self::symbol_to_bits(*name), self.ty_to_infer_ty(*ty)) - }) - .collect(); - fields.iter().all(|field| { - let (name, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - let field_bits = Self::symbol_to_bits(nodes.name(name)); - let Some(field_ty) = field_map.get(&field_bits) else { - return path != ExprId::ZERO; - }; - pat == PatId::ZERO || self.pattern_matches_ty(pat, field_ty) - }) - } - _ => false, - } - } - InferTy::Record(field_map) => { - if resolved_path.is_some() { - return false; - } - - let field_map: FxHashMap<_, _> = - field_map.iter().map(|(name, ty)| (*name, ty.clone())).collect(); - fields.iter().all(|field| { - let (name, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - let field_bits = Self::symbol_to_bits(nodes.name(name)); - let Some(field_ty) = field_map.get(&field_bits) else { - return path == ExprId::ZERO; - }; - pat == PatId::ZERO || self.pattern_matches_ty(pat, field_ty) - }) - } - InferTy::Var(_) | InferTy::Unknown => { - resolved_path.is_some() || path == ExprId::ZERO - } - _ => false, - } - } - NodeKind::PatVariant => { - let (path, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - let Some(field_id) = nodes.as_field(path) else { - return false; - }; - let (base, variant_name_expr) = nodes.field(field_id); - let Some(variant_name) = nodes.as_name(variant_name_expr) else { - return false; - }; - let variant_sym = nodes.name(variant_name); - let explicit_enum = self.resolve_pattern_type_silently(base); - - match expected { - InferTy::Known(bits) => { - let ty = Ty::from_id(salsa::Id::from_bits(*bits)); - let TyKind::Enum(enum_ty) = ty.kind(self.db) else { - return false; - }; - - if explicit_enum.is_some_and(|enum_ty_path| enum_ty_path != ty) { - return false; - } - - let Some((_, payload_tys)) = enum_variants(self.db, *enum_ty) - .iter() - .find(|(name, _)| *name == variant_sym) - else { - return false; - }; - - args.iter().zip(payload_tys.iter()).all(|(arg, payload_ty)| { - self.pattern_matches_ty(arg, &self.ty_to_infer_ty(*payload_ty)) - }) - } - InferTy::Var(_) | InferTy::Unknown => explicit_enum.is_some(), - _ => false, - } - } - _ => true, - } - } - - fn literal_pattern_matches_ty( - expected: &InferTy, - matches_known: impl FnOnce(Ty<'db>) -> bool, - ) -> bool { - match expected { - InferTy::Known(bits) => matches_known(Ty::from_id(salsa::Id::from_bits(*bits))), - InferTy::Var(_) | InferTy::Unknown => true, - _ => false, - } - } - - fn pattern_type_matches(&mut self, actual: &InferTy, annotated: &InferTy) -> bool { - let vars_snapshot = self.vars.clone(); - let matches = self.constrain_top(actual, annotated).is_ok(); - self.vars = vars_snapshot; - matches - } - - fn resolve_type_to_infer_ty_silently(&mut self, ty: TyId) -> Option { - let diagnostics_len = self.inference.diagnostics.len(); - let resolved = self.resolve_type_to_infer_ty(ty); - let changed = self.inference.diagnostics.len() != diagnostics_len; - self.inference.diagnostics.truncate(diagnostics_len); - if changed { None } else { resolved } - } - - fn resolve_pattern_type_silently(&mut self, path: ExprId) -> Option> { - if path == ExprId::ZERO { - return None; - } - - let nodes = self.function.node_store(); - let name = nodes.as_name(path)?; - let symbol = nodes.name(name); - - self.resolve_type_in_node_scope(path, symbol) - } - - fn bind_pattern_name( - &mut self, - name: NameId, - ty: InferTy, - scheme: PatternBindingScheme, - seen: &mut FxHashSet, - ) { - let symbol = self.function.node_store().name(name); - let expr_id: ExprId = name.into(); - let bits = Self::symbol_to_bits(symbol); - if !seen.insert(bits) { - self.emit(DiagnosticKind::DuplicatePatternBinding(name.into())); - } - - self.node_types.insert(expr_id, ty.clone()); - self.binding_names.insert(expr_id); - match scheme { - PatternBindingScheme::Mono => { - self.env.insert(expr_id, Scheme::Mono(ty)); - } - PatternBindingScheme::Poly { level } => { - self.env.insert(expr_id, Scheme::Poly { level, body: ty }); - } - } - } - - fn pattern_constrain(&mut self, pattern: PatId, actual: &InferTy, expected: &InferTy) { - if matches!(actual, InferTy::Unknown) || matches!(expected, InferTy::Unknown) { - return; - } - if self.constrain_top(actual, expected).is_err() { - self.emit(DiagnosticKind::PatternTypeMismatch( - pattern, - self.diagnostic_ty(actual), - self.diagnostic_ty(expected), - )); - } - } - - fn pattern_anchor(&self, pattern: PatId) -> Option { - if pattern == PatId::ZERO { - return None; - } - - let nodes = self.function.node_store(); - match nodes.node_kind(pattern) { - NodeKind::PatBinding => { - let (name, _) = - nodes.pat_binding(nodes.as_pat_binding(pattern).expect("PatBinding")); - Some(name.into()) - } - NodeKind::PatTyped => { - let (inner, _) = nodes.pat_typed(nodes.as_pat_typed(pattern).expect("PatTyped")); - self.pattern_anchor(inner) - } - NodeKind::PatStruct => { - let (path, _) = nodes.pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")); - (path != ExprId::ZERO).then_some(path).or_else(|| { - nodes - .pat_struct(nodes.as_pat_struct(pattern).expect("PatStruct")) - .1 - .iter() - .find_map(|field| { - let (name, pat) = nodes.pat_struct_field( - nodes.as_pat_struct_field(field).expect("PatStructField"), - ); - if pat != PatId::ZERO { - self.pattern_anchor(pat) - } else { - Some(name.into()) - } - }) - }) - } - NodeKind::PatVariant => { - let (path, args) = - nodes.pat_variant(nodes.as_pat_variant(pattern).expect("PatVariant")); - (path != ExprId::ZERO) - .then_some(path) - .or_else(|| args.iter().find_map(|arg| self.pattern_anchor(arg))) - } - NodeKind::PatTuple => nodes - .pat_tuple(nodes.as_pat_tuple(pattern).expect("PatTuple")) - .iter() - .find_map(|item| self.pattern_anchor(item)), - NodeKind::PatParen => { - let (inner, _) = nodes.pat_paren(nodes.as_pat_paren(pattern).expect("PatParen")); - self.pattern_anchor(inner) - } - _ => None, - } - } - - fn resolve_struct_pattern_type(&mut self, path: ExprId) -> Option> { - if path == ExprId::ZERO { - return None; - } - - let nodes = self.function.node_store(); - let name = nodes.as_name(path)?; - let symbol = nodes.name(name); - let Some(ty) = self.resolve_type_in_node_scope(path, symbol) else { - self.emit(DiagnosticKind::UnresolvedIdent(path)); - return None; - }; - Some(ty) - } - - fn resolve_variant_pattern( - &mut self, - path: ExprId, - expected: &InferTy, - ) -> Option<(InferTy, Vec, ExprId, bool)> { - let nodes = self.function.node_store(); - let field_id = nodes.as_field(path)?; - let (base, variant_name_expr) = nodes.field(field_id); - let variant_name = nodes.as_name(variant_name_expr)?; - let variant_sym = nodes.name(variant_name); - - let enum_ty = if base != ExprId::ZERO { - let Some(base_name) = nodes.as_name(base) else { - self.emit(DiagnosticKind::UnresolvedIdent(base)); - return None; - }; - let base_sym = nodes.name(base_name); - let Some(ty) = self.resolve_type_in_node_scope(base, base_sym) else { - self.emit(DiagnosticKind::UnresolvedIdent(base)); - return None; - }; - ty - } else if let Some(ty) = Self::known_ty(expected) { - ty - } else if let Some(ty) = self.resolver.resolve_enum_variant(variant_sym) { - ty - } else { - self.emit(DiagnosticKind::UnresolvedIdent(variant_name_expr)); - return None; - }; - - let TyKind::Enum(enum_ty_id) = enum_ty.kind(self.db) else { - self.emit(DiagnosticKind::NotAnEnum( - if base != ExprId::ZERO { base } else { variant_name_expr }, - enum_ty, - )); - return None; - }; - - let variants = enum_variants(self.db, *enum_ty_id); - let Some((_, payload)) = variants.iter().find(|(name, _)| *name == variant_sym) else { - self.emit(DiagnosticKind::UnresolvedIdent(variant_name_expr)); - return None; - }; - - Some(( - self.ty_to_infer_ty(enum_ty), - payload.iter().map(|&ty| self.ty_to_infer_ty(ty)).collect(), - variant_name_expr, - variants.len() == 1, - )) - } - - fn infer_name_expr(&mut self, node: ExprId, lvl: usize) -> InferTy { - let nodes = self.function.node_store(); - let name_id = nodes.as_name(node).expect("Name node mismatch"); - let path = nodes.name(name_id); - - let Some(resolution) = self.resolve_path_in_node_scope(node, path) else { - if let Some(ty) = self.resolve_type_in_node_scope(node, path) { - self.emit(DiagnosticKind::ExpectedValueFoundType(node, ty)); - return InferTy::Unknown; - } - self.emit(DiagnosticKind::UnresolvedIdent(node)); - return InferTy::Unknown; - }; - - match resolution { - BindingId::Local(binding) | BindingId::Param(binding) => { - self.binding_infer_ty(binding, lvl) - } - BindingId::RuntimeFunction(function) => { - self.ty_to_infer_ty(function.function_ty(self.db)) - } - BindingId::CompilerIntrinsic(intrinsic) => { - self.emit(DiagnosticKind::CompilerIntrinsicMustBeCalled(node, intrinsic)); - InferTy::Unknown - } - BindingId::Function(function) => self.infer_function_binding_ty(function, lvl), - BindingId::Struct(_) | BindingId::Enum(_) | BindingId::BuiltinType(_) => { - let ty = self.resolver.ty_for_binding(resolution).expect("type binding"); - self.emit(DiagnosticKind::ExpectedValueFoundType(node, ty)); - InferTy::Unknown - } - BindingId::EnumVariant(_) => { - self.emit(DiagnosticKind::UnresolvedIdent(node)); - InferTy::Unknown - } - } - } - - fn binding_infer_ty(&mut self, binding: NameId, lvl: usize) -> InferTy { - let key: ExprId = binding.into(); - match self.env.get(&key).cloned() { - Some(scheme) => self.instantiate(&scheme, lvl), - None => self.node_types.get(&key).cloned().unwrap_or_else(|| self.fresh_var(lvl)), - } - } - - fn infer_function_binding_ty( - &mut self, - function: FunctionLocation<'db>, - lvl: usize, - ) -> InferTy { - let resolver = Resolver::new(self.db, function); - let signature = function.signature(self.db); - let sig_nodes = signature.nodes(self.db); - let params = signature.params(self.db); - let ret_type = signature.ret_type(self.db); - let type_params = signature.type_params(self.db); - - let type_param_vars: FxHashMap, InferTy> = - type_params.iter().map(|&name| (name, self.fresh_var(lvl))).collect(); - - let inputs: Vec = params - .iter() - .map(|¶m| { - let (_, ty) = sig_nodes.param(param); - self.resolve_sig_type(ty, sig_nodes, &resolver, &type_param_vars, lvl) - }) - .collect(); - - let output = if ret_type == TyId::ZERO { - InferTy::Tuple(Vec::new()) - } else { - self.resolve_sig_type(ret_type, sig_nodes, &resolver, &type_param_vars, lvl) - }; - - InferTy::Function(inputs, Box::new(output)) - } - - fn compiler_intrinsic_call( - &mut self, - node: ExprId, - callee: ExprId, - args: &[ExprId], - expected: Option<&InferTy>, - lvl: usize, - ) -> Option { - let nodes = self.function.node_store(); - let name_id = nodes.as_name(callee)?; - let symbol = nodes.name(name_id); - let Some(BindingId::CompilerIntrinsic(intrinsic)) = - self.resolve_path_in_node_scope(callee, symbol) - else { - return None; - }; - - Some(match intrinsic { - CompilerIntrinsic::Comptime => self.typecheck_comptime_call(node, args, lvl), - _ if intrinsic.is_reflection() => { - self.typecheck_reflection_call(node, intrinsic, args, lvl) - } - _ => self.typecheck_unsafe_intrinsic_call(node, intrinsic, args, expected, lvl), - }) - } - - fn typecheck_comptime_call(&mut self, node: ExprId, args: &[ExprId], lvl: usize) -> InferTy { - if args.len() != 1 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 1, args.len())); - return InferTy::Unknown; - } - - let call_expr = args[0]; - let nodes = self.function.node_store(); - let Some(call_id) = nodes.as_call(call_expr) else { - self.infer_expr(call_expr, lvl); - self.emit(DiagnosticKind::InvalidComptimeCall(node)); - return InferTy::Unknown; - }; - - let (target_expr, call_args) = nodes.call(call_id); - for arg in call_args.iter() { - self.infer_expr(arg, lvl); - } - - let Some(name_id) = nodes.as_name(target_expr) else { - self.emit(DiagnosticKind::InvalidComptimeCall(node)); - return InferTy::Unknown; - }; - let symbol = nodes.name(name_id); - let Some(BindingId::Function(target)) = - self.resolve_path_in_node_scope(target_expr, symbol) - else { - self.emit(DiagnosticKind::InvalidComptimeCall(node)); - return InferTy::Unknown; - }; - - let hir_function = target.hir_function(self.db).function(self.db); - if !hir_function.is_comptime() { - self.emit(DiagnosticKind::InvalidComptimeCall(node)); - return InferTy::Unknown; - } - - if !call_args.is_empty() || !hir_function.params().is_empty() { - self.emit(DiagnosticKind::ComptimeTargetMustBeZeroArg(node)); - return InferTy::Unknown; - } - - if !hir_function.type_params().is_empty() { - self.emit(DiagnosticKind::ComptimeTargetMustNotBeGeneric(node)); - return InferTy::Unknown; - } - - let target_inference = target.infer(self.db); - let return_ty = target_inference - .type_of_node(hir_function.body()) - .unwrap_or_else(|| Ty::new(self.db, TyKind::Tuple(Vec::new()))); - if !comptime_result_supported(self.db, return_ty) { - self.emit(DiagnosticKind::ComptimeTargetMustReturnSupportedType(node, return_ty)); - return InferTy::Unknown; - } - - self.ty_to_infer_ty(return_ty) - } - - fn typecheck_reflection_call( - &mut self, - node: ExprId, - intrinsic: CompilerIntrinsic, - args: &[ExprId], - lvl: usize, - ) -> InferTy { - if !self.function.is_comptime() { - self.emit(DiagnosticKind::ReflectionOnlyInComptime(node, intrinsic)); - } - - let result = match intrinsic { - CompilerIntrinsic::TypeName - | CompilerIntrinsic::FieldName - | CompilerIntrinsic::VariantName - | CompilerIntrinsic::FunctionParamTypeName - | CompilerIntrinsic::FunctionReturnTypeName => self.string_infer_ty(), - CompilerIntrinsic::FieldCount - | CompilerIntrinsic::VariantCount - | CompilerIntrinsic::FunctionParamCount => self.int_infer_ty(), - CompilerIntrinsic::Comptime - | CompilerIntrinsic::StackAlloc - | CompilerIntrinsic::PtrRead - | CompilerIntrinsic::PtrWrite - | CompilerIntrinsic::PtrAdd - | CompilerIntrinsic::StrBytes - | CompilerIntrinsic::StrFromUtf8Unchecked - | CompilerIntrinsic::ArrayMutBytes => unreachable!("handled above"), - }; - - let expected_arity = match intrinsic { - CompilerIntrinsic::FieldName - | CompilerIntrinsic::VariantName - | CompilerIntrinsic::FunctionParamTypeName => 2, - CompilerIntrinsic::TypeName - | CompilerIntrinsic::FieldCount - | CompilerIntrinsic::VariantCount - | CompilerIntrinsic::FunctionParamCount - | CompilerIntrinsic::FunctionReturnTypeName => 1, - CompilerIntrinsic::Comptime - | CompilerIntrinsic::StackAlloc - | CompilerIntrinsic::PtrRead - | CompilerIntrinsic::PtrWrite - | CompilerIntrinsic::PtrAdd - | CompilerIntrinsic::StrBytes - | CompilerIntrinsic::StrFromUtf8Unchecked - | CompilerIntrinsic::ArrayMutBytes => unreachable!("handled above"), - }; - if args.len() != expected_arity { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, expected_arity, args.len())); - return result; - } - - match intrinsic { - CompilerIntrinsic::TypeName => { - let _ = self.resolve_reflection_type_arg(args[0], intrinsic, "a named type"); - } - CompilerIntrinsic::FieldCount | CompilerIntrinsic::FieldName => { - let Some(ty) = - self.resolve_reflection_type_arg(args[0], intrinsic, "a named struct type") - else { - return result; - }; - if !matches!(ty.kind(self.db), TyKind::Struct(_)) { - self.emit(DiagnosticKind::InvalidReflectionTarget( - args[0], - intrinsic, - "a named struct type", - )); - } - if args.len() > 1 { - let int_ty = self.int_infer_ty(); - self.check_expr(args[1], &int_ty, lvl); - } - } - CompilerIntrinsic::VariantCount | CompilerIntrinsic::VariantName => { - let Some(ty) = - self.resolve_reflection_type_arg(args[0], intrinsic, "a named enum type") - else { - return result; - }; - if !matches!(ty.kind(self.db), TyKind::Enum(_)) { - self.emit(DiagnosticKind::InvalidReflectionTarget( - args[0], - intrinsic, - "a named enum type", - )); - } - if args.len() > 1 { - let int_ty = self.int_infer_ty(); - self.check_expr(args[1], &int_ty, lvl); - } - } - CompilerIntrinsic::FunctionParamCount - | CompilerIntrinsic::FunctionParamTypeName - | CompilerIntrinsic::FunctionReturnTypeName => { - let _ = self.resolve_reflection_function_arg( - args[0], - intrinsic, - "a top-level function name", - ); - if args.len() > 1 { - let int_ty = self.int_infer_ty(); - self.check_expr(args[1], &int_ty, lvl); - } - } - CompilerIntrinsic::Comptime - | CompilerIntrinsic::StackAlloc - | CompilerIntrinsic::PtrRead - | CompilerIntrinsic::PtrWrite - | CompilerIntrinsic::PtrAdd - | CompilerIntrinsic::StrBytes - | CompilerIntrinsic::StrFromUtf8Unchecked - | CompilerIntrinsic::ArrayMutBytes => unreachable!("handled above"), - } - - result - } - - fn typecheck_unsafe_intrinsic_call( - &mut self, - node: ExprId, - intrinsic: CompilerIntrinsic, - args: &[ExprId], - expected: Option<&InferTy>, - lvl: usize, - ) -> InferTy { - if intrinsic.requires_unsafe() { - self.require_unsafe_context(node); - } - - match intrinsic { - CompilerIntrinsic::StackAlloc => { - if args.len() != 1 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 1, args.len())); - return InferTy::Unknown; - } - - self.check_expr(args[0], &self.exact_u32_infer_ty(), lvl); - match expected.and_then(Self::known_ty) { - Some(ty) if matches!(ty.kind(self.db), TyKind::Pointer { .. }) => { - self.ty_to_infer_ty(ty) - } - _ => { - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicResult( - node, - intrinsic, - "an expected raw pointer result type", - )); - InferTy::Unknown - } - } - } - CompilerIntrinsic::PtrRead => { - if args.len() != 1 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 1, args.len())); - return InferTy::Unknown; - } - - let ptr_ty = self.infer_expr(args[0], lvl); - match Self::known_ty(&ptr_ty).map(|ty| ty.kind(self.db)) { - Some(TyKind::Pointer { pointee, .. }) => self.ty_to_infer_ty(*pointee), - Some(_) => { - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a raw pointer argument", - )); - InferTy::Unknown - } - None => InferTy::Unknown, - } - } - CompilerIntrinsic::PtrWrite => { - if args.len() != 2 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 2, args.len())); - return Self::unit_infer_ty(); - } - - let ptr_ty = self.infer_expr(args[0], lvl); - match Self::known_ty(&ptr_ty).map(|ty| ty.kind(self.db)) { - Some(TyKind::Pointer { mutable: true, pointee }) => { - let pointee = self.ty_to_infer_ty(*pointee); - self.check_expr(args[1], &pointee, lvl); - } - Some(TyKind::Pointer { .. }) => { - self.infer_expr(args[1], lvl); - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a mutable raw pointer as the first argument", - )); - } - Some(_) => { - self.infer_expr(args[1], lvl); - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a raw pointer as the first argument", - )); - } - None => { - self.infer_expr(args[1], lvl); - } - } - Self::unit_infer_ty() - } - CompilerIntrinsic::PtrAdd => { - if args.len() != 2 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 2, args.len())); - return InferTy::Unknown; - } - - let ptr_ty = self.infer_expr(args[0], lvl); - self.check_expr(args[1], &self.exact_u32_infer_ty(), lvl); - match Self::known_ty(&ptr_ty).map(|ty| ty.kind(self.db)) { - Some(TyKind::Pointer { .. }) => ptr_ty, - Some(_) => { - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a raw pointer as the first argument", - )); - InferTy::Unknown - } - None => InferTy::Unknown, - } - } - CompilerIntrinsic::StrBytes => { - if args.len() != 1 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 1, args.len())); - return InferTy::Unknown; - } - - self.check_expr(args[0], &self.string_infer_ty(), lvl); - self.byte_view_infer_ty(false, expected) - } - CompilerIntrinsic::StrFromUtf8Unchecked => { - if args.len() != 2 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 2, args.len())); - return InferTy::Unknown; - } - - let ptr_ty = self.infer_expr(args[0], lvl); - match Self::known_ty(&ptr_ty).map(|ty| ty.kind(self.db)) { - Some(TyKind::Pointer { pointee, .. }) - if matches!(pointee.kind(self.db), TyKind::ExactInt(ExactInt::U8)) => {} - Some(TyKind::Pointer { .. }) => { - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a raw `*const u8` or `*mut u8` pointer as the first argument", - )); - } - Some(_) => { - self.emit(DiagnosticKind::InvalidUnsafeIntrinsicArgument( - args[0], - intrinsic, - "a raw `*const u8` or `*mut u8` pointer as the first argument", - )); - } - None => {} - } - - self.check_expr(args[1], &self.exact_u32_infer_ty(), lvl); - self.string_infer_ty() - } - CompilerIntrinsic::ArrayMutBytes => { - if args.len() != 1 { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, 1, args.len())); - return InferTy::Unknown; - } - - self.check_expr(args[0], &self.array_u8_infer_ty(), lvl); - self.byte_view_infer_ty(true, expected) - } - CompilerIntrinsic::Comptime - | CompilerIntrinsic::TypeName - | CompilerIntrinsic::FieldCount - | CompilerIntrinsic::FieldName - | CompilerIntrinsic::VariantCount - | CompilerIntrinsic::VariantName - | CompilerIntrinsic::FunctionParamCount - | CompilerIntrinsic::FunctionParamTypeName - | CompilerIntrinsic::FunctionReturnTypeName => unreachable!("handled above"), - } - } - - fn resolve_reflection_type_arg( - &mut self, - arg: ExprId, - intrinsic: CompilerIntrinsic, - expected: &'static str, - ) -> Option> { - let nodes = self.function.node_store(); - let name_id = nodes - .as_name(arg) - .ok_or_else(|| { - self.emit(DiagnosticKind::InvalidReflectionTarget(arg, intrinsic, expected)); - }) - .ok()?; - let symbol = nodes.name(name_id); - if let Some(ty) = self.resolve_type_in_node_scope(arg, symbol) { - Some(ty) - } else { - match self.resolve_path_in_node_scope(arg, symbol) { - Some(BindingId::Local(_)) - | Some(BindingId::Param(_)) - | Some(BindingId::CompilerIntrinsic(_)) - | Some(BindingId::RuntimeFunction(_)) - | Some(BindingId::Function(_)) - | Some(BindingId::EnumVariant(_)) - | Some(BindingId::Struct(_)) - | Some(BindingId::Enum(_)) - | Some(BindingId::BuiltinType(_)) => { - self.emit(DiagnosticKind::InvalidReflectionTarget(arg, intrinsic, expected)); - None - } - None => { - self.emit(DiagnosticKind::UnresolvedIdent(arg)); - None - } - } - } - } - - fn resolve_reflection_function_arg( - &mut self, - arg: ExprId, - intrinsic: CompilerIntrinsic, - expected: &'static str, - ) -> Option> { - let nodes = self.function.node_store(); - let name_id = nodes - .as_name(arg) - .ok_or_else(|| { - self.emit(DiagnosticKind::InvalidReflectionTarget(arg, intrinsic, expected)); - }) - .ok()?; - let symbol = nodes.name(name_id); - match self.resolve_path_in_node_scope(arg, symbol) { - Some(BindingId::Function(function)) => Some(function), - Some(BindingId::Local(_)) - | Some(BindingId::Param(_)) - | Some(BindingId::CompilerIntrinsic(_)) - | Some(BindingId::RuntimeFunction(_)) - | Some(BindingId::EnumVariant(_)) - | Some(BindingId::Struct(_)) - | Some(BindingId::Enum(_)) - | Some(BindingId::BuiltinType(_)) => { - self.emit(DiagnosticKind::InvalidReflectionTarget(arg, intrinsic, expected)); - None - } - None => { - self.emit(DiagnosticKind::UnresolvedIdent(arg)); - None - } - } - } - - fn infer_bare_enum_variant( - &mut self, - field_name: Symbol<'db>, - field_name_expr: ExprId, - lvl: usize, - ) -> InferTy { - let enum_ty = self.fresh_var(lvl.saturating_sub(1)); - if let InferTy::Var(enum_var) = enum_ty.clone() { - self.variant_constraints.push(VariantConstraint { - enum_var, - variant: field_name, - payload: Vec::new(), - name_node: field_name_expr, - }); - } - enum_ty - } - - fn infer_bare_variant_call( - &mut self, - callee: ExprId, - args: &[ExprId], - lvl: usize, - ) -> Option { - let nodes = self.function.node_store(); - if nodes.node_kind(callee) != NodeKind::Field { - return None; - } - - let field = nodes.field(nodes.as_field(callee).expect("Field node mismatch")); - let (field_expr, field_name_expr) = field; - if field_expr != ExprId::ZERO { - return None; - } - - let field_name_id = nodes.as_name(field_name_expr)?; - let field_name = nodes.name(field_name_id); - let payload: Vec<(ExprId, InferTy)> = - args.iter().map(|&arg| (arg, self.infer_expr(arg, lvl))).collect(); - - let enum_ty = self.fresh_var(lvl.saturating_sub(1)); - if let InferTy::Var(enum_var) = enum_ty.clone() { - self.variant_constraints.push(VariantConstraint { - enum_var, - variant: field_name, - payload, - name_node: field_name_expr, - }); - Some(enum_ty) - } else { - None - } - } - - fn infer_method_call( - &mut self, - node: ExprId, - callee: ExprId, - args: &[ExprId], - lvl: usize, - ) -> Option { - let nodes = self.function.node_store(); - let field = nodes.as_field(callee)?; - let (receiver_expr, field_name_expr) = nodes.field(field); - if receiver_expr == ExprId::ZERO { - return None; - } - - if nodes.node_kind(receiver_expr) == NodeKind::Name - && let Some(name_id) = nodes.as_name(receiver_expr) - && self.resolve_type_in_node_scope(receiver_expr, nodes.name(name_id)).is_some() - { - return None; - } - - let field_name_id = nodes.as_name(field_name_expr)?; - let field_name = nodes.name(field_name_id); - let receiver_ty = self.infer_expr(receiver_expr, lvl); - let receiver_known = Self::known_ty(&receiver_ty)?; - let method = resolve_method_for_receiver(self.db, receiver_known, field_name)?; - let callee_ty = self.infer_function_binding_ty(method.function, lvl); - self.node_types.insert(callee, callee_ty.clone()); - - if method.function.hir_function(self.db).function(self.db).is_unsafe() { - self.require_unsafe_context(node); - } - - let InferTy::Function(inputs, output) = callee_ty else { - return None; - }; - let method_arity = inputs.len().saturating_sub(1); - if method_arity != args.len() { - for &arg in args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch(node, method_arity, args.len())); - return Some(*output); - } - - if let Some(receiver_input) = inputs.first() { - if self.function_param_is_mutable(method.function, 0) { - self.check_mutable_argument(receiver_expr, receiver_input, lvl); - } else { - let _ = self.constrain_top(&receiver_ty, receiver_input); - } - } - for (index, (&arg, input_ty)) in args.iter().zip(inputs.iter().skip(1)).enumerate() { - if self.function_param_is_mutable(method.function, index + 1) { - self.check_mutable_argument(arg, input_ty, lvl); - } else { - self.check_expr(arg, input_ty, lvl); - } - } - - Some(*output) - } - - fn function_param_is_mutable(&self, function: FunctionLocation<'db>, index: usize) -> bool { - function - .source(self.db) - .params() - .and_then(|params| params.iter().nth(index)) - .is_some_and(|param| param.is_mutable()) - } - - fn check_mutable_argument(&mut self, arg: ExprId, input_ty: &InferTy, lvl: usize) { - let _ = match self.resolve_place(arg, lvl) { - PlaceResolution::Mutable(_) => self.check_expr(arg, input_ty, lvl), - PlaceResolution::Immutable | PlaceResolution::Invalid => { - self.emit(DiagnosticKind::MutableArgumentRequiresPlace(arg)); - self.check_expr(arg, input_ty, lvl) - } - }; - } - - fn infer_type_qualified_enum_variant( - &mut self, - field_expr: ExprId, - field_name: Symbol<'db>, - field_name_expr: ExprId, - ) -> Option { - let nodes = self.function.node_store(); - let base_name_id = nodes.as_name(field_expr).expect("field base should be Name"); - let base_name = nodes.name(base_name_id); - let ty = self.resolve_type_in_node_scope(field_expr, base_name)?; - - if let Some(variant_ty) = self.enum_variant_infer_ty(ty, field_name) { - return Some(variant_ty); - } - - if matches!(ty.kind(self.db), TyKind::Enum(_)) { - self.emit(DiagnosticKind::UnresolvedIdent(field_name_expr)); - } else { - self.emit(DiagnosticKind::ExpectedValueFoundType(field_expr, ty)); - } - - Some(InferTy::Unknown) - } - - fn infer_field_expr( - &mut self, - node: ExprId, - field_expr: ExprId, - field_name_expr: ExprId, - _expected: Option<&InferTy>, - lvl: usize, - ) -> InferTy { - let nodes = self.function.node_store(); - let Some(field_name_id) = nodes.as_name(field_name_expr) else { - self.emit(DiagnosticKind::UnresolvedIdent(node)); - return InferTy::Unknown; - }; - let field_name = nodes.name(field_name_id); - - if field_expr == ExprId::ZERO { - return self.infer_bare_enum_variant(field_name, field_name_expr, lvl); - } - - if nodes.node_kind(field_expr) == NodeKind::Name - && let Some(variant_ty) = - self.infer_type_qualified_enum_variant(field_expr, field_name, field_name_expr) - { - return variant_ty; - } - - let base_ty = self.infer_expr(field_expr, lvl); - match base_ty { - InferTy::Unknown => InferTy::Unknown, - InferTy::Record(fields) => fields - .iter() - .find(|(name_bits, _)| *name_bits == Self::symbol_to_bits(field_name)) - .map_or_else( - || { - self.emit(DiagnosticKind::UnknownStructField(node, field_name)); - InferTy::Unknown - }, - |(_, ty)| ty.clone(), - ), - InferTy::Known(bits) => { - let ty = Ty::from_id(salsa::Id::from_bits(bits)); - if let TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty) = - ty.kind(self.db) - { - let fields = struct_fields(self.db, *struct_ty); - if let Some((_, field_ty)) = fields.iter().find(|(name, _)| *name == field_name) - { - self.ty_to_infer_ty(*field_ty) - } else { - self.emit(DiagnosticKind::UnknownStructField(node, field_name)); - InferTy::Unknown - } - } else { - self.emit(DiagnosticKind::NotAStruct(node, ty)); - InferTy::Unknown - } - } - InferTy::Var(_) => { - let result = self.fresh_var(lvl); - let field_req = - InferTy::Record(vec![(Self::symbol_to_bits(field_name), result.clone())]); - let _ = self.constrain_top(&base_ty, &field_req); - result - } - other => { - self.emit(DiagnosticKind::NotAStruct(node, self.diagnostic_ty(&other))); - InferTy::Unknown - } - } - } - - fn collect_enum_candidates(&self, enum_var: VarId) -> FxHashSet { - let mut candidates: FxHashSet = FxHashSet::default(); - let mut seen_vars: FxHashSet = FxHashSet::default(); - let mut stack: Vec = vec![InferTy::Var(enum_var)]; - - while let Some(ty) = stack.pop() { - match ty { - InferTy::Var(var) => { - if !seen_vars.insert(var) { - continue; - } - stack.extend(self.vars[var].lower_bounds.iter().cloned()); - stack.extend(self.vars[var].upper_bounds.iter().cloned()); - } - InferTy::Known(bits) => { - let ty = Ty::from_id(salsa::Id::from_bits(bits)); - if matches!(ty.kind(self.db), TyKind::Enum(_)) { - candidates.insert(bits); - } - } - InferTy::Function(inputs, output) => { - stack.extend(inputs); - stack.push(*output); - } - InferTy::Array(item) => stack.push(*item), - InferTy::Tuple(items) => stack.extend(items), - InferTy::Record(fields) => stack.extend(fields.into_iter().map(|(_, ty)| ty)), - InferTy::Union(items) | InferTy::Inter(items) => stack.extend(items), - InferTy::Unknown => {} - } - } - - candidates - } - - fn solve_variant_constraints(&mut self, emit_unresolved: bool) -> bool { - let constraints = std::mem::take(&mut self.variant_constraints); - let total = constraints.len(); - let mut pending = Vec::new(); - - for constraint in constraints { - if !self.solve_variant_constraint(&constraint) { - if emit_unresolved { - self.emit(DiagnosticKind::UnresolvedIdent(constraint.name_node)); - } else { - pending.push(constraint); - } - } - } - - self.variant_constraints = pending; - self.variant_constraints.len() != total - } - - fn solve_variant_constraint(&mut self, constraint: &VariantConstraint<'db>) -> bool { - let candidates = self.collect_enum_candidates(constraint.enum_var); - let mut narrowed = Vec::new(); - - for candidate_bits in &candidates { - let enum_ty = Ty::from_id(salsa::Id::from_bits(*candidate_bits)); - let Some(variant_ty) = self.enum_variant_infer_ty(enum_ty, constraint.variant) else { - continue; - }; - - let (inputs, output) = match variant_ty { - InferTy::Known(_) => (Vec::new(), InferTy::Known(*candidate_bits)), - InferTy::Function(inputs, output) => (inputs, *output), - _ => continue, - }; - - if inputs.len() == constraint.payload.len() { - narrowed.push((inputs, output)); - } - } - - if narrowed.is_empty() { - if candidates.len() == 1 { - self.emit(DiagnosticKind::UnresolvedIdent(constraint.name_node)); - return true; - } - return false; - } - - if narrowed.len() != 1 { - return false; - } - - let (inputs, output) = narrowed.pop().expect("one narrowed candidate expected"); - - for ((arg_node, arg_ty), input_ty) in constraint.payload.iter().zip(inputs.iter()) { - if self.constrain_top(arg_ty, input_ty).is_err() { - let actual = self.diagnostic_ty(arg_ty); - let expected = self.diagnostic_ty(input_ty); - if actual != expected { - self.emit(DiagnosticKind::TypeMismatch(*arg_node, actual, expected)); - } - return true; - } - } - - let enum_var_ty = InferTy::Var(constraint.enum_var); - let _ = self.constrain_eq_top(&enum_var_ty, &output); - true - } - - fn coalesce_type_raw(&self, ty: &InferTy, polarity: Polarity) -> Ty<'db> { - let mut recursive: FxHashMap<(VarId, Polarity), u32> = FxHashMap::default(); - let mut in_process: FxHashSet<(VarId, Polarity)> = FxHashSet::default(); - // Keep presentation-time recursive binders disjoint from inference vars. - let mut next_present_var = self.vars.len() as u32; - self.coalesce_raw(ty, polarity, &mut in_process, &mut recursive, &mut next_present_var) - } - - // Presentation-only conversion: internal InferTy graph -> user-facing Ty plus - // cleanup. - fn present_type(&self, ty: &InferTy, polarity: Polarity) -> Ty<'db> { - simplify(self.db, self.coalesce_type_raw(ty, polarity), &self.preserved_type_vars) - } - - fn coalesce_type_for_missing_param(&self, ty: &InferTy) -> Ty<'db> { - let positive = self.present_type(ty, Polarity::Positive); - if !matches!(positive.kind(self.db), TyKind::Unknown) { - return positive; - } - - let negative = self.present_type(ty, Polarity::Negative); - - if matches!(negative.kind(self.db), TyKind::Unknown) { positive } else { negative } - } - - // Final "presentation" phase: convert inferred InferTy graph into user-facing - // Ty syntax. This is intentionally separate from constraint solving. - fn finalize_user_types(&mut self) { - for (node, infer_ty) in &self.node_types { - let ty = if self.missing_param_nodes.contains(node) { - self.coalesce_type_for_missing_param(infer_ty) - } else { - self.present_type(infer_ty, Polarity::Positive) - }; - self.inference.type_of_node.insert(*node, ty); - } - for (pattern, infer_ty) in &self.selected_union_members { - let ty = self.present_type(infer_ty, Polarity::Positive); - self.inference.selected_union_members.insert(*pattern, ty); - } - for (pattern, infer_ty) in &self.matched_typed_patterns { - let ty = self.present_type(infer_ty, Polarity::Positive); - self.inference.matched_typed_patterns.insert(*pattern, ty); - } - } - - fn coalesce_raw( - &self, - ty: &InferTy, - polarity: Polarity, - in_process: &mut FxHashSet<(VarId, Polarity)>, - recursive: &mut FxHashMap<(VarId, Polarity), u32>, - next_present_var: &mut u32, - ) -> Ty<'db> { - match ty { - InferTy::Known(bits) => Ty::from_id(salsa::Id::from_bits(*bits)), - InferTy::Function(inputs, output) => { - let inputs = inputs - .iter() - .map(|t| { - self.coalesce_raw( - t, - polarity.flip(), - in_process, - recursive, - next_present_var, - ) - }) - .collect(); - let output = - self.coalesce_raw(output, polarity, in_process, recursive, next_present_var); - Ty::new(self.db, TyKind::Function { inputs, output }) - } - InferTy::Array(item) => { - let item = - self.coalesce_raw(item, polarity, in_process, recursive, next_present_var); - Ty::new(self.db, TyKind::Array(item)) - } - InferTy::Tuple(items) => { - let items = items - .iter() - .map(|t| { - self.coalesce_raw(t, polarity, in_process, recursive, next_present_var) - }) - .collect(); - Ty::new(self.db, TyKind::Tuple(items)) - } - InferTy::Record(fields) => { - let fields = fields - .iter() - .map(|(name_bits, ty)| { - ( - Self::symbol_from_bits(*name_bits), - self.coalesce_raw( - ty, - polarity, - in_process, - recursive, - next_present_var, - ), - ) - }) - .collect(); - Ty::new(self.db, TyKind::Record(fields)) - } - InferTy::Union(items) => { - let items = items - .iter() - .map(|t| { - self.coalesce_raw(t, polarity, in_process, recursive, next_present_var) - }) - .collect(); - Ty::new(self.db, TyKind::Union(items)) - } - InferTy::Inter(items) => { - let items = items - .iter() - .map(|t| { - self.coalesce_raw(t, polarity, in_process, recursive, next_present_var) - }) - .collect(); - Ty::new(self.db, TyKind::Inter(items)) - } - InferTy::Var(v) => { - let v = *v; - let key = (v, polarity); - - if in_process.contains(&key) { - let rec_var = *recursive.entry(key).or_insert_with(|| { - let id = *next_present_var; - *next_present_var += 1; - id - }); - return Ty::new(self.db, TyKind::Var(rec_var)); - } - - let bounds = match polarity { - Polarity::Positive => self.vars[v].lower_bounds.clone(), - Polarity::Negative => self.vars[v].upper_bounds.clone(), - }; - - in_process.insert(key); - let bound_types: Vec> = bounds - .iter() - .map(|b| { - self.coalesce_raw(b, polarity, in_process, recursive, next_present_var) - }) - .collect(); - in_process.remove(&key); - - let var_ty = Ty::new(self.db, TyKind::Var(v as u32)); - - let res = match polarity { - Polarity::Positive => { - if bound_types.is_empty() { - var_ty - } else { - let mut items = Vec::with_capacity(1 + bound_types.len()); - items.push(var_ty); - items.extend(bound_types); - Ty::new(self.db, TyKind::Union(items)) - } - } - Polarity::Negative => { - if bound_types.is_empty() { - var_ty - } else { - let mut items = Vec::with_capacity(1 + bound_types.len()); - items.push(var_ty); - items.extend(bound_types); - Ty::new(self.db, TyKind::Inter(items)) - } - } - }; - - if let Some(&rec_var) = recursive.get(&key) { - Ty::new(self.db, TyKind::Rec(rec_var, res)) - } else { - res - } - } - InferTy::Unknown => Ty::new(self.db, TyKind::Unknown), - } - } - - /// Resolve a type annotation to an InferTy, checking type_param_env for - /// type parameters. - fn resolve_type_to_infer_ty(&mut self, ty: TyId) -> Option { - if ty == TyId::ZERO { - return None; - } - - let nodes = self.function.node_store(); - - if let Some(tuple_id) = nodes.as_type_tuple(ty) { - let items: Vec = nodes - .type_tuple(tuple_id) - .iter() - .map(|item| self.resolve_type_to_infer_ty(item).unwrap_or(InferTy::Unknown)) - .collect(); - return Some(InferTy::Tuple(items)); - } - - if let Some(array_id) = nodes.as_type_array(ty) { - let (item_ty, _) = nodes.type_array(array_id); - let item = self.resolve_type_to_infer_ty(item_ty).unwrap_or(InferTy::Unknown); - return Some(InferTy::Array(Box::new(item))); - } - - if let Some(ptr_id) = nodes.as_type_ptr_const(ty) { - let (item_ty, _) = nodes.type_ptr_const(ptr_id); - let item = self.resolve_type_to_infer_ty(item_ty).map(|item| self.diagnostic_ty(&item)); - return Some(self.infer_ty_from_kind(TyKind::Pointer { - mutable: false, - pointee: item.unwrap_or_else(|| Ty::new(self.db, TyKind::Unknown)), - })); - } - - if let Some(ptr_id) = nodes.as_type_ptr_mut(ty) { - let (item_ty, _) = nodes.type_ptr_mut(ptr_id); - let item = self.resolve_type_to_infer_ty(item_ty).map(|item| self.diagnostic_ty(&item)); - return Some(self.infer_ty_from_kind(TyKind::Pointer { - mutable: true, - pointee: item.unwrap_or_else(|| Ty::new(self.db, TyKind::Unknown)), - })); - } - - if let Some(function_id) = nodes.as_type_function(ty) { - let (inputs_ty, output_ty) = nodes.type_function(function_id); - let inputs = if let Some(tuple_id) = nodes.as_type_tuple(inputs_ty) { - nodes - .type_tuple(tuple_id) - .iter() - .map(|item| self.resolve_type_to_infer_ty(item).unwrap_or(InferTy::Unknown)) - .collect() - } else if inputs_ty == TyId::ZERO { - Vec::new() - } else { - vec![self.resolve_type_to_infer_ty(inputs_ty).unwrap_or(InferTy::Unknown)] - }; - let output = self.resolve_type_to_infer_ty(output_ty).unwrap_or(InferTy::Unknown); - return Some(InferTy::Function(inputs, Box::new(output))); - } - - if let Some(union_id) = nodes.as_type_union(ty) { - let (lhs_ty, rhs_ty) = nodes.type_union(union_id); - let lhs = self.resolve_type_to_infer_ty(lhs_ty).unwrap_or(InferTy::Unknown); - let rhs = self.resolve_type_to_infer_ty(rhs_ty).unwrap_or(InferTy::Unknown); - return Some(Self::mk_union(lhs, rhs)); - } - - if let Some(inter_id) = nodes.as_type_inter(ty) { - let (lhs_ty, rhs_ty) = nodes.type_inter(inter_id); - let lhs = self.resolve_type_to_infer_ty(lhs_ty).unwrap_or(InferTy::Unknown); - let rhs = self.resolve_type_to_infer_ty(rhs_ty).unwrap_or(InferTy::Unknown); - return Some(Self::mk_inter(lhs, rhs)); - } - - if let Some(record_id) = nodes.as_type_record(ty) { - let fields = nodes - .type_record(record_id) - .iter() - .filter_map(|field_ty| { - let field_id = nodes.as_type_field(field_ty)?; - let (name_id, field_ty) = nodes.type_field(field_id); - let name = nodes.name(name_id); - let field_ty = - self.resolve_type_to_infer_ty(field_ty).unwrap_or(InferTy::Unknown); - Some((Self::symbol_to_bits(name), field_ty)) - }) - .collect(); - return Some(InferTy::Record(fields)); - } - - if let Some(type_apply) = nodes.as_type_apply(ty) { - let (path_ty, args_ty) = nodes.type_apply(type_apply); - let base = self.resolve_type_to_infer_ty(path_ty).unwrap_or(InferTy::Unknown); - let args = if let Some(tuple_id) = nodes.as_type_tuple(args_ty) { - nodes - .type_tuple(tuple_id) - .iter() - .map(|arg| { - self.resolve_type_to_infer_ty(arg).map_or_else( - || Ty::new(self.db, TyKind::Unknown), - |arg| self.diagnostic_ty(&arg), - ) - }) - .collect::>() - } else { - Vec::new() - }; - - return match Self::known_ty(&base) - .and_then(|base| instantiate_nominal_type(self.db, base, args)) - { - Some(ty) => Some(self.ty_to_infer_ty(ty)), - None => Some(InferTy::Unknown), - }; - } - - if let Some(type_path) = nodes.as_type_path(ty) { - let name = nodes.type_ref(type_path); - if let Some(var) = self.type_param_env.get(&name) { - return Some(var.clone()); - } - - let guard = self.resolver.scopes_for_type(ty); - let resolved = if let Some(ty) = self - .resolver - .resolve_type_binding(name) - .and_then(|binding| self.resolver.ty_for_binding(binding)) - { - Some(self.ty_to_infer_ty(ty)) - } else { - self.emit(DiagnosticKind::UnresolvedType(ty, name)); - Some(InferTy::Unknown) - }; - self.resolver.reset(guard); - return resolved; - } - - Some(InferTy::Unknown) - } - - fn resolve_sig_type( - &mut self, - ty: TyId, - sig_nodes: &NodeStore<'db>, - resolver: &Resolver<'db>, - type_param_vars: &FxHashMap, InferTy>, - lvl: usize, - ) -> InferTy { - if ty == TyId::ZERO { - return self.fresh_var(lvl); - } - - if let Some(tuple_id) = sig_nodes.as_type_tuple(ty) { - let items: Vec = sig_nodes - .type_tuple(tuple_id) - .iter() - .map(|item| self.resolve_sig_type(item, sig_nodes, resolver, type_param_vars, lvl)) - .collect(); - return InferTy::Tuple(items); - } - - if let Some(array_id) = sig_nodes.as_type_array(ty) { - let (item_ty, _) = sig_nodes.type_array(array_id); - let item = self.resolve_sig_type(item_ty, sig_nodes, resolver, type_param_vars, lvl); - return InferTy::Array(Box::new(item)); - } - - if let Some(ptr_id) = sig_nodes.as_type_ptr_const(ty) { - let (item_ty, _) = sig_nodes.type_ptr_const(ptr_id); - let item = self.resolve_sig_type(item_ty, sig_nodes, resolver, type_param_vars, lvl); - return self.infer_ty_from_kind(TyKind::Pointer { - mutable: false, - pointee: self.diagnostic_ty(&item), - }); - } - - if let Some(ptr_id) = sig_nodes.as_type_ptr_mut(ty) { - let (item_ty, _) = sig_nodes.type_ptr_mut(ptr_id); - let item = self.resolve_sig_type(item_ty, sig_nodes, resolver, type_param_vars, lvl); - return self.infer_ty_from_kind(TyKind::Pointer { - mutable: true, - pointee: self.diagnostic_ty(&item), - }); - } - - if let Some(function_id) = sig_nodes.as_type_function(ty) { - let (inputs_ty, output_ty) = sig_nodes.type_function(function_id); - let inputs = if let Some(tuple_id) = sig_nodes.as_type_tuple(inputs_ty) { - sig_nodes - .type_tuple(tuple_id) - .iter() - .map(|item| { - self.resolve_sig_type(item, sig_nodes, resolver, type_param_vars, lvl) - }) - .collect() - } else if inputs_ty == TyId::ZERO { - Vec::new() - } else { - vec![self.resolve_sig_type(inputs_ty, sig_nodes, resolver, type_param_vars, lvl)] - }; - let output = - self.resolve_sig_type(output_ty, sig_nodes, resolver, type_param_vars, lvl); - return InferTy::Function(inputs, Box::new(output)); - } - - if let Some(union_id) = sig_nodes.as_type_union(ty) { - let (lhs_ty, rhs_ty) = sig_nodes.type_union(union_id); - let lhs = self.resolve_sig_type(lhs_ty, sig_nodes, resolver, type_param_vars, lvl); - let rhs = self.resolve_sig_type(rhs_ty, sig_nodes, resolver, type_param_vars, lvl); - return Self::mk_union(lhs, rhs); - } - - if let Some(inter_id) = sig_nodes.as_type_inter(ty) { - let (lhs_ty, rhs_ty) = sig_nodes.type_inter(inter_id); - let lhs = self.resolve_sig_type(lhs_ty, sig_nodes, resolver, type_param_vars, lvl); - let rhs = self.resolve_sig_type(rhs_ty, sig_nodes, resolver, type_param_vars, lvl); - return Self::mk_inter(lhs, rhs); - } - - if let Some(record_id) = sig_nodes.as_type_record(ty) { - let fields = sig_nodes - .type_record(record_id) - .iter() - .filter_map(|field_ty| { - let field_id = sig_nodes.as_type_field(field_ty)?; - let (name_id, field_ty) = sig_nodes.type_field(field_id); - let name = sig_nodes.name(name_id); - let field_ty = - self.resolve_sig_type(field_ty, sig_nodes, resolver, type_param_vars, lvl); - Some((Self::symbol_to_bits(name), field_ty)) - }) - .collect(); - return InferTy::Record(fields); - } - - if let Some(type_apply) = sig_nodes.as_type_apply(ty) { - let (path_ty, args_ty) = sig_nodes.type_apply(type_apply); - let base = self.resolve_sig_type(path_ty, sig_nodes, resolver, type_param_vars, lvl); - let args = if let Some(tuple_id) = sig_nodes.as_type_tuple(args_ty) { - sig_nodes - .type_tuple(tuple_id) - .iter() - .map(|arg| { - let arg = - self.resolve_sig_type(arg, sig_nodes, resolver, type_param_vars, lvl); - self.diagnostic_ty(&arg) - }) - .collect::>() - } else { - Vec::new() - }; - - return match Self::known_ty(&base) - .and_then(|base| instantiate_nominal_type(self.db, base, args)) - { - Some(ty) => self.ty_to_infer_ty(ty), - None => self.fresh_var(lvl), - }; - } - - let Some(type_path) = sig_nodes.as_type_path(ty) else { - return self.fresh_var(lvl); - }; - let name = sig_nodes.type_ref(type_path); - - if let Some(var) = type_param_vars.get(&name) { - return var.clone(); - } - - match resolver - .resolve_type_binding(name) - .and_then(|binding| resolver.ty_for_binding(binding)) - { - Some(ty) => self.ty_to_infer_ty(ty), - None => self.fresh_var(lvl), - } - } - - fn infer_expr(&mut self, node: ExprId, lvl: usize) -> InferTy { - self.typecheck_expr(node, None, lvl) - } - - fn check_expr(&mut self, node: ExprId, expected: &InferTy, lvl: usize) -> InferTy { - self.typecheck_expr(node, Some(expected.clone()), lvl) - } - - fn typecheck_expr(&mut self, node: ExprId, expected: Option, lvl: usize) -> InferTy { - let nodes = self.function.node_store(); - let kind = nodes.node_kind(node); - let result = if Self::is_context_node(kind) { - self.with_context(node, |this| this.typecheck_inner(node, expected, lvl)) - } else { - self.typecheck_inner(node, expected, lvl) - }; - self.node_types.insert(node, result.clone()); - result - } - - fn typecheck_inner(&mut self, node: ExprId, expected: Option, lvl: usize) -> InferTy { - let nodes = self.function.node_store(); - let actual = match nodes.node_kind(node) { - NodeKind::Int => self.integer_literal_infer_ty(expected.as_ref()), - NodeKind::Float => self.float_infer_ty(), - NodeKind::String => self.string_infer_ty(), - NodeKind::Char => self.char_infer_ty(), - NodeKind::True | NodeKind::False => self.bool_infer_ty(), - NodeKind::Tuple => { - let tuple = nodes.tuple(nodes.as_tuple(node).expect("Tuple node mismatch")); - match &expected { - Some(InferTy::Tuple(expected_items)) if expected_items.len() == tuple.len() => { - let items: Vec = tuple - .iter() - .zip(expected_items.iter()) - .map(|(item, exp)| self.check_expr(item, exp, lvl)) - .collect(); - return InferTy::Tuple(items); - } - Some(InferTy::Tuple(expected_items)) => { - self.emit(DiagnosticKind::TupleArityMismatch( - node, - expected_items.len(), - tuple.len(), - )); - let items = tuple.iter().map(|item| self.infer_expr(item, lvl)).collect(); - return InferTy::Tuple(items); - } - _ => { - let items = tuple.iter().map(|item| self.infer_expr(item, lvl)).collect(); - InferTy::Tuple(items) - } - } - } - NodeKind::Array => { - let array = nodes.array(nodes.as_array(node).expect("Array node mismatch")); - let expected_item = match expected.as_ref() { - Some(InferTy::Array(item)) => Some((**item).clone()), - _ => None, - }; - let item_ty = expected_item.unwrap_or_else(|| self.fresh_var(lvl)); - for item in array.iter() { - self.check_expr(item, &item_ty, lvl); - } - InferTy::Array(Box::new(item_ty)) - } - NodeKind::ArrayRepeat => { - let (value, len) = - nodes.array_repeat(nodes.as_array_repeat(node).expect("ArrayRepeat mismatch")); - let expected_item = match expected.as_ref() { - Some(InferTy::Array(item)) => Some((**item).clone()), - _ => None, - }; - let item_ty = expected_item.unwrap_or_else(|| self.fresh_var(lvl)); - self.check_expr(value, &item_ty, lvl); - self.check_expr(len, &self.int_infer_ty(), lvl); - InferTy::Array(Box::new(item_ty)) - } - NodeKind::LocalVar => { - let var_id = nodes.as_local_var(node).expect("LocalVar node mismatch"); - let var = nodes.local_var(var_id); - let ty = if var.initializer != ExprId::ZERO { - self.infer_expr(var.initializer, lvl) - } else { - InferTy::Unknown - }; - self.bind_pattern_root( - var.pattern, - &ty, - lvl, - PatternBindingScheme::Mono, - false, - node, - ); - return InferTy::Tuple(Vec::new()); - } - NodeKind::Name => self.infer_name_expr(node, lvl), - NodeKind::Field => { - let (field_expr, field_name_expr) = - nodes.field(nodes.as_field(node).expect("Field node mismatch")); - self.infer_field_expr(node, field_expr, field_name_expr, expected.as_ref(), lvl) - } - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(node).expect("Block node mismatch")); - let mut returned = false; - - for stmt in stmts.iter() { - if self.typecheck_stmt(stmt, lvl) { - returned = true; - break; - } - } - - if returned { - return expected.unwrap_or_else(|| InferTy::Tuple(Vec::new())); - } - - if tail != ExprId::ZERO { - return self.typecheck_expr(tail, expected, lvl); - } - return InferTy::Tuple(Vec::new()); - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(node).expect("UnsafeBlock mismatch")); - if body != ExprId::ZERO { - return self - .with_unsafe_context(|this| this.typecheck_expr(body, expected, lvl)); - } - return Self::unit_infer_ty(); - } - NodeKind::Binary => { - let binary = nodes.binary(nodes.as_binary(node).expect("Binary node mismatch")); - let lhs_ty = self.infer_expr(binary.lhs, lvl); - let rhs_ty = self.infer_expr(binary.rhs, lvl); - - if binary.op == ExprId::ZERO { - return InferTy::Unknown; - } - - let op_sym = nodes.name(nodes.as_name(binary.op).expect("op should be Name")); - let emit_invalid = |this: &mut Self| { - this.emit(DiagnosticKind::InvalidBinaryOp( - node, - op_sym, - this.diagnostic_ty(&lhs_ty), - this.diagnostic_ty(&rhs_ty), - )); - InferTy::Unknown - }; - let constrain_numeric = |this: &mut Self, ty: &InferTy, kind: NumericKind| { - let expected = this.numeric_infer_ty(kind); - this.constrain_top(ty, &expected).is_ok() - }; - let constrain_orderable = |this: &mut Self, ty: &InferTy, kind: OrderableKind| { - let expected = this.orderable_infer_ty(kind); - this.constrain_top(ty, &expected).is_ok() - }; - - match op_sym.text(self.db) { - "+" | "-" | "*" | "/" | "%" => { - if matches!(lhs_ty, InferTy::Unknown) || matches!(rhs_ty, InferTy::Unknown) - { - InferTy::Unknown - } else { - let expected_numeric = - expected.as_ref().and_then(|ty| self.numeric_kind(ty)); - - let target = expected_numeric - .or(self.numeric_kind(&lhs_ty)) - .or(self.numeric_kind(&rhs_ty)); - - match target { - Some(target) => { - let lhs_ty = if Self::literal_can_coerce_to_numeric(target) - && nodes.node_kind(binary.lhs) == NodeKind::Int - { - self.check_expr( - binary.lhs, - &self.numeric_infer_ty(target), - lvl, - ) - } else { - lhs_ty.clone() - }; - let rhs_ty = if Self::literal_can_coerce_to_numeric(target) - && nodes.node_kind(binary.rhs) == NodeKind::Int - { - self.check_expr( - binary.rhs, - &self.numeric_infer_ty(target), - lvl, - ) - } else { - rhs_ty.clone() - }; - if !constrain_numeric(self, &lhs_ty, target) - || !constrain_numeric(self, &rhs_ty, target) - { - emit_invalid(self) - } else { - self.numeric_infer_ty(target) - } - } - None => InferTy::Unknown, - } - } - } - "==" | "!=" => { - if matches!(lhs_ty, InferTy::Unknown) || matches!(rhs_ty, InferTy::Unknown) - { - InferTy::Unknown - } else { - let target = self.numeric_kind(&lhs_ty).or(self.numeric_kind(&rhs_ty)); - let (lhs_ty, rhs_ty) = if let Some(target) = target { - let lhs_ty = if Self::literal_can_coerce_to_numeric(target) - && nodes.node_kind(binary.lhs) == NodeKind::Int - { - self.check_expr(binary.lhs, &self.numeric_infer_ty(target), lvl) - } else { - lhs_ty.clone() - }; - let rhs_ty = if Self::literal_can_coerce_to_numeric(target) - && nodes.node_kind(binary.rhs) == NodeKind::Int - { - self.check_expr(binary.rhs, &self.numeric_infer_ty(target), lvl) - } else { - rhs_ty.clone() - }; - (lhs_ty, rhs_ty) - } else { - (lhs_ty.clone(), rhs_ty.clone()) - }; - let same = self.constrain_eq_top(&lhs_ty, &rhs_ty).is_ok(); - if same { self.bool_infer_ty() } else { emit_invalid(self) } - } - } - "<" | ">" | "<=" | ">=" => { - if matches!(lhs_ty, InferTy::Unknown) || matches!(rhs_ty, InferTy::Unknown) - { - InferTy::Unknown - } else { - let target = - self.orderable_kind(&lhs_ty).or(self.orderable_kind(&rhs_ty)); - match target { - Some(target) => { - let lhs_ty = if Self::literal_can_coerce_to_orderable(target) - && nodes.node_kind(binary.lhs) == NodeKind::Int - { - self.check_expr( - binary.lhs, - &self.orderable_infer_ty(target), - lvl, - ) - } else { - lhs_ty.clone() - }; - let rhs_ty = if Self::literal_can_coerce_to_orderable(target) - && nodes.node_kind(binary.rhs) == NodeKind::Int - { - self.check_expr( - binary.rhs, - &self.orderable_infer_ty(target), - lvl, - ) - } else { - rhs_ty.clone() - }; - if constrain_orderable(self, &lhs_ty, target) - && constrain_orderable(self, &rhs_ty, target) - { - self.bool_infer_ty() - } else { - emit_invalid(self) - } - } - None => emit_invalid(self), - } - } - } - "&&" | "||" => { - let bool_ty = self.bool_infer_ty(); - if matches!(lhs_ty, InferTy::Unknown) || matches!(rhs_ty, InferTy::Unknown) - { - InferTy::Unknown - } else if self.constrain_top(&lhs_ty, &bool_ty).is_ok() - && self.constrain_top(&rhs_ty, &bool_ty).is_ok() - { - bool_ty - } else { - emit_invalid(self) - } - } - _ => emit_invalid(self), - } - } - NodeKind::Postfix => { - let postfix = nodes.postfix(nodes.as_postfix(node).expect("Postfix node mismatch")); - let expr_ty = self.infer_expr(postfix.expr, lvl); - - if postfix.op == ExprId::ZERO { - return InferTy::Unknown; - } - - let op_sym = nodes.name(nodes.as_name(postfix.op).expect("op should be Name")); - - match &expr_ty { - InferTy::Unknown | InferTy::Var(_) => InferTy::Unknown, - _ => { - self.emit(DiagnosticKind::InvalidPostfixOp( - node, - op_sym, - self.diagnostic_ty(&expr_ty), - )); - InferTy::Unknown - } - } - } - NodeKind::Prefix => { - let prefix = nodes.prefix(nodes.as_prefix(node).expect("Prefix node mismatch")); - let expr_ty = self.infer_expr(prefix.expr, lvl); - - if prefix.op == ExprId::ZERO { - return InferTy::Unknown; - } - - let op_sym = nodes.name(nodes.as_name(prefix.op).expect("op should be Name")); - let emit_invalid = |this: &mut Self| { - this.emit(DiagnosticKind::InvalidPrefixOp( - node, - op_sym, - this.diagnostic_ty(&expr_ty), - )); - InferTy::Unknown - }; - - if matches!(expr_ty, InferTy::Unknown) { - InferTy::Unknown - } else { - match op_sym.text(self.db) { - "!" => { - let bool_ty = self.bool_infer_ty(); - if self.constrain_top(&expr_ty, &bool_ty).is_ok() { - bool_ty - } else { - emit_invalid(self) - } - } - "-" => { - let expected_numeric = - expected.as_ref().and_then(|ty| self.numeric_kind(ty)); - let target = expected_numeric.or(self.numeric_kind(&expr_ty)); - - match target { - Some(target) => { - let target_ty = self.numeric_infer_ty(target); - if self.constrain_top(&expr_ty, &target_ty).is_ok() { - target_ty - } else { - emit_invalid(self) - } - } - None => { - if matches!(expr_ty, InferTy::Var(_)) { - InferTy::Unknown - } else { - emit_invalid(self) - } - } - } - } - _ => emit_invalid(self), - } - } - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(node).expect("If node mismatch")); - let bool_ty = self.bool_infer_ty(); - self.check_expr(if_expr.cond, &bool_ty, lvl); - - if let Some(expected_ty) = &expected { - if if_expr.then_branch != ExprId::ZERO { - self.check_expr(if_expr.then_branch, expected_ty, lvl); - } - if if_expr.else_branch != ExprId::ZERO { - self.check_expr(if_expr.else_branch, expected_ty, lvl); - } else if *expected_ty != InferTy::Tuple(Vec::new()) { - self.emit(DiagnosticKind::MissingElseBranch(node)); - } - return expected_ty.clone(); - } - - let then_ty = if if_expr.then_branch != ExprId::ZERO { - self.infer_expr(if_expr.then_branch, lvl) - } else { - InferTy::Tuple(Vec::new()) - }; - let else_ty = if if_expr.else_branch != ExprId::ZERO { - self.infer_expr(if_expr.else_branch, lvl) - } else { - InferTy::Tuple(Vec::new()) - }; - - // Try to unify branches via subtyping. - let result = self.fresh_var(lvl); - let _ = self.constrain_top(&then_ty, &result); - let _ = self.constrain_top(&else_ty, &result); - result - } - NodeKind::Match => { - let (scrutinee, arms) = - nodes.match_expr(nodes.as_match(node).expect("Match node mismatch")); - let scrutinee_ty = self.infer_expr(scrutinee, lvl); - - if let Some(expected_ty) = &expected { - for arm in arms.iter() { - let (pattern, expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm")); - let saved_env = self.env.clone(); - let saved_binding_names = self.binding_names.clone(); - self.bind_pattern_root( - pattern, - &scrutinee_ty, - lvl, - PatternBindingScheme::Mono, - true, - expr, - ); - self.check_expr(expr, expected_ty, lvl); - self.env = saved_env; - self.binding_names = saved_binding_names; - } - return expected_ty.clone(); - } - - let result = self.fresh_var(lvl); - for arm in arms.iter() { - let (pattern, expr) = - nodes.match_arm(nodes.as_match_arm(arm).expect("MatchArm")); - let saved_env = self.env.clone(); - let saved_binding_names = self.binding_names.clone(); - self.bind_pattern_root( - pattern, - &scrutinee_ty, - lvl, - PatternBindingScheme::Mono, - true, - expr, - ); - let arm_ty = self.infer_expr(expr, lvl); - let _ = self.constrain_top(&arm_ty, &result); - self.env = saved_env; - self.binding_names = saved_binding_names; - } - result - } - NodeKind::LoopExpr => { - let (body, _) = - nodes.loop_expr(nodes.as_loop_expr(node).expect("LoopExpr node mismatch")); - let unit_ty = InferTy::Tuple(Vec::new()); - if body != ExprId::ZERO { - self.with_loop_depth(|this| { - this.check_expr(body, &unit_ty, lvl); - }); - } - unit_ty - } - NodeKind::BreakExpr => { - if self.loop_depth == 0 { - self.emit(DiagnosticKind::BreakOutsideLoop(node)); - } - InferTy::Tuple(Vec::new()) - } - NodeKind::ContinueExpr => { - if self.loop_depth == 0 { - self.emit(DiagnosticKind::ContinueOutsideLoop(node)); - } - InferTy::Tuple(Vec::new()) - } - NodeKind::Closure => { - let (params, body) = - nodes.closure_parts(nodes.as_closure(node).expect("Closure node mismatch")); - - if let Some(InferTy::Function(exp_inputs, exp_output)) = &expected { - if exp_inputs.len() == params.len() { - for (param, exp_ty) in params.iter().zip(exp_inputs.iter()) { - let (pattern, ty_id) = nodes.param(param); - let anchor = self.pattern_anchor(pattern).unwrap_or(node); - let annotated = self.resolve_type_to_infer_ty(ty_id); - let param_ty = match annotated { - Some(annotated_infer_ty) => { - if !matches!(&annotated_infer_ty, InferTy::Unknown) - && !matches!(exp_ty, InferTy::Unknown) - && self.constrain_top(&annotated_infer_ty, exp_ty).is_err() - { - self.emit(DiagnosticKind::TypeMismatch( - anchor, - self.diagnostic_ty(&annotated_infer_ty), - self.diagnostic_ty(exp_ty), - )); - } - annotated_infer_ty - } - None => exp_ty.clone(), - }; - self.bind_pattern_root( - pattern, - ¶m_ty, - lvl, - PatternBindingScheme::Mono, - false, - anchor, - ); - } - let output = if body != ExprId::ZERO { - self.check_expr(body, exp_output, lvl) - } else { - InferTy::Unknown - }; - return InferTy::Function(exp_inputs.clone(), Box::new(output)); - } - self.emit(DiagnosticKind::ClosureArityMismatch( - node, - params.len(), - exp_inputs.len(), - )); - } - - let mut inputs = Vec::with_capacity(params.len()); - for param in params.iter() { - let (pattern, ty_id) = nodes.param(param); - let anchor = self.pattern_anchor(pattern).unwrap_or(node); - let param_ty = match self.resolve_type_to_infer_ty(ty_id) { - Some(ty) => ty, - None => self.fresh_var(lvl), - }; - self.bind_pattern_root( - pattern, - ¶m_ty, - lvl, - PatternBindingScheme::Mono, - false, - anchor, - ); - inputs.push(param_ty); - } - let output = if body != ExprId::ZERO { - self.infer_expr(body, lvl) - } else { - InferTy::Unknown - }; - InferTy::Function(inputs, Box::new(output)) - } - NodeKind::Call => { - let (callee, args) = nodes.call(nodes.as_call(node).expect("Call node mismatch")); - let args: Vec = args.iter().collect(); - if let Some(intrinsic_result) = - self.compiler_intrinsic_call(node, callee, &args, expected.as_ref(), lvl) - { - return intrinsic_result; - } - if let Some(variant_result) = self.infer_bare_variant_call(callee, &args, lvl) { - return variant_result; - } - if let Some(method_result) = self.infer_method_call(node, callee, &args, lvl) { - return method_result; - } - let direct_target = if nodes.node_kind(callee) == NodeKind::Name - && let Some(name_id) = nodes.as_name(callee) - && let Some(BindingId::Function(target)) = - self.resolve_path_in_node_scope(callee, nodes.name(name_id)) - { - Some(target) - } else { - None - }; - if nodes.node_kind(callee) == NodeKind::Name - && let Some(target) = direct_target - && target.hir_function(self.db).function(self.db).is_unsafe() - { - self.require_unsafe_context(node); - } - let callee_ty = self.infer_expr(callee, lvl); - - match &callee_ty { - InferTy::Function(inputs, output) => { - if inputs.len() != args.len() { - for &arg in &args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallArityMismatch( - node, - inputs.len(), - args.len(), - )); - } else { - for (index, (&arg, input_ty)) in - args.iter().zip(inputs.iter()).enumerate() - { - if direct_target.is_some_and(|target| { - self.function_param_is_mutable(target, index) - }) { - self.check_mutable_argument(arg, input_ty, lvl); - } else { - self.check_expr(arg, input_ty, lvl); - } - } - } - *output.clone() - } - InferTy::Unknown => { - for &arg in &args { - self.infer_expr(arg, lvl); - } - InferTy::Unknown - } - InferTy::Var(_) => { - // Apply function via constraint: callee <: (args) -> result - let arg_tys: Vec = - args.iter().map(|&arg| self.infer_expr(arg, lvl)).collect(); - let result = self.fresh_var(lvl); - let fun_ty = InferTy::Function(arg_tys, Box::new(result.clone())); - let _ = self.constrain_top(&callee_ty, &fun_ty); - result - } - _ => { - for &arg in &args { - self.infer_expr(arg, lvl); - } - self.emit(DiagnosticKind::CallNonFunction( - node, - self.diagnostic_ty(&callee_ty), - )); - InferTy::Unknown - } - } - } - NodeKind::StructExpr => { - let struct_expr_id = nodes.as_struct_expr(node).expect("StructExpr node mismatch"); - let items = nodes.struct_expr(struct_expr_id); - - let has_struct_name = items.len() % 2 == 1; - if !has_struct_name { - let mut fields: Vec<(u64, InferTy)> = Vec::new(); - let mut i = 0; - while i + 1 < items.len() { - let field_name_id = items.get(i).unwrap(); - let field_expr_id = items.get(i + 1).unwrap(); - i += 2; - - let field_sym = nodes - .name(nodes.as_name(field_name_id).expect("field name should be Name")); - let field_bits = Self::symbol_to_bits(field_sym); - let field_ty = self.infer_expr(field_expr_id, lvl); - if let Some((_, existing)) = - fields.iter_mut().find(|(name_bits, _)| *name_bits == field_bits) - { - *existing = field_ty; - } else { - fields.push((field_bits, field_ty)); - } - } - InferTy::Record(fields) - } else { - // items layout for named struct expr: - // [struct_name, field1_name, field1_expr, ...] - let name_id = items.get(0).unwrap(); - let name_sym = nodes - .name(nodes.as_name(name_id).expect("struct expr name should be Name")); - - let Some(ty) = self.resolve_type_in_node_scope(name_id, name_sym) else { - self.emit(DiagnosticKind::UnresolvedIdent(name_id)); - // Still infer field exprs. - let mut i = 1; - while i + 1 < items.len() { - self.infer_expr(items.get(i + 1).unwrap(), lvl); - i += 2; - } - return InferTy::Unknown; - }; - - let ty = expected - .as_ref() - .and_then(Self::known_ty) - .and_then(|expected_ty| self.match_expected_nominal_type(ty, expected_ty)) - .unwrap_or(ty); - - let struct_ty = match ty.kind(self.db) { - TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty) => *struct_ty, - _ => { - // Still infer field exprs. - let mut i = 1; - while i + 1 < items.len() { - self.infer_expr(items.get(i + 1).unwrap(), lvl); - i += 2; - } - self.emit(DiagnosticKind::NotAStruct(node, ty)); - return InferTy::Unknown; - } - }; - let fields = struct_fields(self.db, struct_ty); - - let field_map: FxHashMap, Ty<'db>> = - fields.iter().map(|(name, ty)| (*name, *ty)).collect(); - let mut seen_fields: FxHashSet> = FxHashSet::default(); - - let mut i = 1; - while i + 1 < items.len() { - let field_name_id = items.get(i).unwrap(); - let field_expr_id = items.get(i + 1).unwrap(); - i += 2; - - let field_sym = nodes - .name(nodes.as_name(field_name_id).expect("field name should be Name")); - - if let Some(&expected_ty) = field_map.get(&field_sym) { - seen_fields.insert(field_sym); - let expected_infer_ty = self.ty_to_infer_ty(expected_ty); - self.check_expr(field_expr_id, &expected_infer_ty, lvl); - } else { - self.infer_expr(field_expr_id, lvl); - self.emit(DiagnosticKind::UnknownStructField(node, field_sym)); - } - } - - for (field_name, _) in fields.iter() { - if !seen_fields.contains(field_name) { - self.emit(DiagnosticKind::MissingStructField(node, *field_name)); - } - } - - self.node_types.insert(name_id, self.ty_to_infer_ty(ty)); - InferTy::Known(ty.as_id().as_bits()) - } - } - _ => InferTy::Unknown, - }; - - if let Some(ref expected_ty) = expected { - self.coerce(node, &actual, expected_ty); - expected_ty.clone() - } else { - actual - } - } - - fn coerce(&mut self, node: ExprId, actual: &InferTy, expected: &InferTy) { - let _ = self.solve_variant_constraints(false); - if self.should_defer_coercion(actual, expected) { - self.deferred_coercions.push(DeferredCoercion { - node, - actual: actual.clone(), - expected: expected.clone(), - }); - return; - } - self.coerce_now(node, actual, expected); - } - - fn coerce_now(&mut self, node: ExprId, actual: &InferTy, expected: &InferTy) { - if matches!(expected, InferTy::Unknown) || matches!(actual, InferTy::Unknown) { - return; - } - if self.constrain_top(actual, expected).is_err() { - let actual_ty = self.diagnostic_ty(actual); - let expected_ty = self.diagnostic_ty(expected); - if actual_ty != expected_ty { - self.emit(DiagnosticKind::TypeMismatch(node, actual_ty, expected_ty)); - } - } else { - let _ = self.solve_variant_constraints(false); - } - } - - fn should_defer_coercion(&self, actual: &InferTy, expected: &InferTy) -> bool { - self.has_pending_variant_dependency(actual) && !self.is_enum_nominal(expected) - } - - fn is_enum_nominal(&self, ty: &InferTy) -> bool { - let InferTy::Known(bits) = ty else { - return false; - }; - let nominal = Ty::from_id(salsa::Id::from_bits(*bits)); - matches!(nominal.kind(self.db), TyKind::Enum(_)) - } - - fn has_pending_variant_dependency(&self, ty: &InferTy) -> bool { - if self.variant_constraints.is_empty() { - return false; - } - - let pending_vars: FxHashSet = - self.variant_constraints.iter().map(|c| c.enum_var).collect(); - let mut seen_vars: FxHashSet = FxHashSet::default(); - let mut stack = vec![ty.clone()]; - - while let Some(current) = stack.pop() { - match current { - InferTy::Var(v) => { - if pending_vars.contains(&v) { - return true; - } - if !seen_vars.insert(v) { - continue; - } - stack.extend(self.vars[v].lower_bounds.iter().cloned()); - stack.extend(self.vars[v].upper_bounds.iter().cloned()); - } - InferTy::Function(inputs, output) => { - stack.extend(inputs); - stack.push(*output); - } - InferTy::Array(item) => stack.push(*item), - InferTy::Tuple(items) => stack.extend(items), - InferTy::Record(fields) => stack.extend(fields.into_iter().map(|(_, ty)| ty)), - InferTy::Union(items) | InferTy::Inter(items) => stack.extend(items), - InferTy::Known(_) | InferTy::Unknown => {} - } - } - - false - } - - fn process_deferred_coercions(&mut self) { - let deferred = std::mem::take(&mut self.deferred_coercions); - for coercion in deferred { - self.coerce_now(coercion.node, &coercion.actual, &coercion.expected); - } - } - - fn typecheck_stmt(&mut self, stmt: StmtId, lvl: usize) -> bool { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::LocalVar => { - let var_id = nodes.as_local_var(stmt).expect("LocalVar node mismatch"); - let var = nodes.local_var(var_id); - let expected_infer_ty = self.resolve_type_to_infer_ty(var.ty); - let anchor = self.pattern_anchor(var.pattern).unwrap_or(stmt.node_id()); - - let binding_ty = if var.initializer != ExprId::ZERO { - match expected_infer_ty { - Some(expected) => { - // Let RHS is always checked at a deeper level so fresh vars can be - // generalized unless they escape through constraints. - self.check_expr(var.initializer, &expected, lvl + 1); - expected - } - None => self.infer_expr(var.initializer, lvl + 1), - } - } else { - self.emit(DiagnosticKind::MissingInitializer(anchor)); - expected_infer_ty.unwrap_or_else(|| self.fresh_var(lvl)) - }; - - self.bind_pattern_root( - var.pattern, - &binding_ty, - lvl, - PatternBindingScheme::Poly { level: lvl }, - false, - anchor, - ); - false - } - NodeKind::AssignStmt => { - let assign_id = nodes.as_assign_stmt(stmt).expect("AssignStmt node mismatch"); - let (target, value) = nodes.assign_stmt(assign_id); - let _ = match self.resolve_place(target, lvl) { - PlaceResolution::Mutable(target_ty) => self.check_expr(value, &target_ty, lvl), - PlaceResolution::Immutable => { - self.emit(DiagnosticKind::AssignmentRequiresMutable(target)); - self.infer_expr(value, lvl) - } - PlaceResolution::Invalid => { - self.emit(DiagnosticKind::InvalidAssignmentTarget(target)); - self.infer_expr(value, lvl) - } - }; - false - } - NodeKind::ReturnStmt => { - let (value, _) = - nodes.return_stmt(nodes.as_return_stmt(stmt).expect("ReturnStmt mismatch")); - if value != ExprId::ZERO { - if let Some(return_ty) = self.return_ty.clone() { - self.check_expr(value, &return_ty, lvl); - } else { - self.infer_expr(value, lvl); - } - } - true - } - _ => { - if let Some(expr) = stmt_as_expr(nodes, stmt) { - self.infer_expr(expr, lvl); - } - self.expr_guarantees_return(stmt_as_expr(nodes, stmt)) - } - } - } - - fn expr_guarantees_return(&self, expr: Option) -> bool { - let Some(expr) = expr else { - return false; - }; - - let nodes = self.function.node_store(); - match nodes.node_kind(expr) { - NodeKind::Block => { - let (stmts, tail) = - nodes.block_stmts(nodes.as_block(expr).expect("Block node mismatch")); - stmts.iter().any(|stmt| self.stmt_guarantees_return(stmt)) - || self.expr_guarantees_return((tail != ExprId::ZERO).then_some(tail)) - } - NodeKind::UnsafeBlock => { - let (body, _) = - nodes.unsafe_block(nodes.as_unsafe_block(expr).expect("UnsafeBlock mismatch")); - self.expr_guarantees_return((body != ExprId::ZERO).then_some(body)) - } - NodeKind::If => { - let if_expr = nodes.if_expr(nodes.as_if(expr).expect("If node mismatch")); - if_expr.then_branch != ExprId::ZERO - && if_expr.else_branch != ExprId::ZERO - && self.expr_guarantees_return(Some(if_expr.then_branch)) - && self.expr_guarantees_return(Some(if_expr.else_branch)) - } - _ => false, - } - } - - fn stmt_guarantees_return(&self, stmt: StmtId) -> bool { - let nodes = self.function.node_store(); - match nodes.node_kind(stmt) { - NodeKind::ReturnStmt => true, - _ => self.expr_guarantees_return(stmt_as_expr(nodes, stmt)), - } - } - - fn build(mut self) -> Inference<'db> { - // Create fresh type variables for each type parameter. - for &tp in self.function.type_params() { - let var = self.fresh_var(0); - if let InferTy::Var(id) = var { - self.preserved_type_vars.insert(id as u32); - } - self.type_param_env.insert(tp, var); - } - - for ¶m in self.function.params() { - let (pattern, ty_id) = self.function.node_store().param(param); - let anchor = self.pattern_anchor(pattern).unwrap_or(ExprId::ZERO); - let param_ty = if let Some(ty) = self.resolve_type_to_infer_ty(ty_id) { - ty - } else { - self.emit(DiagnosticKind::MissingParameterType(anchor)); - for name in self.function.node_store().pattern_binding_names(pattern) { - self.missing_param_nodes.insert(name.into()); - } - self.fresh_var(0) - }; - self.bind_pattern_root( - pattern, - ¶m_ty, - 0, - PatternBindingScheme::Mono, - false, - anchor, - ); - } - - if self.function.body() == ExprId::ZERO { - let ret_ty = if self.function.ret_type() == TyId::ZERO { - Ty::new(self.db, TyKind::Tuple(Vec::new())) - } else { - self.resolve_type_to_infer_ty(self.function.ret_type()).map_or_else( - || Ty::new(self.db, TyKind::Unknown), - |ty| self.present_type(&ty, Polarity::Positive), - ) - }; - - for ¶m in self.function.params() { - let (pattern, _) = self.function.node_store().param(param); - for name in self.function.node_store().pattern_binding_names(pattern) { - let name_node = name.into(); - if let Some(param_ty) = - self.env.get(&name.into()).and_then(|scheme| match scheme { - Scheme::Mono(ty) => { - Some(if self.missing_param_nodes.contains(&name_node) { - self.coalesce_type_for_missing_param(ty) - } else { - self.present_type(ty, Polarity::Positive) - }) - } - Scheme::Poly { .. } => None, - }) - { - self.inference.type_of_node.insert(name_node, param_ty); - } - } - } - - self.inference.type_of_node.insert(self.function.body(), ret_ty); - for (pattern, infer_ty) in &self.selected_union_members { - let ty = self.present_type(infer_ty, Polarity::Positive); - self.inference.selected_union_members.insert(*pattern, ty); - } - for (pattern, infer_ty) in &self.matched_typed_patterns { - let ty = self.present_type(infer_ty, Polarity::Positive); - self.inference.matched_typed_patterns.insert(*pattern, ty); - } - return self.inference; - } - - let ret_ty = if self.function.ret_type() == TyId::ZERO { - InferTy::Tuple(Vec::new()) - } else { - self.resolve_type_to_infer_ty(self.function.ret_type()) - .unwrap_or_else(|| self.fresh_var(0)) - }; - - self.return_ty = Some(ret_ty.clone()); - self.check_expr(self.function.body(), &ret_ty, 0); - - while self.solve_variant_constraints(false) {} - - self.process_deferred_coercions(); - - while self.solve_variant_constraints(false) {} - - let _ = self.solve_variant_constraints(true); - self.emit_unknown_type_errors(); - - self.finalize_user_types(); - - self.inference - } - - fn constrain_top(&mut self, lhs: &InferTy, rhs: &InferTy) -> Result<(), ()> { - let mut cache: FxHashSet<(InferTy, InferTy)> = FxHashSet::default(); - self.constrain(lhs, rhs, &mut cache) - } - - fn constrain_eq_top(&mut self, lhs: &InferTy, rhs: &InferTy) -> Result<(), ()> { - let mut cache: FxHashSet<(InferTy, InferTy)> = FxHashSet::default(); - self.constrain(lhs, rhs, &mut cache)?; - self.constrain(rhs, lhs, &mut cache) - } - - fn constrain_with_snapshot( - &mut self, - lhs: &InferTy, - rhs: &InferTy, - cache: &mut FxHashSet<(InferTy, InferTy)>, - ) -> Result<(), ()> { - let vars_snapshot = self.vars.clone(); - let cache_snapshot = cache.clone(); - if let Ok(()) = self.constrain(lhs, rhs, cache) { - Ok(()) - } else { - self.vars = vars_snapshot; - *cache = cache_snapshot; - Err(()) - } - } - - fn constrain( - &mut self, - lhs: &InferTy, - rhs: &InferTy, - cache: &mut FxHashSet<(InferTy, InferTy)>, - ) -> Result<(), ()> { - if lhs == rhs { - return Ok(()); - } - if matches!(lhs, InferTy::Unknown) || matches!(rhs, InferTy::Unknown) { - return Ok(()); - } - - let pair = (lhs.clone(), rhs.clone()); - if matches!(lhs, InferTy::Var(_)) || matches!(rhs, InferTy::Var(_)) { - if cache.contains(&pair) { - return Ok(()); - } - cache.insert(pair); - } - - match (lhs, rhs) { - (InferTy::Union(lhs_items), rhs) => { - for lhs_item in lhs_items { - self.constrain(lhs_item, rhs, cache)?; - } - Ok(()) - } - (lhs, InferTy::Inter(rhs_items)) => { - for rhs_item in rhs_items { - self.constrain(lhs, rhs_item, cache)?; - } - Ok(()) - } - (InferTy::Inter(lhs_items), rhs) => { - let Some((last, rest)) = lhs_items.split_last() else { - return Err(()); - }; - for lhs_item in rest { - if self.constrain_with_snapshot(lhs_item, rhs, cache).is_ok() { - return Ok(()); - } - } - self.constrain(last, rhs, cache) - } - (lhs, InferTy::Union(rhs_items)) => { - let Some((last, rest)) = rhs_items.split_last() else { - return Err(()); - }; - for rhs_item in rest { - if self.constrain_with_snapshot(lhs, rhs_item, cache).is_ok() { - return Ok(()); - } - } - self.constrain(lhs, last, cache) - } - (InferTy::Known(a), InferTy::Known(b)) if a == b => Ok(()), - (InferTy::Known(a), InferTy::Known(b)) => { - let lhs_ty = Ty::from_id(salsa::Id::from_bits(*a)); - let rhs_ty = Ty::from_id(salsa::Id::from_bits(*b)); - match (lhs_ty.kind(self.db), rhs_ty.kind(self.db)) { - ( - TyKind::Pointer { mutable: true, pointee: lhs_pointee }, - TyKind::Pointer { mutable: false, pointee: rhs_pointee }, - ) if lhs_pointee == rhs_pointee => Ok(()), - _ => Err(()), - } - } - (InferTy::Function(l_in, l_out), InferTy::Function(r_in, r_out)) - if l_in.len() == r_in.len() => - { - for (l, r) in l_in.iter().zip(r_in.iter()) { - self.constrain(r, l, cache)?; - } - self.constrain(l_out, r_out, cache) - } - (InferTy::Array(l_item), InferTy::Array(r_item)) => { - self.constrain(l_item, r_item, cache) - } - (InferTy::Tuple(l_items), InferTy::Tuple(r_items)) - if l_items.len() == r_items.len() => - { - for (l, r) in l_items.iter().zip(r_items.iter()) { - self.constrain(l, r, cache)?; - } - Ok(()) - } - (InferTy::Record(l_fields), InferTy::Record(r_fields)) => { - for (r_name_bits, r_ty) in r_fields { - let Some((_, l_ty)) = - l_fields.iter().find(|(l_name_bits, _)| l_name_bits == r_name_bits) - else { - return Err(()); - }; - self.constrain(l_ty, r_ty, cache)?; - } - Ok(()) - } - (InferTy::Known(bits), InferTy::Record(r_fields)) => { - let nominal = Ty::from_id(salsa::Id::from_bits(*bits)); - let (TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty)) = - nominal.kind(self.db) - else { - return Err(()); - }; - let fields = struct_fields(self.db, *struct_ty); - - for (r_name_bits, r_ty) in r_fields { - let Some((_, field_ty)) = - fields.iter().find(|(name, _)| Self::symbol_to_bits(*name) == *r_name_bits) - else { - return Err(()); - }; - let l_ty = self.ty_to_infer_ty(*field_ty); - self.constrain(&l_ty, r_ty, cache)?; - } - Ok(()) - } - (InferTy::Record(l_fields), InferTy::Known(bits)) => { - let nominal = Ty::from_id(salsa::Id::from_bits(*bits)); - let (TyKind::Struct(struct_ty) | TyKind::ExternStruct(struct_ty)) = - nominal.kind(self.db) - else { - return Err(()); - }; - let fields = struct_fields(self.db, *struct_ty); - - if l_fields.len() != fields.len() { - return Err(()); - } - - for (field_name, field_ty) in fields { - let field_name_bits = Self::symbol_to_bits(*field_name); - let Some((_, l_ty)) = - l_fields.iter().find(|(name_bits, _)| *name_bits == field_name_bits) - else { - return Err(()); - }; - let r_ty = self.ty_to_infer_ty(*field_ty); - self.constrain(l_ty, &r_ty, cache)?; - } - Ok(()) - } - (InferTy::Var(lhs_v), _) if self.level(rhs) <= self.vars[*lhs_v].level => { - let lhs_v = *lhs_v; - self.vars[lhs_v].upper_bounds.insert(0, rhs.clone()); - let lowers = self.vars[lhs_v].lower_bounds.clone(); - for l in lowers { - self.constrain(&l, rhs, cache)?; - } - Ok(()) - } - (_, InferTy::Var(rhs_v)) if self.level(lhs) <= self.vars[*rhs_v].level => { - let rhs_v = *rhs_v; - self.vars[rhs_v].lower_bounds.insert(0, lhs.clone()); - let uppers = self.vars[rhs_v].upper_bounds.clone(); - for u in uppers { - self.constrain(lhs, &u, cache)?; - } - Ok(()) - } - (InferTy::Var(lhs_v), _) => { - let lhs_v = *lhs_v; - let rhs_ex = self.extrude( - rhs, - Polarity::Negative, - self.vars[lhs_v].level, - &mut FxHashMap::default(), - ); - self.constrain(&InferTy::Var(lhs_v), &rhs_ex, cache) - } - (_, InferTy::Var(rhs_v)) => { - let rhs_v = *rhs_v; - let lhs_ex = self.extrude( - lhs, - Polarity::Positive, - self.vars[rhs_v].level, - &mut FxHashMap::default(), - ); - self.constrain(&lhs_ex, &InferTy::Var(rhs_v), cache) - } - _ => Err(()), - } - } - - fn extrude( - &mut self, - ty: &InferTy, - pol: Polarity, - lvl: usize, - cache: &mut FxHashMap<(VarId, Polarity), VarId>, - ) -> InferTy { - if self.level(ty) <= lvl { - return ty.clone(); - } - - match ty { - InferTy::Known(_) | InferTy::Unknown => ty.clone(), - InferTy::Function(inputs, output) => InferTy::Function( - inputs.iter().map(|t| self.extrude(t, pol.flip(), lvl, cache)).collect(), - Box::new(self.extrude(output, pol, lvl, cache)), - ), - InferTy::Array(item) => InferTy::Array(Box::new(self.extrude(item, pol, lvl, cache))), - InferTy::Tuple(items) => { - InferTy::Tuple(items.iter().map(|t| self.extrude(t, pol, lvl, cache)).collect()) - } - InferTy::Record(fields) => InferTy::Record( - fields - .iter() - .map(|(name, ty)| (*name, self.extrude(ty, pol, lvl, cache))) - .collect(), - ), - InferTy::Union(items) => { - InferTy::Union(items.iter().map(|t| self.extrude(t, pol, lvl, cache)).collect()) - } - InferTy::Inter(items) => { - InferTy::Inter(items.iter().map(|t| self.extrude(t, pol, lvl, cache)).collect()) - } - InferTy::Var(tv) => { - let tv = *tv; - if let Some(nv) = cache.get(&(tv, pol)) { - return InferTy::Var(*nv); - } - let nvs = self.fresh_id(lvl); - cache.insert((tv, pol), nvs); - - match pol { - Polarity::Positive => { - self.vars[tv].upper_bounds.insert(0, InferTy::Var(nvs)); - let old_lower = self.vars[tv].lower_bounds.clone(); - let new_lower: Vec = - old_lower.iter().map(|b| self.extrude(b, pol, lvl, cache)).collect(); - self.vars[nvs].lower_bounds = new_lower; - } - Polarity::Negative => { - self.vars[tv].lower_bounds.insert(0, InferTy::Var(nvs)); - let old_upper = self.vars[tv].upper_bounds.clone(); - let new_upper: Vec = - old_upper.iter().map(|b| self.extrude(b, pol, lvl, cache)).collect(); - self.vars[nvs].upper_bounds = new_upper; - } - } - InferTy::Var(nvs) - } - } - } - - fn freshen(&mut self, lim: usize, ty: &InferTy, lvl: usize) -> InferTy { - let mut freshened: FxHashMap = FxHashMap::default(); - self.freshen_inner(lim, ty, lvl, &mut freshened) - } - - fn freshen_inner( - &mut self, - lim: usize, - ty: &InferTy, - lvl: usize, - freshened: &mut FxHashMap, - ) -> InferTy { - if self.level(ty) <= lim { - return ty.clone(); - } - - match ty { - InferTy::Known(_) | InferTy::Unknown => ty.clone(), - InferTy::Function(inputs, output) => InferTy::Function( - inputs.iter().map(|t| self.freshen_inner(lim, t, lvl, freshened)).collect(), - Box::new(self.freshen_inner(lim, output, lvl, freshened)), - ), - InferTy::Array(item) => { - InferTy::Array(Box::new(self.freshen_inner(lim, item, lvl, freshened))) - } - InferTy::Tuple(items) => InferTy::Tuple( - items.iter().map(|t| self.freshen_inner(lim, t, lvl, freshened)).collect(), - ), - InferTy::Record(fields) => InferTy::Record( - fields - .iter() - .map(|(name, ty)| (*name, self.freshen_inner(lim, ty, lvl, freshened))) - .collect(), - ), - InferTy::Union(items) => InferTy::Union( - items.iter().map(|t| self.freshen_inner(lim, t, lvl, freshened)).collect(), - ), - InferTy::Inter(items) => InferTy::Inter( - items.iter().map(|t| self.freshen_inner(lim, t, lvl, freshened)).collect(), - ), - InferTy::Var(tv) => { - let tv = *tv; - if self.vars[tv].level <= lim { - return ty.clone(); - } - if let Some(v) = freshened.get(&tv) { - return InferTy::Var(*v); - } - let v = self.fresh_id(lvl); - freshened.insert(tv, v); - - let old_lower = self.vars[tv].lower_bounds.clone(); - let old_upper = self.vars[tv].upper_bounds.clone(); - - let mut new_lower = Vec::with_capacity(old_lower.len()); - for b in old_lower.iter().rev() { - new_lower.push(self.freshen_inner(lim, b, lvl, freshened)); - } - new_lower.reverse(); - - let mut new_upper = Vec::with_capacity(old_upper.len()); - for b in old_upper.iter().rev() { - new_upper.push(self.freshen_inner(lim, b, lvl, freshened)); - } - new_upper.reverse(); - - self.vars[v].lower_bounds = new_lower; - self.vars[v].upper_bounds = new_upper; - InferTy::Var(v) - } - } - } - - fn match_expected_nominal_type( - &self, - resolved: Ty<'db>, - expected_ty: Ty<'db>, - ) -> Option> { - match (resolved.kind(self.db), expected_ty.kind(self.db)) { - (TyKind::Struct(resolved), TyKind::Struct(expected)) - if same_struct_declaration(self.db, *resolved, *expected) => - { - Some(expected_ty) - } - (TyKind::ExternStruct(resolved), TyKind::ExternStruct(expected)) - if same_struct_declaration(self.db, *resolved, *expected) => - { - Some(expected_ty) - } - (TyKind::Struct(resolved), TyKind::ExternStruct(expected)) - if same_struct_declaration(self.db, *resolved, *expected) => - { - Some(expected_ty) - } - (TyKind::ExternStruct(resolved), TyKind::Struct(expected)) - if same_struct_declaration(self.db, *resolved, *expected) => - { - Some(expected_ty) - } - _ => None, - } - } -} - -fn same_struct_declaration<'db>( - db: &'db dyn Database, - lhs: StructTy<'db>, - rhs: StructTy<'db>, -) -> bool { - lhs.module(db) == rhs.module(db) - && lhs.index(db) == rhs.index(db) - && lhs.name(db) == rhs.name(db) -} - -fn stmt_as_expr(nodes: &NodeStore<'_>, stmt: StmtId) -> Option { - match nodes.node_kind(stmt) { - NodeKind::Name => nodes.as_name(stmt).map(Into::into), - NodeKind::True => nodes.as_true(stmt).map(Into::into), - NodeKind::False => nodes.as_false(stmt).map(Into::into), - NodeKind::Error => nodes.as_error(stmt).map(Into::into), - NodeKind::Int => nodes.as_int(stmt).map(Into::into), - NodeKind::Float => nodes.as_float(stmt).map(Into::into), - NodeKind::String => nodes.as_string(stmt).map(Into::into), - NodeKind::Char => nodes.as_char(stmt).map(Into::into), - NodeKind::Tuple => nodes.as_tuple(stmt).map(Into::into), - NodeKind::Array => nodes.as_array(stmt).map(Into::into), - NodeKind::ArrayRepeat => nodes.as_array_repeat(stmt).map(Into::into), - NodeKind::Call => nodes.as_call(stmt).map(Into::into), - NodeKind::Field => nodes.as_field(stmt).map(Into::into), - NodeKind::Binary => nodes.as_binary(stmt).map(Into::into), - NodeKind::Postfix => nodes.as_postfix(stmt).map(Into::into), - NodeKind::Prefix => nodes.as_prefix(stmt).map(Into::into), - NodeKind::LoopExpr => nodes.as_loop_expr(stmt).map(Into::into), - NodeKind::BreakExpr => nodes.as_break_expr(stmt).map(Into::into), - NodeKind::ContinueExpr => nodes.as_continue_expr(stmt).map(Into::into), - NodeKind::If => nodes.as_if(stmt).map(Into::into), - NodeKind::Closure => nodes.as_closure(stmt).map(Into::into), - NodeKind::Block => nodes.as_block(stmt).map(Into::into), - NodeKind::UnsafeBlock => nodes.as_unsafe_block(stmt).map(Into::into), - NodeKind::StructExpr => nodes.as_struct_expr(stmt).map(Into::into), - _ => None, - } -} - -fn simplify<'db>(db: &'db dyn Database, ty: Ty<'db>, preserve: &FxHashSet) -> Ty<'db> { - let mut polarities: FxHashMap = FxHashMap::default(); - let mut rec_vars: FxHashSet = FxHashSet::default(); - collect_polarities( - db, - ty, - Polarity::Positive, - &mut polarities, - &mut rec_vars, - &mut FxHashSet::default(), - ); - - let remove: FxHashSet = polarities - .into_iter() - .filter(|(id, (pos, neg))| { - !(preserve.contains(id) || rec_vars.contains(id) || *pos && *neg) - }) - .map(|(id, _)| id) - .collect(); - - remove_vars(db, ty, &remove) -} - -fn collect_polarities( - db: &dyn Database, - ty: Ty<'_>, - polarity: Polarity, - polarities: &mut FxHashMap, - rec_vars: &mut FxHashSet, - seen: &mut FxHashSet<(u32, Polarity)>, -) { - match ty.kind(db) { - TyKind::Var(id) => { - let entry = polarities.entry(*id).or_insert((false, false)); - match polarity { - Polarity::Positive => entry.0 = true, - Polarity::Negative => entry.1 = true, - } - } - TyKind::Function { inputs, output } => { - for input in inputs { - collect_polarities(db, *input, polarity.flip(), polarities, rec_vars, seen); - } - collect_polarities(db, *output, polarity, polarities, rec_vars, seen); - } - TyKind::Array(item) => { - collect_polarities(db, *item, polarity, polarities, rec_vars, seen); - } - TyKind::Tuple(items) => { - for item in items { - collect_polarities(db, *item, polarity, polarities, rec_vars, seen); - } - } - TyKind::Record(fields) => { - for (_, field_ty) in fields { - collect_polarities(db, *field_ty, polarity, polarities, rec_vars, seen); - } - } - TyKind::Union(items) | TyKind::Inter(items) => { - for item in items { - collect_polarities(db, *item, polarity, polarities, rec_vars, seen); - } - } - TyKind::Rec(id, body) => { - rec_vars.insert(*id); - if !seen.insert((*id, polarity)) { - return; - } - collect_polarities(db, *body, polarity, polarities, rec_vars, seen); - } - _ => {} - } -} - -fn remove_vars<'db>(db: &'db dyn Database, ty: Ty<'db>, remove: &FxHashSet) -> Ty<'db> { - match ty.kind(db) { - TyKind::Var(id) if remove.contains(id) => Ty::new(db, TyKind::Unknown), - TyKind::Function { inputs, output } => { - let inputs = inputs.iter().map(|&t| remove_vars(db, t, remove)).collect(); - let output = remove_vars(db, *output, remove); - Ty::new(db, TyKind::Function { inputs, output }) - } - TyKind::Array(item) => { - let item = remove_vars(db, *item, remove); - Ty::new(db, TyKind::Array(item)) - } - TyKind::Tuple(items) => { - let items = items.iter().map(|&t| remove_vars(db, t, remove)).collect(); - Ty::new(db, TyKind::Tuple(items)) - } - TyKind::Record(fields) => { - let fields = - fields.iter().map(|(name, ty)| (*name, remove_vars(db, *ty, remove))).collect(); - Ty::new(db, TyKind::Record(fields)) - } - TyKind::Union(items) => { - let mut seen: FxHashSet> = FxHashSet::default(); - let mut reduced = Vec::new(); - for item in items { - let reduced_item = remove_vars(db, *item, remove); - if matches!(reduced_item.kind(db), TyKind::Unknown) { - continue; - } - if seen.insert(reduced_item) { - reduced.push(reduced_item); - } - } - match reduced.len() { - 0 => Ty::new(db, TyKind::Unknown), - 1 => reduced.pop().expect("single element"), - _ => Ty::new(db, TyKind::Union(reduced)), - } - } - TyKind::Inter(items) => { - let mut seen: FxHashSet> = FxHashSet::default(); - let mut reduced = Vec::new(); - for item in items { - let reduced_item = remove_vars(db, *item, remove); - if matches!(reduced_item.kind(db), TyKind::Unknown) { - continue; - } - if seen.insert(reduced_item) { - reduced.push(reduced_item); - } - } - match reduced.len() { - 0 => Ty::new(db, TyKind::Unknown), - 1 => reduced.pop().expect("single element"), - _ => Ty::new(db, TyKind::Inter(reduced)), - } - } - TyKind::Rec(id, body) => { - if remove.contains(id) { - remove_vars(db, *body, remove) - } else { - let body = remove_vars(db, *body, remove); - Ty::new(db, TyKind::Rec(*id, body)) - } - } - _ => ty, - } -} diff --git a/crates/mitki-typeck/src/lib.rs b/crates/mitki-typeck/src/lib.rs deleted file mode 100644 index 5d6bd04..0000000 --- a/crates/mitki-typeck/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod infer; -pub use infer::Inferable; diff --git a/crates/mitki-wasm-runtime/Cargo.toml b/crates/mitki-wasm-runtime/Cargo.toml deleted file mode 100644 index dd358ba..0000000 --- a/crates/mitki-wasm-runtime/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "mitki-wasm-runtime" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -mitki-abi.workspace = true -wasmparser.workspace = true -wasmtime.workspace = true -wasmtime-wasi.workspace = true - -[dev-dependencies] -mitki-comptime-wasm.workspace = true -mitki-db.workspace = true -mitki-inputs.workspace = true -wasm-encoder.workspace = true diff --git a/crates/mitki-wasm-runtime/examples/inspect_and_invoke.rs b/crates/mitki-wasm-runtime/examples/inspect_and_invoke.rs deleted file mode 100644 index 34a70ec..0000000 --- a/crates/mitki-wasm-runtime/examples/inspect_and_invoke.rs +++ /dev/null @@ -1,51 +0,0 @@ -use mitki_abi::{AbiScalar, AbiValue}; -use mitki_comptime_wasm::compile_file_to_wasm; -use mitki_db::RootDatabase; -use mitki_inputs::File; -use mitki_wasm_runtime::{WasmRuntime, describe_module_abi}; - -fn main() -> anyhow::Result<()> { - let db = RootDatabase::default(); - let file = File::new( - &db, - "inspect_and_invoke.mitki".into(), - r#" -import "env" fun host_base(): int; - -export fun sum_host_base(): int { - host_base() + 22 -} -"# - .to_owned(), - ); - - let bytes = compile_file_to_wasm(&db, file).map_err(|diagnostics| { - let messages = diagnostics - .iter() - .map(|diagnostic| diagnostic.message().to_owned()) - .collect::>(); - anyhow::anyhow!("expected Wasm compilation to succeed, got diagnostics: {messages:?}") - })?; - - let abi = describe_module_abi(&bytes)?; - println!("abi v2 metadata: {}", if abi.metadata.is_some() { "present" } else { "missing" }); - println!("imports:"); - for import in &abi.imports { - println!(" {}::{}", import.module, import.name); - } - println!("exports:"); - for export in &abi.exports { - println!(" {}", export.name); - } - - let mut runtime = WasmRuntime::builder() - .host_function("env", "host_base", |_args| { - Ok(Some(AbiValue::Immediate(AbiScalar::Int { signed: true, bits: 32, value: 20 }))) - }) - .instantiate(&bytes)?; - let output = runtime.invoke_export("sum_host_base", &[])?; - - println!("stdout: {:?}", output.stdout); - println!("result: {:?}", output.result); - Ok(()) -} diff --git a/crates/mitki-wasm-runtime/inspect_and_invoke.rs b/crates/mitki-wasm-runtime/inspect_and_invoke.rs deleted file mode 100644 index a1fa4c8..0000000 --- a/crates/mitki-wasm-runtime/inspect_and_invoke.rs +++ /dev/null @@ -1,51 +0,0 @@ -use mitki_abi::{AbiScalar, AbiValue}; -use mitki_comptime_wasm::compile_file_to_wasm; -use mitki_wasm_runtime::{WasmRuntime, describe_module_abi}; -use mitki_db::RootDatabase; -use mitki_inputs::File; - -fn main() -> anyhow::Result<()> { - let db = RootDatabase::default(); - let file = File::new( - &db, - "inspect_and_invoke.mitki".into(), - r#" -import "env" fun host_base(): int; - -export fun sum_host_base(): int { - host_base() + 22 -} -"# - .to_owned(), - ); - - let bytes = compile_file_to_wasm(&db, file).map_err(|diagnostics| { - let messages = diagnostics - .iter() - .map(|diagnostic| diagnostic.message().to_owned()) - .collect::>(); - anyhow::anyhow!("expected Wasm compilation to succeed, got diagnostics: {messages:?}") - })?; - - let abi = describe_module_abi(&bytes)?; - println!("abi v2 metadata: {}", if abi.metadata.is_some() { "present" } else { "missing" }); - println!("imports:"); - for import in &abi.imports { - println!(" {}::{}", import.module, import.name); - } - println!("exports:"); - for export in &abi.exports { - println!(" {}", export.name); - } - - let mut runtime = WasmRuntime::builder() - .host_function("env", "host_base", |_args| { - Ok(Some(AbiValue::Immediate(AbiScalar::Int { signed: true, bits: 32, value: 20 }))) - }) - .instantiate(&bytes)?; - let output = runtime.invoke_export("sum_host_base", &[])?; - - println!("stdout: {:?}", output.stdout); - println!("result: {:?}", output.result); - Ok(()) -} diff --git a/crates/mitki-wasm-runtime/src/lib.rs b/crates/mitki-wasm-runtime/src/lib.rs deleted file mode 100644 index 7812cb8..0000000 --- a/crates/mitki-wasm-runtime/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod runtime; -mod runtime_v2; - -pub use runtime::{ - RunConfig, WasmExportAbi, WasmExternAbiKind, WasmFunctionAbi, WasmImportAbi, WasmModuleAbi, - WasmRunOutput, WasmRuntime, WasmRuntimeBuilder, collect_unsupported_imports, - collect_unsupported_imports_with_config, describe_module_abi, invoke_export, - invoke_export_instance, invoke_export_instance_with_config, invoke_export_with_config, - invoke_raw_export_with_config, -}; -pub use runtime_v2::{describe_module_abi_v2, read_immediate_result, typed_result_slots}; diff --git a/crates/mitki-wasm-runtime/src/runtime.rs b/crates/mitki-wasm-runtime/src/runtime.rs deleted file mode 100644 index 33cb69a..0000000 --- a/crates/mitki-wasm-runtime/src/runtime.rs +++ /dev/null @@ -1,1663 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::{Context as _, anyhow, bail}; -use mitki_abi::{ - AbiValue, FunctionSignature as AbiFunctionSignature, InstanceId, LinkageKind, - SemanticTypeGraph, TransportClass, decode_canonical_blob, encode_canonical_blob, - find_import_instance_by_linkage, string_value as graph_string_value, - symbol_name as graph_symbol_name, transport_carrier_type, transport_has_wasm_lane, -}; -use wasmtime::{Caller, Extern, Linker}; -use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder, p1}; - -const WASM_PAGE_SIZE: u64 = 65_536; - -#[cfg(test)] -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum WasmValue { - Unit, - I32(i32), - Bool(bool), - F64(f64), - Char(char), - String(String), - Array(Vec), - Tuple(Vec), - Record(Vec<(String, WasmValue)>), - Struct(Vec<(String, WasmValue)>), - Enum { variant: String, fields: Vec }, - Union { arm_index: u32, value: Box }, - Intersection { carrier: Box, facets: Vec }, - Handle { type_id: u32, handle_id: u32 }, -} - -type HostImportHandler = Box anyhow::Result> + Send>; - -#[derive(Default)] -struct HostHandlers { - named: BTreeMap>, - by_instance: BTreeMap, -} - -enum HostHandlerKey { - Instance(u32), - Named { module: String, name: String }, -} - -impl HostHandlers { - fn is_empty(&self) -> bool { - self.named.is_empty() && self.by_instance.is_empty() - } - - fn insert_named(&mut self, module: String, name: String, handler: HostImportHandler) { - self.named.entry(module).or_default().insert(name, handler); - } - - fn insert_instance(&mut self, instance_id: InstanceId, handler: HostImportHandler) { - self.by_instance.insert(instance_id.0, handler); - } - - fn has_named(&self, module: &str, name: &str) -> bool { - self.named.get(module).is_some_and(|module_handlers| module_handlers.contains_key(name)) - } - - fn has_instance(&self, instance_id: InstanceId) -> bool { - self.by_instance.contains_key(&instance_id.0) - } - - fn get_mut(&mut self, key: &HostHandlerKey) -> Option<&mut HostImportHandler> { - match key { - HostHandlerKey::Instance(id) => self.by_instance.get_mut(id), - HostHandlerKey::Named { module, name } => { - self.named.get_mut(module).and_then(|module_handlers| module_handlers.get_mut(name)) - } - } - } -} - -/// The captured stdout and optional decoded result from a Wasm export -/// invocation. -#[derive(Clone, Debug, PartialEq)] -pub struct WasmRunOutput { - pub stdout: String, - pub result: Option, -} - -/// Configuration for builtin imports wired by the Wasm runtime harness. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct RunConfig { - pub enable_wasi: bool, - pub enable_mitki_runtime: bool, -} - -impl RunConfig { - pub fn wasi_default() -> Self { - Self { enable_wasi: true, enable_mitki_runtime: true } - } - - pub fn without_wasi() -> Self { - Self { enable_wasi: false, enable_mitki_runtime: true } - } -} - -impl Default for RunConfig { - fn default() -> Self { - Self::wasi_default() - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct ActiveAllocation { - requested_size: u64, - reserved_size: u64, - align: u64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct FreeRegion { - start: u64, - len: u64, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AllocSource { - Fresh { end: u64 }, - Free { index: usize, region: FreeRegion, alloc_start: u64, alloc_end: u64 }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct AllocationPlan { - ptr: u64, - requested_size: u64, - reserved_size: u64, - align: u64, - source: AllocSource, -} - -impl AllocationPlan { - fn required_end(self) -> u64 { - match self.source { - AllocSource::Fresh { end } => end, - AllocSource::Free { alloc_end, .. } => alloc_end, - } - } -} - -#[derive(Default)] -struct GuestAllocator { - frontier: Option, - active: BTreeMap, - free_regions: Vec, -} - -impl GuestAllocator { - fn plan_alloc( - &self, - current_limit: u64, - size: u64, - align: u64, - ) -> anyhow::Result { - if align == 0 || !align.is_power_of_two() { - anyhow::bail!("runtime alloc requires a positive power-of-two alignment"); - } - - let reserved_size = size.max(1); - for (index, region) in self.free_regions.iter().copied().enumerate() { - let ptr = align_to(region.start, align); - let end = ptr - .checked_add(reserved_size) - .ok_or_else(|| anyhow!("runtime alloc overflowed the address space"))?; - let region_end = region - .start - .checked_add(region.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - if end <= region_end { - return Ok(AllocationPlan { - ptr, - requested_size: size, - reserved_size, - align, - source: AllocSource::Free { index, region, alloc_start: ptr, alloc_end: end }, - }); - } - } - - let frontier = self.frontier.unwrap_or(current_limit); - let ptr = align_to(frontier, align); - let end = - ptr.checked_add(reserved_size).ok_or_else(|| anyhow!("runtime alloc overflowed"))?; - Ok(AllocationPlan { - ptr, - requested_size: size, - reserved_size, - align, - source: AllocSource::Fresh { end }, - }) - } - - fn commit_alloc(&mut self, plan: AllocationPlan) -> anyhow::Result { - match plan.source { - AllocSource::Fresh { end } => { - self.frontier = Some(end); - } - AllocSource::Free { index, region, alloc_start, alloc_end } => { - let existing = self.free_regions.get(index).copied().ok_or_else(|| { - anyhow!("runtime allocator plan referenced a missing free slot") - })?; - if existing != region { - anyhow::bail!("runtime allocator plan was invalidated before commit"); - } - - self.free_regions.remove(index); - if alloc_start > region.start { - self.free_regions.insert( - index, - FreeRegion { start: region.start, len: alloc_start - region.start }, - ); - } - if alloc_end < region.start + region.len { - let suffix = - FreeRegion { start: alloc_end, len: region.start + region.len - alloc_end }; - let insert_at = if alloc_start > region.start { index + 1 } else { index }; - self.free_regions.insert(insert_at, suffix); - } - } - } - - if self - .active - .insert( - plan.ptr, - ActiveAllocation { - requested_size: plan.requested_size, - reserved_size: plan.reserved_size, - align: plan.align, - }, - ) - .is_some() - { - anyhow::bail!("runtime allocator produced a duplicate live pointer"); - } - - i32::try_from(plan.ptr) - .map_err(|_error| anyhow!("runtime alloc exceeded i32 address space")) - } - - fn dealloc(&mut self, ptr: i32, size: i32, align: i32) -> anyhow::Result<()> { - let ptr = u64::try_from(ptr) - .map_err(|_error| anyhow!("runtime dealloc requires a non-negative pointer"))?; - let requested_size = u64::try_from(size) - .map_err(|_error| anyhow!("runtime dealloc requires a non-negative size"))?; - let align = u64::try_from(align).map_err(|_error| { - anyhow!("runtime dealloc requires a positive power-of-two alignment") - })?; - if align == 0 || !align.is_power_of_two() { - anyhow::bail!("runtime dealloc requires a positive power-of-two alignment"); - } - - let allocation = self - .active - .get(&ptr) - .copied() - .ok_or_else(|| anyhow!("runtime dealloc received an unknown pointer"))?; - if allocation.requested_size != requested_size || allocation.align != align { - anyhow::bail!("runtime dealloc did not match the original allocation"); - } - self.active.remove(&ptr); - self.insert_free_region(ptr, allocation.reserved_size) - } - - fn insert_free_region(&mut self, start: u64, len: u64) -> anyhow::Result<()> { - if len == 0 { - return Ok(()); - } - - let mut index = self.free_regions.partition_point(|region| region.start < start); - let mut merged_start = start; - let mut merged_end = - start.checked_add(len).ok_or_else(|| anyhow!("runtime free region overflowed"))?; - - if index > 0 { - let prev = self.free_regions[index - 1]; - let prev_end = prev - .start - .checked_add(prev.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - if prev_end > merged_start { - anyhow::bail!("runtime allocator detected overlapping free regions"); - } - if prev_end == merged_start { - merged_start = prev.start; - self.free_regions.remove(index - 1); - index -= 1; - } - } - - while index < self.free_regions.len() { - let next = self.free_regions[index]; - if next.start < merged_end { - anyhow::bail!("runtime allocator detected overlapping free regions"); - } - if next.start != merged_end { - break; - } - merged_end = next - .start - .checked_add(next.len) - .ok_or_else(|| anyhow!("runtime free-list metadata overflowed"))?; - self.free_regions.remove(index); - } - - self.free_regions - .insert(index, FreeRegion { start: merged_start, len: merged_end - merged_start }); - Ok(()) - } -} - -/// A raw Wasm function type exposed by a module import or export. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WasmFunctionAbi { - pub params: Vec, - pub results: Vec, -} - -/// The kind of Wasm extern exported from or imported into a module. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum WasmExternAbiKind { - Function(WasmFunctionAbi), - Table, - Memory, - Global, - Tag, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WasmImportAbi { - pub module: String, - pub name: String, - pub kind: WasmExternAbiKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WasmExportAbi { - pub name: String, - pub kind: WasmExternAbiKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct WasmModuleAbi { - pub metadata: Option, - pub imports: Vec, - pub exports: Vec, -} - -/// Builder for instantiating a module with optional typed custom host imports. -#[derive(Default)] -pub struct WasmRuntimeBuilder { - config: RunConfig, - host_handlers: HostHandlers, -} - -/// A reusable Wasm runtime instance that can inspect ABI metadata and invoke -/// typed exports. -/// -/// ```ignore -/// use mitki_abi::{AbiScalar, AbiValue}; -/// use mitki_codegen_wasm::{WasmRuntime, compile_file_to_wasm, describe_module_abi}; -/// use mitki_db::RootDatabase; -/// use mitki_inputs::File; -/// -/// # fn main() -> anyhow::Result<()> { -/// let db = RootDatabase::default(); -/// let file = File::new( -/// &db, -/// "example.mitki".into(), -/// r#" -/// struct Point { -/// x: int, -/// y: int, -/// } -/// -/// import "env" fun host_point(): Point; -/// -/// export fun sum_host_point(): int { -/// val point = host_point(); -/// point.x + point.y -/// } -/// "# -/// .to_owned(), -/// ); -/// -/// let bytes = compile_file_to_wasm(&db, file).expect("expected Wasm compilation to succeed"); -/// let abi = describe_module_abi(&bytes)?; -/// assert!(abi.metadata.is_some()); -/// assert!(abi.imports.iter().any(|import| import.module == "env" && import.name == "host_point")); -/// -/// let mut runtime = WasmRuntime::builder() -/// .host_function("env", "host_point", |_args| { -/// Ok(Some(AbiValue::Canonical { -/// transport_type: mitki_abi::TypeId(1), -/// graph: mitki_abi::CanonicalGraph { -/// root: mitki_abi::ValueRef::NodeRef(mitki_abi::NodeId(0)), -/// nodes: vec![mitki_abi::CanonicalNode::Struct { -/// transport_type: mitki_abi::TypeId(1), -/// fields: vec![ -/// mitki_abi::ValueRef::InlineScalar(AbiScalar::Int { signed: true, bits: 32, value: 20 }), -/// mitki_abi::ValueRef::InlineScalar(AbiScalar::Int { signed: true, bits: 32, value: 22 }), -/// ], -/// }], -/// handles: Vec::new(), -/// }, -/// })) -/// }) -/// .instantiate(&bytes)?; -/// let output = runtime.invoke_export("sum_host_point", &[])?; -/// assert_eq!( -/// output.result, -/// Some(AbiValue::Immediate(AbiScalar::Int { signed: true, bits: 32, value: 42 })) -/// ); -/// # Ok(()) -/// # } -/// ``` -pub struct WasmRuntime { - store: wasmtime::Store, - instance: wasmtime::Instance, - memory: Option, - abi: WasmModuleAbi, - pub(crate) abi_v2: crate::runtime_v2::MitkiModuleAbiV2, -} - -impl WasmRuntimeBuilder { - /// Overrides the builtin runtime configuration used during instantiation. - pub fn config(mut self, config: RunConfig) -> Self { - self.config = config; - self - } - - /// Registers a typed host handler for a Mitki `import "module" fun - /// name(...)`. - pub fn host_function( - mut self, - module: impl Into, - name: impl Into, - handler: F, - ) -> Self - where - F: FnMut(&[AbiValue]) -> anyhow::Result> + Send + 'static, - { - insert_host_handler(&mut self.host_handlers, module.into(), name.into(), Box::new(handler)); - self - } - - /// Registers a typed host handler for one concrete ABI v2 import instance. - pub fn host_function_instance(mut self, instance_id: InstanceId, handler: F) -> Self - where - F: FnMut(&[AbiValue]) -> anyhow::Result> + Send + 'static, - { - insert_host_instance_handler(&mut self.host_handlers, instance_id, Box::new(handler)); - self - } - - /// Instantiates a Wasm runtime using the configured builtin and typed host - /// imports. - pub fn instantiate(self, bytes: &[u8]) -> anyhow::Result { - WasmRuntime::instantiate(bytes, self.config, self.host_handlers) - } -} - -impl WasmRuntime { - /// Creates a runtime builder for modules that need typed custom host - /// imports. - pub fn builder() -> WasmRuntimeBuilder { - WasmRuntimeBuilder::default() - } - - /// Instantiates a module using only builtin `mitki` imports and optional - /// WASI support. - pub fn new(bytes: &[u8], config: RunConfig) -> anyhow::Result { - Self::instantiate(bytes, config, HostHandlers::default()) - } - - fn instantiate( - bytes: &[u8], - config: RunConfig, - host_handlers: HostHandlers, - ) -> anyhow::Result { - let abi = describe_module_abi(bytes)?; - ensure_supported_imports(&abi, config, &host_handlers)?; - let engine = wasmtime::Engine::default(); - let module = wasmtime::Module::from_binary(&engine, bytes)?; - let mut linker = Linker::new(&engine); - if config.enable_wasi { - p1::add_to_linker_sync(&mut linker, |state: &mut RuntimeStore| &mut state.wasi)?; - } - if config.enable_mitki_runtime { - add_runtime_imports(&mut linker)?; - } - add_builtin_typed_import_aliases(&mut linker, &module, abi.metadata.as_ref(), config)?; - add_typed_host_imports(&mut linker, &module, abi.metadata.as_ref(), &host_handlers)?; - - let mut store = wasmtime::Store::new( - &engine, - RuntimeStore { - wasi: build_wasi_context(config)?, - stdout: String::new(), - allocator: GuestAllocator::default(), - host_handlers, - }, - ); - let instance = linker.instantiate(&mut store, &module)?; - let memory = instance.get_memory(&mut store, "memory"); - let abi_v2 = crate::runtime_v2::MitkiModuleAbiV2 { metadata: abi.metadata.clone() }; - Ok(Self { store, instance, memory, abi, abi_v2 }) - } - - /// Returns the parsed Wasm import/export surface for this runtime's module. - pub fn abi(&self) -> &WasmModuleAbi { - &self.abi - } - - pub fn invoke_export( - &mut self, - export: &str, - args: &[AbiValue], - ) -> anyhow::Result { - let output = self.invoke_export_v2(export, args)?; - Ok(WasmRunOutput { stdout: output.stdout, result: output.result }) - } - - /// Invokes one concrete ABI v2 export instance by its metadata instance id. - pub fn invoke_export_instance( - &mut self, - instance_id: InstanceId, - args: &[AbiValue], - ) -> anyhow::Result { - let output = self.invoke_export_instance_v2(instance_id, args)?; - Ok(WasmRunOutput { stdout: output.stdout, result: output.result }) - } - - /// Invokes a raw exported function with no parameters and no typed result. - pub fn invoke_raw_export(&mut self, export: &str) -> anyhow::Result { - self.clear_stdout(); - self.call_dynamic_export(export, &[], &mut [])?; - Ok(WasmRunOutput { stdout: self.take_stdout(), result: None }) - } - - pub(crate) fn clear_stdout(&mut self) { - self.store.data_mut().stdout.clear(); - } - - pub(crate) fn take_stdout(&mut self) -> String { - std::mem::take(&mut self.store.data_mut().stdout) - } - - pub(crate) fn exported_memory(&self) -> anyhow::Result { - self.memory - .ok_or_else(|| anyhow!("typed invocation requires an exported memory named `memory`")) - } - - pub(crate) fn call_dynamic_export( - &mut self, - export: &str, - params: &[wasmtime::Val], - results: &mut [wasmtime::Val], - ) -> anyhow::Result<()> { - let func = self - .instance - .get_func(&mut self.store, export) - .ok_or_else(|| anyhow!("missing export `{export}`"))?; - func.call(&mut self.store, params, results)?; - Ok(()) - } - - pub(crate) fn call_abi_v2_alloc(&mut self, size: i32, align: i32) -> anyhow::Result { - let alloc = self - .instance - .get_typed_func::<(i32, i32), i32>(&mut self.store, "mitki:abi/2/alloc")?; - Ok(alloc.call(&mut self.store, (size, align))?) - } - - pub(crate) fn call_abi_v2_blob_release(&mut self, ptr: i32) -> anyhow::Result<()> { - let release = - self.instance.get_typed_func::(&mut self.store, "mitki:abi/2/blob_release")?; - Ok(release.call(&mut self.store, ptr)?) - } - - pub(crate) fn call_abi_v2_handle_retain(&mut self, handle: i32) -> anyhow::Result { - let retain = self - .instance - .get_typed_func::(&mut self.store, "mitki:abi/2/handle_retain")?; - Ok(retain.call(&mut self.store, handle)?) - } - - pub(crate) fn call_abi_v2_handle_release(&mut self, handle: i32) -> anyhow::Result<()> { - let release = self - .instance - .get_typed_func::(&mut self.store, "mitki:abi/2/handle_release")?; - Ok(release.call(&mut self.store, handle)?) - } - - pub(crate) fn retain_abi_value_handles( - &mut self, - value: &AbiValue, - ) -> anyhow::Result> { - let mut retained = Vec::new(); - for handle_id in abi_value_handle_ids(value) { - self.call_abi_v2_handle_retain(handle_to_i32(handle_id)?)?; - retained.push(handle_id); - } - Ok(retained) - } - - pub(crate) fn release_handle_ids(&mut self, handles: &[u32]) -> anyhow::Result<()> { - for &handle_id in handles.iter().rev() { - self.call_abi_v2_handle_release(handle_to_i32(handle_id)?)?; - } - Ok(()) - } - - pub(crate) fn write_memory(&mut self, offset: usize, bytes: &[u8]) -> anyhow::Result<()> { - let memory = self.exported_memory()?; - memory.write(&mut self.store, offset, bytes)?; - Ok(()) - } - - pub(crate) fn read_canonical_blob(&mut self, ptr: i32) -> anyhow::Result> { - let memory = self.exported_memory()?; - let start = usize::try_from(ptr) - .map_err(|_error| anyhow!("canonical blob pointer was negative"))?; - let bytes = memory.data(&self.store); - let header = bytes - .get(start..start + 26) - .ok_or_else(|| anyhow!("canonical blob header was truncated in guest memory"))?; - let total = u32::from_le_bytes(header[22..26].try_into().expect("total len bytes")); - let end = start - .checked_add(total as usize) - .ok_or_else(|| anyhow!("canonical blob length overflowed"))?; - let blob = bytes - .get(start..end) - .ok_or_else(|| anyhow!("canonical blob was truncated in guest memory"))?; - Ok(blob.to_vec()) - } -} - -pub fn invoke_export( - bytes: &[u8], - export: &str, - args: &[AbiValue], -) -> anyhow::Result { - invoke_export_with_config(bytes, export, args, RunConfig::default()) -} - -pub fn invoke_export_instance( - bytes: &[u8], - instance_id: InstanceId, - args: &[AbiValue], -) -> anyhow::Result { - invoke_export_instance_with_config(bytes, instance_id, args, RunConfig::default()) -} - -pub fn invoke_export_with_config( - bytes: &[u8], - export: &str, - args: &[AbiValue], - config: RunConfig, -) -> anyhow::Result { - let mut runtime = WasmRuntime::new(bytes, config)?; - runtime.invoke_export(export, args) -} - -pub fn invoke_raw_export_with_config( - bytes: &[u8], - export: &str, - config: RunConfig, -) -> anyhow::Result { - let mut runtime = WasmRuntime::new(bytes, config)?; - runtime.invoke_raw_export(export) -} - -pub fn invoke_export_instance_with_config( - bytes: &[u8], - instance_id: InstanceId, - args: &[AbiValue], - config: RunConfig, -) -> anyhow::Result { - let mut runtime = WasmRuntime::new(bytes, config)?; - runtime.invoke_export_instance(instance_id, args) -} - -pub fn collect_unsupported_imports(bytes: &[u8]) -> anyhow::Result> { - collect_unsupported_imports_with_config(bytes, RunConfig::default()) -} - -pub fn collect_unsupported_imports_with_config( - bytes: &[u8], - config: RunConfig, -) -> anyhow::Result> { - let mut unsupported = Vec::new(); - let metadata = crate::runtime_v2::describe_module_abi_v2(bytes)?.metadata; - - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - let wasmparser::Payload::ImportSection(reader) = payload else { - continue; - }; - - for import in reader.into_imports() { - let import = import?; - let runtime_import = - config.enable_mitki_runtime && runtime_builtin_matches(import.module, import.name); - let wasi_import = config.enable_wasi && import.module == "wasi_snapshot_preview1"; - if !runtime_import && !wasi_import { - if let Some(graph) = metadata.as_ref() - && let Some(instance) = - find_import_instance_by_linkage(graph, import.module, import.name) - && let Ok(symbol) = graph_symbol_name(graph, instance.logical_symbol) - { - unsupported.push(format!("{}::{}", import.module, symbol)); - } else { - unsupported.push(format!("{}::{}", import.module, import.name)); - } - } - } - } - - Ok(unsupported) -} - -/// Describes the raw Wasm import/export surface together with decoded ABI v2 -/// metadata, when present. -pub fn describe_module_abi(bytes: &[u8]) -> anyhow::Result { - let mut function_types = Vec::new(); - let mut function_type_indices = Vec::new(); - let mut imports = Vec::new(); - let mut raw_exports = Vec::new(); - let metadata = crate::runtime_v2::describe_module_abi_v2(bytes)?.metadata; - - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - match payload { - wasmparser::Payload::TypeSection(reader) => { - for ty in reader.into_iter_err_on_gc_types() { - let ty = ty?; - function_types.push(WasmFunctionAbi { - params: ty.params().iter().map(|ty| ty.to_string()).collect(), - results: ty.results().iter().map(|ty| ty.to_string()).collect(), - }); - } - } - wasmparser::Payload::ImportSection(reader) => { - for import in reader.into_imports() { - let import = import?; - let kind = match import.ty { - wasmparser::TypeRef::Func(type_index) => { - function_type_indices.push(type_index); - WasmExternAbiKind::Function( - function_types.get(type_index as usize).cloned().ok_or_else( - || anyhow!("missing function type `{type_index}`"), - )?, - ) - } - wasmparser::TypeRef::Table(_) => WasmExternAbiKind::Table, - wasmparser::TypeRef::Memory(_) => WasmExternAbiKind::Memory, - wasmparser::TypeRef::Global(_) => WasmExternAbiKind::Global, - wasmparser::TypeRef::Tag(_) => WasmExternAbiKind::Tag, - wasmparser::TypeRef::FuncExact(type_index) => WasmExternAbiKind::Function( - function_types.get(type_index as usize).cloned().ok_or_else(|| { - anyhow!("missing exact function type `{type_index}`") - })?, - ), - }; - imports.push(WasmImportAbi { - module: import.module.to_owned(), - name: import.name.to_owned(), - kind, - }); - } - } - wasmparser::Payload::FunctionSection(reader) => { - for function in reader { - function_type_indices.push(function?); - } - } - wasmparser::Payload::ExportSection(reader) => { - for export in reader { - raw_exports.push(export?); - } - } - _ => {} - } - } - - let exports = raw_exports - .into_iter() - .map(|export| { - let kind = match export.kind { - wasmparser::ExternalKind::Func | wasmparser::ExternalKind::FuncExact => { - let type_index = *function_type_indices - .get(export.index as usize) - .ok_or_else(|| anyhow!("missing function index `{}`", export.index))?; - WasmExternAbiKind::Function( - function_types - .get(type_index as usize) - .cloned() - .ok_or_else(|| anyhow!("missing function type `{type_index}`"))?, - ) - } - wasmparser::ExternalKind::Table => WasmExternAbiKind::Table, - wasmparser::ExternalKind::Memory => WasmExternAbiKind::Memory, - wasmparser::ExternalKind::Global => WasmExternAbiKind::Global, - wasmparser::ExternalKind::Tag => WasmExternAbiKind::Tag, - }; - let name = export.name.to_owned(); - Ok(WasmExportAbi { name, kind }) - }) - .collect::>>()?; - - Ok(WasmModuleAbi { metadata, imports, exports }) -} - -fn builtin_import_supported(module: &str, name: &str, config: RunConfig) -> bool { - let runtime_import = config.enable_mitki_runtime && runtime_builtin_matches(module, name); - let wasi_import = config.enable_wasi && module == "wasi_snapshot_preview1"; - runtime_import || wasi_import -} - -fn import_display_name(metadata: Option<&SemanticTypeGraph>, module: &str, name: &str) -> String { - if let Some(graph) = metadata - && let Some(instance) = find_import_instance_by_linkage(graph, module, name) - && let Ok(symbol) = graph_symbol_name(graph, instance.logical_symbol) - { - return format!("{module}::{symbol}"); - } - format!("{module}::{name}") -} - -fn import_instance_named_matches( - graph: &SemanticTypeGraph, - instance: &mitki_abi::FunctionInstance, - module: &str, - name: &str, -) -> bool { - instance.linkage == LinkageKind::WasmImport - && instance - .wasm_module_name - .and_then(|id| graph_string_value(graph, id).ok()) - .is_some_and(|candidate| candidate == module) - && graph_symbol_name(graph, instance.logical_symbol).is_ok_and(|symbol| symbol == name) -} - -fn resolve_host_handler_key( - graph: &SemanticTypeGraph, - instance: &mitki_abi::FunctionInstance, - host_handlers: &HostHandlers, -) -> anyhow::Result> { - if host_handlers.has_instance(instance.id) { - return Ok(Some(HostHandlerKey::Instance(instance.id.0))); - } - - let Some(module_id) = instance.wasm_module_name else { - return Ok(None); - }; - let module = graph_string_value(graph, module_id)?; - let logical_name = graph_symbol_name(graph, instance.logical_symbol)?; - if !host_handlers.has_named(module, logical_name) { - return Ok(None); - } - - let mut matches = graph - .function_instances - .iter() - .filter(|candidate| import_instance_named_matches(graph, candidate, module, logical_name)); - let Some(_first) = matches.next() else { - return Ok(None); - }; - if matches.next().is_some() { - bail!( - "typed host handler `{module}::{logical_name}` is ambiguous; register a concrete \ - instance handler instead" - ); - } - Ok(Some(HostHandlerKey::Named { module: module.to_owned(), name: logical_name.to_owned() })) -} - -fn ensure_supported_imports( - abi: &WasmModuleAbi, - config: RunConfig, - host_handlers: &HostHandlers, -) -> anyhow::Result<()> { - let unsupported_imports = abi - .imports - .iter() - .filter(|import| !supports_import(import, abi.metadata.as_ref(), config, host_handlers)) - .map(|import| import_display_name(abi.metadata.as_ref(), &import.module, &import.name)) - .collect::>(); - if unsupported_imports.is_empty() { - return Ok(()); - } - - let imports = unsupported_imports.join(", "); - if host_handlers.is_empty() { - anyhow::bail!( - "run-wasm supports only WASI and `mitki` runtime imports; unresolved imports: \ - {imports}" - ); - } - anyhow::bail!( - "runtime supports only configured typed host imports, WASI, and `mitki` runtime imports; \ - unresolved imports: {imports}" - ); -} - -fn insert_host_handler( - handlers: &mut HostHandlers, - module: String, - name: String, - handler: HostImportHandler, -) { - handlers.insert_named(module, name, handler); -} - -fn insert_host_instance_handler( - handlers: &mut HostHandlers, - instance_id: InstanceId, - handler: HostImportHandler, -) { - handlers.insert_instance(instance_id, handler); -} - -fn supports_import( - import: &WasmImportAbi, - metadata: Option<&SemanticTypeGraph>, - config: RunConfig, - host_handlers: &HostHandlers, -) -> bool { - let builtin_import = builtin_import_supported(&import.module, &import.name, config) - || metadata - .and_then(|graph| find_import_instance_by_linkage(graph, &import.module, &import.name)) - .is_some_and(|instance| { - graph_symbol_name(metadata.expect("graph should exist"), instance.logical_symbol) - .is_ok_and(|name| builtin_import_supported(&import.module, name, config)) - }); - let host_import = metadata - .and_then(|graph| find_import_instance_by_linkage(graph, &import.module, &import.name)) - .is_some_and(|instance| { - metadata - .and_then(|graph| resolve_host_handler_key(graph, instance, host_handlers).ok()) - .flatten() - .is_some() - }); - builtin_import || host_import -} - -fn add_builtin_typed_import_aliases( - linker: &mut Linker, - module: &wasmtime::Module, - metadata: Option<&SemanticTypeGraph>, - config: RunConfig, -) -> anyhow::Result<()> { - let Some(graph) = metadata else { - return Ok(()); - }; - - for import in module.imports() { - let Some(instance) = find_import_instance_by_linkage(graph, import.module(), import.name()) - else { - continue; - }; - let logical_name = graph_symbol_name(graph, instance.logical_symbol)?; - if import.name() == logical_name - || !builtin_import_supported(import.module(), logical_name, config) - { - continue; - } - linker.alias(import.module(), logical_name, import.module(), import.name())?; - } - - Ok(()) -} - -fn add_typed_host_imports( - linker: &mut Linker, - module: &wasmtime::Module, - metadata: Option<&SemanticTypeGraph>, - host_handlers: &HostHandlers, -) -> anyhow::Result<()> { - let Some(graph) = metadata else { - return Ok(()); - }; - - for import in module.imports() { - let Some(instance) = find_import_instance_by_linkage(graph, import.module(), import.name()) - else { - continue; - }; - let Some(handler_key) = resolve_host_handler_key(graph, instance, host_handlers)? else { - continue; - }; - - let extern_ty = import.ty(); - let wasmtime::ExternType::Func(func_ty) = extern_ty else { - anyhow::bail!( - "typed host handlers require function imports, found `{}::{}`", - import.module(), - import.name() - ); - }; - let function_signature = graph - .signatures - .get(instance.signature.0 as usize) - .cloned() - .ok_or_else(|| anyhow!("missing ABI v2 signature `{}`", instance.signature.0))?; - - register_typed_host_import( - linker, - import.module(), - import.name(), - func_ty, - handler_key, - graph.clone(), - function_signature, - )?; - } - - Ok(()) -} - -fn register_typed_host_import( - linker: &mut Linker, - module: &str, - name: &str, - ty: wasmtime::FuncType, - handler_key: HostHandlerKey, - graph: SemanticTypeGraph, - function_signature: AbiFunctionSignature, -) -> anyhow::Result<()> { - linker.func_new( - module, - name, - ty, - move |mut caller, params, results| -> wasmtime::Result<()> { - invoke_typed_host_import( - &mut caller, - &handler_key, - &graph, - &function_signature, - params, - results, - ) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - Ok(()) -} - -fn build_wasi_context(config: RunConfig) -> anyhow::Result { - if config.enable_wasi { - let mut builder = WasiCtxBuilder::new(); - builder.inherit_stdio().inherit_args().inherit_env(); - let cwd = std::env::current_dir().context("failed to resolve current working directory")?; - builder.preopened_dir(cwd, ".", DirPerms::all(), FilePerms::all()).map_err(|error| { - anyhow!("failed to preopen the current working directory for WASI: {error}") - })?; - Ok(builder.build_p1()) - } else { - Ok(WasiCtxBuilder::new().build_p1()) - } -} - -fn invoke_typed_host_import( - caller: &mut Caller<'_, RuntimeStore>, - handler_key: &HostHandlerKey, - graph: &SemanticTypeGraph, - function_signature: &AbiFunctionSignature, - params: &[wasmtime::Val], - results: &mut [wasmtime::Val], -) -> anyhow::Result<()> { - let memory = guest_memory(caller)?; - let bytes = memory.data(&*caller).to_vec(); - let mut value_index = 0usize; - let abi_args = function_signature - .params - .iter() - .map(|transport| match transport.transport_class { - TransportClass::Immediate => { - let needs_lane = transport_has_wasm_lane(graph, transport)?; - let raw = needs_lane - .then(|| { - params - .get(value_index) - .ok_or_else(|| anyhow!("missing immediate typed host import argument")) - }) - .transpose()?; - if needs_lane { - value_index += 1; - Ok(AbiValue::Immediate(crate::runtime_v2::read_immediate_result( - graph, - transport_carrier_type(transport), - std::slice::from_ref(raw.expect("immediate raw value")), - )?)) - } else { - Ok(AbiValue::Immediate(crate::runtime_v2::read_immediate_result( - graph, - transport_carrier_type(transport), - &[], - )?)) - } - } - TransportClass::CanonicalValue => { - let ptr = params - .get(value_index) - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("missing canonical typed host import argument"))?; - value_index += 1; - let blob = read_canonical_blob_from_bytes(&bytes, ptr)?; - decode_canonical_blob(&blob) - } - TransportClass::CapabilityHandle => { - let ptr = params - .get(value_index) - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("missing capability typed host import argument"))?; - value_index += 1; - Ok(AbiValue::Handle { - type_id: transport_carrier_type(transport), - handle_id: i32_to_handle(ptr)?, - }) - } - }) - .collect::>>()?; - if value_index != params.len() { - anyhow::bail!( - "expected {value_index} raw argument value(s) for typed host import, got {}", - params.len() - ); - } - let result = { - let handler = caller - .data_mut() - .host_handlers - .get_mut(handler_key) - .ok_or_else(|| anyhow!("missing host handler for typed import"))?; - handler(&abi_args) - }; - let result = result?; - match function_signature.result.transport_class { - TransportClass::Immediate => { - let unit = AbiValue::Immediate(mitki_abi::AbiScalar::Unit); - let value = result.as_ref().unwrap_or(&unit); - if let Some(raw) = crate::runtime_v2::abi_immediate_to_val( - graph, - transport_carrier_type(&function_signature.result), - value, - )? { - let slot = results.first_mut().ok_or_else(|| { - anyhow!("missing immediate result slot for typed host import") - })?; - *slot = raw; - } - Ok(()) - } - TransportClass::CanonicalValue => { - let value = result - .as_ref() - .ok_or_else(|| anyhow!("typed host import expected a canonical result"))?; - let retained_handles = retain_guest_abi_value_handles(caller, value)?; - let blob = match encode_canonical_blob(value) { - Ok(blob) => blob, - Err(error) => { - release_guest_handle_ids(caller, &retained_handles)?; - return Err(error); - } - }; - let ptr = match alloc_store_region( - caller, - &memory, - u32::try_from(blob.len()) - .map_err(|_error| anyhow!("canonical host import result blob was too large"))?, - 4, - ) { - Ok(ptr) => ptr, - Err(error) => { - release_guest_handle_ids(caller, &retained_handles)?; - return Err(error); - } - }; - let offset = usize::try_from(ptr) - .map_err(|_error| anyhow!("canonical host import result pointer was negative"))?; - if let Err(error) = memory.write(&mut *caller, offset, &blob) { - release_guest_handle_ids(caller, &retained_handles)?; - return Err(error.into()); - } - let slot = results - .first_mut() - .ok_or_else(|| anyhow!("missing pointer result slot for typed host import"))?; - *slot = wasmtime::Val::I32(ptr); - Ok(()) - } - TransportClass::CapabilityHandle => { - let value = result - .as_ref() - .ok_or_else(|| anyhow!("typed host import expected a handle result"))?; - let AbiValue::Handle { type_id, handle_id } = value else { - bail!("typed host import expected a handle result"); - }; - let carrier = transport_carrier_type(&function_signature.result); - if *type_id != carrier { - bail!( - "typed host import returned handle type `{}` but signature expected `{}`", - type_id.0, - carrier.0 - ); - } - let slot = results - .first_mut() - .ok_or_else(|| anyhow!("missing handle result slot for typed host import"))?; - *slot = wasmtime::Val::I32(handle_to_i32(*handle_id)?); - Ok(()) - } - } -} - -fn read_canonical_blob_from_bytes(bytes: &[u8], ptr: i32) -> anyhow::Result> { - let start = - usize::try_from(ptr).map_err(|_error| anyhow!("canonical blob pointer was negative"))?; - let header = bytes - .get(start..start + 26) - .ok_or_else(|| anyhow!("canonical blob header was truncated in guest memory"))?; - let total = u32::from_le_bytes(header[22..26].try_into().expect("total len bytes")); - let end = start - .checked_add(total as usize) - .ok_or_else(|| anyhow!("canonical blob length overflowed"))?; - let blob = bytes - .get(start..end) - .ok_or_else(|| anyhow!("canonical blob was truncated in guest memory"))?; - Ok(blob.to_vec()) -} - -#[derive(Clone, Copy)] -enum RuntimeBuiltinBinding { - PrintI32, - PrintStr, - Alloc, - Dealloc, - CopyNonOverlapping, -} - -#[derive(Clone, Copy)] -struct RuntimeBuiltinDescriptor { - import_module: &'static str, - import_name: &'static str, - binding: RuntimeBuiltinBinding, -} - -const RUNTIME_BUILTINS: [RuntimeBuiltinDescriptor; 7] = [ - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "print_i32", - binding: RuntimeBuiltinBinding::PrintI32, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "_print_i32", - binding: RuntimeBuiltinBinding::PrintI32, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "print_str", - binding: RuntimeBuiltinBinding::PrintStr, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "_print_str", - binding: RuntimeBuiltinBinding::PrintStr, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "alloc", - binding: RuntimeBuiltinBinding::Alloc, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "dealloc", - binding: RuntimeBuiltinBinding::Dealloc, - }, - RuntimeBuiltinDescriptor { - import_module: "mitki", - import_name: "_copy_nonoverlapping", - binding: RuntimeBuiltinBinding::CopyNonOverlapping, - }, -]; - -fn runtime_builtin_matches(module: &str, name: &str) -> bool { - RUNTIME_BUILTINS - .iter() - .any(|builtin| builtin.import_module == module && builtin.import_name == name) -} - -fn add_runtime_imports(linker: &mut Linker) -> anyhow::Result<()> { - for descriptor in RUNTIME_BUILTINS { - register_runtime_builtin(linker, descriptor)?; - } - Ok(()) -} - -fn register_runtime_builtin( - linker: &mut Linker, - descriptor: RuntimeBuiltinDescriptor, -) -> anyhow::Result<()> { - match descriptor.binding { - RuntimeBuiltinBinding::PrintI32 => { - linker.func_wrap( - descriptor.import_module, - descriptor.import_name, - |mut caller: Caller<'_, RuntimeStore>, value: i32| { - caller.data_mut().stdout.push_str(&value.to_string()); - }, - )?; - } - RuntimeBuiltinBinding::PrintStr => { - linker.func_wrap( - descriptor.import_module, - descriptor.import_name, - |mut caller: Caller<'_, RuntimeStore>, ptr: i32| -> wasmtime::Result<()> { - let string = read_guest_string(&mut caller, ptr) - .map_err(|error| wasmtime::Error::msg(error.to_string()))?; - caller.data_mut().stdout.push_str(&string); - Ok(()) - }, - )?; - } - RuntimeBuiltinBinding::Alloc => register_alloc_runtime_builtin( - linker, - descriptor.import_module, - descriptor.import_name, - )?, - RuntimeBuiltinBinding::Dealloc => register_dealloc_runtime_builtin( - linker, - descriptor.import_module, - descriptor.import_name, - )?, - RuntimeBuiltinBinding::CopyNonOverlapping => register_copy_non_overlapping_runtime_builtin( - linker, - descriptor.import_module, - descriptor.import_name, - )?, - } - Ok(()) -} - -fn register_alloc_runtime_builtin( - linker: &mut Linker, - module: &str, - name: &str, -) -> anyhow::Result<()> { - linker.func_wrap( - module, - name, - |mut caller: Caller<'_, T>, size: i32, align: i32| -> wasmtime::Result { - let size = u64::try_from(size).map_err(|_error| { - wasmtime::Error::msg("runtime alloc requires a non-negative size") - })?; - let align = u64::try_from(align).map_err(|_error| { - wasmtime::Error::msg("runtime alloc requires a positive power-of-two alignment") - })?; - alloc_caller_region(&mut caller, size, align) - .map_err(|error| wasmtime::Error::msg(error.to_string())) - }, - )?; - Ok(()) -} - -fn register_dealloc_runtime_builtin( - linker: &mut Linker, - module: &str, - name: &str, -) -> anyhow::Result<()> { - linker.func_wrap( - module, - name, - |mut caller: Caller<'_, T>, ptr: i32, size: i32, align: i32| -> wasmtime::Result<()> { - caller.data_mut().allocator_mut().dealloc(ptr, size, align).map_err(|error| { - wasmtime::Error::msg(format!( - "runtime dealloc failed for ptr={ptr} size={size} align={align}: {error}" - )) - }) - }, - )?; - Ok(()) -} - -fn register_copy_non_overlapping_runtime_builtin( - linker: &mut Linker, - module: &str, - name: &str, -) -> anyhow::Result<()> { - linker.func_wrap( - module, - name, - |mut caller: Caller<'_, T>, dst: i32, src: i32, count: i32| -> wasmtime::Result<()> { - let memory = guest_memory(&mut caller).map_err(|error| { - wasmtime::Error::msg(format!( - "runtime copy_nonoverlapping could not access guest memory: {error}" - )) - })?; - let dst = usize::try_from(dst).map_err(|_error| { - wasmtime::Error::msg("runtime copy_nonoverlapping requires a non-negative dst") - })?; - let src = usize::try_from(src).map_err(|_error| { - wasmtime::Error::msg("runtime copy_nonoverlapping requires a non-negative src") - })?; - let count = usize::try_from(count).map_err(|_error| { - wasmtime::Error::msg("runtime copy_nonoverlapping requires a non-negative count") - })?; - let mut bytes = vec![0; count]; - memory.read(&mut caller, src, &mut bytes).map_err(|error| { - wasmtime::Error::msg(format!( - "runtime copy_nonoverlapping could not read guest memory: {error}" - )) - })?; - memory.write(&mut caller, dst, &bytes).map_err(|error| { - wasmtime::Error::msg(format!( - "runtime copy_nonoverlapping could not write guest memory: {error}" - )) - })?; - Ok(()) - }, - )?; - Ok(()) -} - -fn abi_value_handle_ids(value: &AbiValue) -> Vec { - let mut handles = std::collections::BTreeSet::new(); - match value { - AbiValue::Immediate(_) => {} - AbiValue::Handle { handle_id, .. } => { - handles.insert(*handle_id); - } - AbiValue::Canonical { graph, .. } => { - handles.extend(graph.handles.iter().map(|slot| slot.handle_id)); - } - } - handles.into_iter().collect() -} - -fn handle_to_i32(handle_id: u32) -> anyhow::Result { - i32::try_from(handle_id).map_err(|_error| anyhow!("handle id exceeded i32 range")) -} - -fn i32_to_handle(raw: i32) -> anyhow::Result { - u32::try_from(raw).map_err(|_error| anyhow!("handle id was negative")) -} - -fn retain_guest_abi_value_handles( - caller: &mut Caller<'_, RuntimeStore>, - value: &AbiValue, -) -> anyhow::Result> { - let mut retained = Vec::new(); - for handle_id in abi_value_handle_ids(value) { - caller_handle_retain(caller, handle_to_i32(handle_id)?)?; - retained.push(handle_id); - } - Ok(retained) -} - -fn release_guest_handle_ids( - caller: &mut Caller<'_, RuntimeStore>, - handles: &[u32], -) -> anyhow::Result<()> { - for &handle_id in handles.iter().rev() { - caller_handle_release(caller, handle_to_i32(handle_id)?)?; - } - Ok(()) -} - -fn caller_handle_retain(caller: &mut Caller<'_, RuntimeStore>, handle: i32) -> anyhow::Result { - let func = caller - .get_export("mitki:abi/2/handle_retain") - .and_then(Extern::into_func) - .ok_or_else(|| anyhow!("missing export `mitki:abi/2/handle_retain`"))?; - let typed = func.typed::(&mut *caller)?; - Ok(typed.call(&mut *caller, handle)?) -} - -fn caller_handle_release(caller: &mut Caller<'_, RuntimeStore>, handle: i32) -> anyhow::Result<()> { - let func = caller - .get_export("mitki:abi/2/handle_release") - .and_then(Extern::into_func) - .ok_or_else(|| anyhow!("missing export `mitki:abi/2/handle_release`"))?; - let typed = func.typed::(&mut *caller)?; - Ok(typed.call(&mut *caller, handle)?) -} - -fn read_guest_string(caller: &mut Caller<'_, T>, ptr: i32) -> anyhow::Result { - let memory = guest_memory(caller)?; - read_memory_string(&memory, caller, ptr) -} - -fn read_memory_string( - memory: &wasmtime::Memory, - mut store: impl wasmtime::AsContextMut, - ptr: i32, -) -> anyhow::Result { - let ptr = usize::try_from(ptr) - .map_err(|_error| anyhow!("runtime print_str requires a non-negative pointer"))?; - let mut len_bytes = [0; 4]; - memory - .read(&mut store, ptr, &mut len_bytes) - .context("runtime print_str could not read string length")?; - let len = u32::from_le_bytes(len_bytes) as usize; - let mut bytes = vec![0; len]; - memory - .read(&mut store, ptr + 4, &mut bytes) - .context("runtime print_str could not read string bytes")?; - String::from_utf8(bytes).context("runtime print_str encountered invalid UTF-8") -} - -fn guest_memory(caller: &mut Caller<'_, T>) -> anyhow::Result { - caller - .get_export("memory") - .and_then(Extern::into_memory) - .ok_or_else(|| anyhow!("runtime import requires an exported memory named `memory`")) -} - -fn ensure_memory( - caller: &mut Caller<'_, T>, - memory: &wasmtime::Memory, - end: u64, -) -> anyhow::Result<()> { - let current = memory.data_size(&mut *caller) as u64; - if end <= current { - return Ok(()); - } - - let additional = end - current; - let pages = additional.div_ceil(WASM_PAGE_SIZE); - memory - .grow(&mut *caller, pages) - .map(|_previous| ()) - .map_err(|error| anyhow!("runtime alloc failed to grow memory: {error}")) -} - -fn ensure_store_memory>( - store: &mut S, - memory: &wasmtime::Memory, - end: u64, -) -> anyhow::Result<()> { - let mut store = store.as_context_mut(); - let current = memory.data_size(&mut store) as u64; - if end <= current { - return Ok(()); - } - - let additional = end - current; - let pages = additional.div_ceil(WASM_PAGE_SIZE); - memory - .grow(&mut store, pages) - .map(|_previous| ()) - .map_err(|error| anyhow!("runtime alloc failed to grow memory: {error}")) -} - -fn align_to(offset: u64, align: u64) -> u64 { - if align <= 1 { - offset - } else { - let mask = align - 1; - (offset + mask) & !mask - } -} - -fn alloc_caller_region( - caller: &mut Caller<'_, T>, - size: u64, - align: u64, -) -> anyhow::Result { - let memory = guest_memory(caller)?; - let current_limit = memory.data_size(&*caller) as u64; - let plan = caller.data().allocator().plan_alloc(current_limit, size, align)?; - ensure_memory(caller, &memory, plan.required_end())?; - caller.data_mut().allocator_mut().commit_alloc(plan) -} - -fn alloc_store_region>( - store: &mut S, - memory: &wasmtime::Memory, - size: u32, - align: u32, -) -> anyhow::Result { - let size = u64::from(size); - let align = u64::from(align.max(1)); - let mut store_context = store.as_context_mut(); - let current_limit = memory.data_size(&mut store_context) as u64; - let plan = store_context.data().allocator().plan_alloc(current_limit, size, align)?; - ensure_store_memory(&mut store_context, memory, plan.required_end())?; - store_context.data_mut().allocator_mut().commit_alloc(plan) -} - -trait AllocatingStore { - fn allocator(&self) -> &GuestAllocator; - fn allocator_mut(&mut self) -> &mut GuestAllocator; -} - -impl AllocatingStore for RuntimeStore { - fn allocator(&self) -> &GuestAllocator { - &self.allocator - } - - fn allocator_mut(&mut self) -> &mut GuestAllocator { - &mut self.allocator - } -} - -struct RuntimeStore { - wasi: p1::WasiP1Ctx, - stdout: String, - allocator: GuestAllocator, - host_handlers: HostHandlers, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn guest_allocator_splits_and_coalesces_free_regions() { - let mut allocator = GuestAllocator::default(); - let first = allocator - .commit_alloc(allocator.plan_alloc(64, 8, 4).expect("first allocation plan")) - .expect("first allocation"); - let second = allocator - .commit_alloc(allocator.plan_alloc(64, 8, 4).expect("second allocation plan")) - .expect("second allocation"); - assert_eq!(allocator.frontier, Some(80)); - - allocator.dealloc(first, 8, 4).expect("free first region"); - let split_plan = allocator.plan_alloc(64, 4, 4).expect("split allocation plan"); - assert_eq!(split_plan.ptr, u64::try_from(first).expect("non-negative first pointer")); - let split = allocator.commit_alloc(split_plan).expect("split allocation"); - assert_eq!(allocator.frontier, Some(80)); - - allocator.dealloc(split, 4, 4).expect("free split region"); - allocator.dealloc(second, 8, 4).expect("free second region"); - let coalesced = allocator.plan_alloc(64, 16, 4).expect("coalesced allocation plan"); - assert_eq!(coalesced.ptr, u64::try_from(first).expect("non-negative first pointer")); - assert_eq!(allocator.frontier, Some(80)); - } - - #[test] - fn guest_allocator_rejects_invalid_deallocs() { - let mut allocator = GuestAllocator::default(); - let ptr = allocator - .commit_alloc(allocator.plan_alloc(64, 8, 4).expect("allocation plan")) - .expect("allocation"); - - let mismatch = allocator.dealloc(ptr, 4, 4).expect_err("mismatched dealloc should fail"); - assert!(mismatch.to_string().contains("did not match the original allocation")); - - let unknown = - allocator.dealloc(ptr + 16, 8, 4).expect_err("unknown pointer dealloc should fail"); - assert!(unknown.to_string().contains("unknown pointer")); - - allocator.dealloc(ptr, 8, 4).expect("original allocation can still be freed"); - } -} diff --git a/crates/mitki-wasm-runtime/src/runtime_v2.rs b/crates/mitki-wasm-runtime/src/runtime_v2.rs deleted file mode 100644 index 4e30874..0000000 --- a/crates/mitki-wasm-runtime/src/runtime_v2.rs +++ /dev/null @@ -1,1360 +0,0 @@ -#[cfg(test)] -use std::collections::BTreeMap; - -use anyhow::{Context as _, anyhow, bail}; -use mitki_abi::{ - AbiScalar, AbiTypeKind, AbiValue, ContractValType, ExecutionDomain, SemanticTypeGraph, - TransportClass, TransportRef, TypeId, WASM_CORE_V2_M32_RUNTIME_SUPPORT, decode_canonical_blob, - decode_semantic_type_graph, encode_canonical_blob, export_wasm_name, find_export_instance, - find_export_instance_by_id, immediate_is_unit_like, signature, string_value, symbol_name, - transport_carrier_type, transport_wasm_lane, validate_graph_support, - validate_wasm_module_contract, -}; -#[cfg(test)] -use mitki_abi::{ - ArrayElements, CanonicalGraph, CanonicalNode, FacetPlan, FacetPlanEntry, FacetPlanEntryKind, - SymbolId, ValueRef, field_name, variant_name, -}; - -use crate::runtime::WasmRuntime; -#[cfg(test)] -use crate::runtime::WasmValue; - -const MITKI_ABI_V2_CUSTOM_SECTION: &str = "mitki.abi.v2"; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MitkiModuleAbiV2 { - pub metadata: Option, -} - -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct AbiRunOutput { - pub stdout: String, - pub result: Option, -} - -pub fn describe_module_abi_v2(bytes: &[u8]) -> anyhow::Result { - let mut metadata = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(reader) = payload - && reader.name() == MITKI_ABI_V2_CUSTOM_SECTION - { - let graph = decode_semantic_type_graph(reader.data())?; - validate_module_abi_v2(bytes, &graph)?; - metadata = Some(graph); - } - } - Ok(MitkiModuleAbiV2 { metadata }) -} - -fn validate_module_abi_v2(bytes: &[u8], graph: &SemanticTypeGraph) -> anyhow::Result<()> { - validate_graph_support(graph, WASM_CORE_V2_M32_RUNTIME_SUPPORT)?; - validate_wasm_module_contract(bytes, graph)?; - Ok(()) -} - -impl WasmRuntime { - pub(crate) fn invoke_export_v2( - &mut self, - export: &str, - args: &[AbiValue], - ) -> anyhow::Result { - let metadata = self - .abi_v2 - .metadata - .clone() - .ok_or_else(|| anyhow!("typed v2 invocation requires `mitki.abi.v2` metadata"))?; - let instance = find_export_instance(&metadata, export)?; - if instance.domain == ExecutionDomain::Stage { - bail!("stage entry `{export}` cannot be invoked through the runtime export API"); - } - let signature = signature(&metadata, instance.signature)?; - if signature.params.len() != args.len() { - bail!( - "typed v2 invocation expected {} argument(s) for `{export}`, found {}", - signature.params.len(), - args.len() - ); - } - let typed_export_name = instance - .wasm_field_name - .and_then(|field| string_value(&metadata, field).ok()) - .ok_or_else(|| anyhow!("typed v2 export `{export}` is missing its Wasm linkage name"))? - .to_owned(); - self.clear_stdout(); - - let mut params = Vec::with_capacity(signature.params.len()); - let mut owned_arg_blobs = Vec::new(); - for (arg, transport) in args.iter().zip(signature.params.iter()) { - match transport.transport_class { - TransportClass::Immediate => { - if let Some(val) = - abi_immediate_to_val(&metadata, transport_carrier_type(transport), arg)? - { - params.push(val); - } - } - TransportClass::CanonicalValue => { - let retained_handles = self.retain_abi_value_handles(arg)?; - let bytes = match encode_canonical_blob(arg) { - Ok(bytes) => bytes, - Err(error) => { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - }; - let ptr = match self.call_abi_v2_alloc( - i32::try_from(bytes.len()) - .map_err(|_error| anyhow!("canonical argument blob was too large"))?, - 4, - ) { - Ok(ptr) => ptr, - Err(error) => { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - }; - let offset = usize::try_from(ptr) - .map_err(|_error| anyhow!("canonical argument pointer was negative"))?; - if let Err(error) = self.write_memory(offset, &bytes) { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - params.push(wasmtime::Val::I32(ptr)); - owned_arg_blobs.push(ptr); - } - TransportClass::CapabilityHandle => { - params.push(wasmtime::Val::I32(abi_value_to_handle_lane(transport, arg)?)); - } - } - } - - let mut results = typed_result_slots(&metadata, &signature.result)?; - let call_result = self.call_dynamic_export(&typed_export_name, ¶ms, &mut results); - if let Err(error) = call_result { - for ptr in owned_arg_blobs.into_iter().rev() { - let _ = self.call_abi_v2_blob_release(ptr); - } - return Err(error); - } - - let result = decode_typed_v2_result(self, &metadata, &signature.result, &results)?; - for ptr in owned_arg_blobs.into_iter().rev() { - self.call_abi_v2_blob_release(ptr) - .with_context(|| format!("while releasing typed v2 argument blob `{ptr}`"))?; - } - let stdout = self.take_stdout(); - Ok(AbiRunOutput { stdout, result }) - } - - pub(crate) fn invoke_export_instance_v2( - &mut self, - instance_id: mitki_abi::InstanceId, - args: &[AbiValue], - ) -> anyhow::Result { - let metadata = self - .abi_v2 - .metadata - .clone() - .ok_or_else(|| anyhow!("typed v2 invocation requires `mitki.abi.v2` metadata"))?; - let instance = find_export_instance_by_id(&metadata, instance_id)?; - let logical_name = symbol_name(&metadata, instance.logical_symbol)?.to_owned(); - let typed_export_name = export_wasm_name(&metadata, instance)?.to_owned(); - let signature = signature(&metadata, instance.signature)?; - if signature.params.len() != args.len() { - bail!( - "typed v2 invocation expected {} argument(s) for export instance `{}` \ - (`{logical_name}`), found {}", - signature.params.len(), - instance_id.0, - args.len() - ); - } - self.clear_stdout(); - - let mut params = Vec::with_capacity(signature.params.len()); - let mut owned_arg_blobs = Vec::new(); - for (arg, transport) in args.iter().zip(signature.params.iter()) { - match transport.transport_class { - TransportClass::Immediate => { - if let Some(val) = - abi_immediate_to_val(&metadata, transport_carrier_type(transport), arg)? - { - params.push(val); - } - } - TransportClass::CanonicalValue => { - let retained_handles = self.retain_abi_value_handles(arg)?; - let bytes = match encode_canonical_blob(arg) { - Ok(bytes) => bytes, - Err(error) => { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - }; - let ptr = match self.call_abi_v2_alloc( - i32::try_from(bytes.len()) - .map_err(|_error| anyhow!("canonical argument blob was too large"))?, - 4, - ) { - Ok(ptr) => ptr, - Err(error) => { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - }; - let offset = usize::try_from(ptr) - .map_err(|_error| anyhow!("canonical argument pointer was negative"))?; - if let Err(error) = self.write_memory(offset, &bytes) { - self.release_handle_ids(&retained_handles)?; - return Err(error); - } - params.push(wasmtime::Val::I32(ptr)); - owned_arg_blobs.push(ptr); - } - TransportClass::CapabilityHandle => { - params.push(wasmtime::Val::I32(abi_value_to_handle_lane(transport, arg)?)); - } - } - } - - let mut results = typed_result_slots(&metadata, &signature.result)?; - let call_result = self.call_dynamic_export(&typed_export_name, ¶ms, &mut results); - if let Err(error) = call_result { - for ptr in owned_arg_blobs.into_iter().rev() { - let _ = self.call_abi_v2_blob_release(ptr); - } - return Err(error); - } - - let result = decode_typed_v2_result(self, &metadata, &signature.result, &results)?; - for ptr in owned_arg_blobs.into_iter().rev() { - self.call_abi_v2_blob_release(ptr) - .with_context(|| format!("while releasing typed v2 argument blob `{ptr}`"))?; - } - let stdout = self.take_stdout(); - Ok(AbiRunOutput { stdout, result }) - } -} - -pub fn typed_result_slots( - graph: &SemanticTypeGraph, - transport: &TransportRef, -) -> anyhow::Result> { - Ok(match transport_wasm_lane(graph, transport)? { - None => Vec::new(), - Some(ContractValType::I32) => vec![wasmtime::Val::I32(0)], - Some(ContractValType::F64) => vec![wasmtime::Val::F64(0)], - Some(other) => bail!("unexpected ABI v2 result lane `{other}`"), - }) -} - -pub(crate) fn abi_immediate_to_val( - graph: &SemanticTypeGraph, - ty: TypeId, - value: &AbiValue, -) -> anyhow::Result> { - let AbiValue::Immediate(scalar) = value else { - bail!("ABI v2 immediate transport expected an immediate value") - }; - Ok(match scalar { - AbiScalar::Unit => { - if immediate_is_unit_like(&type_kind(graph, ty)?.kind) { - None - } else { - bail!("ABI v2 unit scalar did not match the semantic transport type") - } - } - AbiScalar::Bool(value) => Some(wasmtime::Val::I32(i32::from(*value))), - AbiScalar::Int { value, .. } => Some(wasmtime::Val::I32( - i32::try_from(*value) - .map_err(|_error| anyhow!("ABI v2 integer scalar exceeded i32 range"))?, - )), - AbiScalar::Float { raw_bits, .. } => Some(wasmtime::Val::F64(*raw_bits)), - AbiScalar::Char { unicode_scalar } => Some(wasmtime::Val::I32( - i32::try_from(*unicode_scalar) - .map_err(|_error| anyhow!("ABI v2 char scalar exceeded i32 range"))?, - )), - AbiScalar::EnumTag { variant_index, .. } => { - let AbiTypeKind::Enum { variants, .. } = &type_kind(graph, ty)?.kind else { - bail!("ABI v2 enum-tag immediate expected an enum type") - }; - if *variant_index as usize >= variants.len() { - bail!("ABI v2 enum tag `{variant_index}` was out of range") - } - Some(wasmtime::Val::I32( - i32::try_from(*variant_index) - .map_err(|_error| anyhow!("ABI v2 enum tag exceeded i32 range"))?, - )) - } - }) -} - -fn decode_typed_v2_result( - runtime: &mut WasmRuntime, - graph: &SemanticTypeGraph, - transport: &TransportRef, - results: &[wasmtime::Val], -) -> anyhow::Result> { - Ok(match transport.transport_class { - TransportClass::Immediate => { - let carrier = transport_carrier_type(transport); - if immediate_is_unit_like(&type_kind(graph, carrier)?.kind) { - None - } else { - Some(AbiValue::Immediate(read_immediate_result(graph, carrier, results)?)) - } - } - TransportClass::CanonicalValue => { - let ptr = results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected canonical ABI v2 pointer result"))?; - let bytes = runtime.read_canonical_blob(ptr)?; - let value = decode_canonical_blob(&bytes)?; - let retained_handles = runtime.retain_abi_value_handles(&value); - runtime - .call_abi_v2_blob_release(ptr) - .with_context(|| format!("while releasing typed v2 result blob `{ptr}`"))?; - retained_handles?; - Some(value) - } - TransportClass::CapabilityHandle => Some(AbiValue::Handle { - type_id: transport_carrier_type(transport), - handle_id: i32_to_handle_id( - results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected ABI v2 handle result"))?, - )?, - }), - }) -} - -pub fn read_immediate_result( - graph: &SemanticTypeGraph, - ty: TypeId, - results: &[wasmtime::Val], -) -> anyhow::Result { - let kind = &type_kind(graph, ty)?.kind; - Ok(match kind { - AbiTypeKind::Bool => AbiScalar::Bool( - results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected i32 boolean result"))? - != 0, - ), - AbiTypeKind::Int { signed, bits } => AbiScalar::Int { - signed: *signed, - bits: *bits, - value: i64::from( - results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected i32 integer result"))?, - ), - }, - AbiTypeKind::Float { bits } => AbiScalar::Float { - bits: *bits, - raw_bits: results - .first() - .and_then(wasmtime::Val::f64) - .ok_or_else(|| anyhow!("expected f64 result"))? - .to_bits(), - }, - AbiTypeKind::Char => AbiScalar::Char { - unicode_scalar: u32::try_from( - results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected i32 char result"))?, - ) - .map_err(|_error| anyhow!("ABI v2 char result was negative"))?, - }, - AbiTypeKind::Enum { .. } => AbiScalar::EnumTag { - type_id: ty, - variant_index: u32::try_from( - results - .first() - .and_then(wasmtime::Val::i32) - .ok_or_else(|| anyhow!("expected i32 enum-tag result"))?, - ) - .map_err(|_error| anyhow!("ABI v2 enum tag result was negative"))?, - }, - kind if immediate_is_unit_like(kind) => AbiScalar::Unit, - other => bail!("unexpected immediate ABI v2 result kind `{other:?}`"), - }) -} - -#[cfg(test)] -pub(crate) fn abi_value_to_wasm_value( - graph: &SemanticTypeGraph, - transport: &TransportRef, - value: &AbiValue, -) -> anyhow::Result { - match transport.transport_class { - TransportClass::Immediate => { - immediate_abi_value_to_wasm(graph, transport_carrier_type(transport), value) - } - TransportClass::CanonicalValue => canonical_abi_value_to_wasm(graph, transport, value), - TransportClass::CapabilityHandle => handle_abi_value_to_wasm(transport, value), - } -} - -#[cfg(test)] -pub(crate) fn wasm_value_to_abi_value( - graph: &SemanticTypeGraph, - transport: &TransportRef, - value: &WasmValue, -) -> anyhow::Result { - match transport.transport_class { - TransportClass::Immediate => { - wasm_value_to_immediate_abi(graph, transport_carrier_type(transport), value) - } - TransportClass::CanonicalValue => wasm_value_to_canonical_abi(graph, transport, value), - TransportClass::CapabilityHandle => wasm_value_to_handle_abi(transport, value), - } -} - -#[cfg(test)] -fn immediate_abi_value_to_wasm( - graph: &SemanticTypeGraph, - ty: TypeId, - value: &AbiValue, -) -> anyhow::Result { - let AbiValue::Immediate(scalar) = value else { - bail!("ABI v2 immediate transport expected an immediate value") - }; - scalar_to_wasm_value(graph, ty, scalar) -} - -#[cfg(test)] -fn wasm_value_to_immediate_abi( - graph: &SemanticTypeGraph, - ty: TypeId, - value: &WasmValue, -) -> anyhow::Result { - Ok(AbiValue::Immediate(wasm_value_to_scalar(graph, ty, value)?)) -} - -#[cfg(test)] -fn canonical_abi_value_to_wasm( - graph: &SemanticTypeGraph, - transport: &TransportRef, - value: &AbiValue, -) -> anyhow::Result { - let AbiValue::Canonical { transport_type, graph: canonical } = value else { - bail!("ABI v2 canonical transport expected a canonical value") - }; - let carrier = transport_carrier_type(transport); - if *transport_type != carrier { - bail!( - "ABI v2 canonical value type mismatch: expected type `{}`, found `{}`", - carrier.0, - transport_type.0 - ); - } - decode_canonical_value_ref(graph, canonical, transport.semantic_type, &canonical.root) -} - -#[cfg(test)] -fn wasm_value_to_canonical_abi( - graph: &SemanticTypeGraph, - transport: &TransportRef, - value: &WasmValue, -) -> anyhow::Result { - let mut nodes = Vec::new(); - let mut handles = Vec::new(); - let root = - encode_wasm_value_ref(graph, transport.semantic_type, value, &mut nodes, &mut handles)?; - Ok(AbiValue::Canonical { - transport_type: transport_carrier_type(transport), - graph: CanonicalGraph { root, nodes, handles }, - }) -} - -#[cfg(test)] -fn scalar_to_wasm_value( - graph: &SemanticTypeGraph, - ty: TypeId, - scalar: &AbiScalar, -) -> anyhow::Result { - Ok(match scalar { - AbiScalar::Unit => WasmValue::Unit, - AbiScalar::Bool(value) => WasmValue::Bool(*value), - AbiScalar::Int { value, .. } => WasmValue::I32( - i32::try_from(*value) - .map_err(|_error| anyhow!("ABI v2 integer scalar exceeded i32 range"))?, - ), - AbiScalar::Float { raw_bits, .. } => WasmValue::F64(f64::from_bits(*raw_bits)), - AbiScalar::Char { unicode_scalar } => WasmValue::Char( - char::from_u32(*unicode_scalar) - .ok_or_else(|| anyhow!("invalid ABI v2 char scalar `{unicode_scalar}`"))?, - ), - AbiScalar::EnumTag { variant_index, .. } => { - let AbiTypeKind::Enum { variants, .. } = &type_kind(graph, ty)?.kind else { - bail!("ABI v2 enum tag transport expected an enum type") - }; - let variant = variants - .get(*variant_index as usize) - .ok_or_else(|| anyhow!("ABI v2 enum tag `{variant_index}` was out of range"))?; - WasmValue::Enum { - variant: variant_name(graph, variant.name)?.to_owned(), - fields: Vec::new(), - } - } - }) -} - -#[cfg(test)] -fn wasm_value_to_scalar( - graph: &SemanticTypeGraph, - ty: TypeId, - value: &WasmValue, -) -> anyhow::Result { - Ok(match (value, &type_kind(graph, ty)?.kind) { - (WasmValue::Unit, AbiTypeKind::Unit) => AbiScalar::Unit, - (WasmValue::Bool(value), AbiTypeKind::Bool) => AbiScalar::Bool(*value), - (WasmValue::I32(value), AbiTypeKind::Int { signed, bits }) => { - AbiScalar::Int { signed: *signed, bits: *bits, value: i64::from(*value) } - } - (WasmValue::F64(value), AbiTypeKind::Float { bits }) => { - AbiScalar::Float { bits: *bits, raw_bits: value.to_bits() } - } - (WasmValue::Char(value), AbiTypeKind::Char) => { - AbiScalar::Char { unicode_scalar: u32::from(*value) } - } - (WasmValue::Enum { variant, fields }, AbiTypeKind::Enum { variants, .. }) - if fields.is_empty() => - { - let variant_index = variants - .iter() - .position(|candidate| { - variant_name(graph, candidate.name).is_ok_and(|name| name == variant) - }) - .ok_or_else(|| anyhow!("unknown nullary enum variant `{variant}`"))?; - AbiScalar::EnumTag { type_id: ty, variant_index: variant_index as u32 } - } - _ => bail!("value `{value:?}` does not match immediate ABI v2 type `{}`", ty.0), - }) -} - -#[cfg(test)] -fn handle_abi_value_to_wasm( - transport: &TransportRef, - value: &AbiValue, -) -> anyhow::Result { - let AbiValue::Handle { type_id, handle_id } = value else { - bail!("ABI v2 capability transport expected a handle value"); - }; - let carrier = transport_carrier_type(transport); - if *type_id != carrier { - bail!("ABI v2 handle type mismatch: expected `{}`, found `{}`", carrier.0, type_id.0); - } - Ok(WasmValue::Handle { type_id: type_id.0, handle_id: *handle_id }) -} - -#[cfg(test)] -fn wasm_value_to_handle_abi( - transport: &TransportRef, - value: &WasmValue, -) -> anyhow::Result { - let WasmValue::Handle { type_id, handle_id } = value else { - bail!("ABI v2 capability transport expected a handle value"); - }; - let carrier = transport_carrier_type(transport); - if *type_id != carrier.0 { - bail!("ABI v2 handle type mismatch: expected `{}`, found `{}`", carrier.0, type_id); - } - Ok(AbiValue::Handle { type_id: carrier, handle_id: *handle_id }) -} - -#[cfg(test)] -fn decode_canonical_value_ref( - graph: &SemanticTypeGraph, - canonical: &CanonicalGraph, - ty: TypeId, - value: &ValueRef, -) -> anyhow::Result { - if let AbiTypeKind::Intersection { carrier, facet_plan, .. } = &type_kind(graph, ty)?.kind { - return match value { - ValueRef::NodeRef(node_id) => { - let node = canonical - .nodes - .get(node_id.0 as usize) - .ok_or_else(|| anyhow!("canonical node `{}` was out of range", node_id.0))?; - match node { - CanonicalNode::Intersection { carrier: carrier_value, facets, .. } => { - let live_entries = live_facet_entries(graph, *facet_plan)?; - if live_entries.len() != facets.len() { - bail!("intersection facet count did not match the facet plan"); - } - Ok(WasmValue::Intersection { - carrier: Box::new(decode_canonical_value_ref( - graph, - canonical, - *carrier, - carrier_value, - )?), - facets: live_entries - .iter() - .zip(facets.iter()) - .map(|(entry, facet)| { - decode_canonical_value_ref( - graph, - canonical, - entry.member, - facet, - ) - }) - .collect::>>()?, - }) - } - _ => Ok(WasmValue::Intersection { - carrier: Box::new(decode_canonical_value_ref( - graph, canonical, *carrier, value, - )?), - facets: Vec::new(), - }), - } - } - ValueRef::InlineScalar(_) | ValueRef::HandleRef(_) => Ok(WasmValue::Intersection { - carrier: Box::new(decode_canonical_value_ref(graph, canonical, *carrier, value)?), - facets: Vec::new(), - }), - }; - } - - match value { - ValueRef::InlineScalar(scalar) => scalar_to_wasm_value(graph, ty, scalar), - ValueRef::NodeRef(node_id) => { - let node = canonical - .nodes - .get(node_id.0 as usize) - .ok_or_else(|| anyhow!("canonical node `{}` was out of range", node_id.0))?; - decode_node_to_wasm_value(graph, canonical, ty, node) - } - ValueRef::HandleRef(handle_id) => { - let slot = canonical.handles.get(handle_id.0 as usize).ok_or_else(|| { - anyhow!("canonical handle slot `{}` was out of range", handle_id.0) - })?; - Ok(WasmValue::Handle { type_id: slot.type_id.0, handle_id: slot.handle_id }) - } - } -} - -#[cfg(test)] -fn decode_node_to_wasm_value( - graph: &SemanticTypeGraph, - canonical: &CanonicalGraph, - ty: TypeId, - node: &CanonicalNode, -) -> anyhow::Result { - Ok(match (&type_kind(graph, ty)?.kind, node) { - (AbiTypeKind::String, CanonicalNode::String { value, .. }) => { - WasmValue::String(value.clone()) - } - (AbiTypeKind::Array { elem }, CanonicalNode::Array { elements, .. }) => { - let values = match elements { - ArrayElements::Values(values) => values - .iter() - .map(|value| decode_canonical_value_ref(graph, canonical, *elem, value)) - .collect::>>()?, - ArrayElements::PackedScalars { kind, len, bytes } => { - decode_packed_array(graph, *elem, *kind, *len, bytes)? - } - }; - WasmValue::Array(values) - } - (AbiTypeKind::Tuple { elems }, CanonicalNode::Tuple { fields, .. }) => WasmValue::Tuple( - elems - .iter() - .zip(fields.iter()) - .map(|(&ty, value)| decode_canonical_value_ref(graph, canonical, ty, value)) - .collect::>>()?, - ), - (AbiTypeKind::Record { fields: shape }, CanonicalNode::Record { fields, .. }) => { - WasmValue::Record( - shape - .iter() - .zip(fields.iter()) - .map(|(field, value)| { - Ok(( - field_name(graph, field.name)?.to_owned(), - decode_canonical_value_ref(graph, canonical, field.ty, value)?, - )) - }) - .collect::>>()?, - ) - } - (AbiTypeKind::Struct { fields: shape, .. }, CanonicalNode::Struct { fields, .. }) => { - WasmValue::Struct( - shape - .iter() - .zip(fields.iter()) - .map(|(field, value)| { - Ok(( - field_name(graph, field.name)?.to_owned(), - decode_canonical_value_ref(graph, canonical, field.ty, value)?, - )) - }) - .collect::>>()?, - ) - } - (AbiTypeKind::Enum { variants, .. }, CanonicalNode::Enum { variant_index, fields, .. }) => { - let variant = variants - .get(*variant_index as usize) - .ok_or_else(|| anyhow!("enum variant index `{variant_index}` was out of range"))?; - WasmValue::Enum { - variant: variant_name(graph, variant.name)?.to_owned(), - fields: variant - .fields - .iter() - .zip(fields.iter()) - .map(|(&field_ty, value)| { - decode_canonical_value_ref(graph, canonical, field_ty, value) - }) - .collect::>>()?, - } - } - (AbiTypeKind::Union { members }, CanonicalNode::Union { arm_index, payload, .. }) => { - let payload_ty = *members - .get(*arm_index as usize) - .ok_or_else(|| anyhow!("union arm index `{arm_index}` was out of range"))?; - WasmValue::Union { - arm_index: *arm_index, - value: Box::new(decode_canonical_value_ref(graph, canonical, payload_ty, payload)?), - } - } - (AbiTypeKind::Intersection { .. }, CanonicalNode::Intersection { .. }) => { - bail!("intersection nodes are decoded at the value-ref layer") - } - _ => bail!("canonical node did not match ABI v2 type `{}`", ty.0), - }) -} - -#[cfg(test)] -fn encode_wasm_value_ref( - graph: &SemanticTypeGraph, - ty: TypeId, - value: &WasmValue, - nodes: &mut Vec, - handles: &mut Vec, -) -> anyhow::Result { - match &type_kind(graph, ty)?.kind { - AbiTypeKind::Unit - | AbiTypeKind::Bool - | AbiTypeKind::Int { .. } - | AbiTypeKind::Float { .. } - | AbiTypeKind::Char => Ok(ValueRef::InlineScalar(wasm_value_to_scalar(graph, ty, value)?)), - AbiTypeKind::Enum { variants, .. } - if variants.iter().all(|variant| variant.fields.is_empty()) => - { - Ok(ValueRef::InlineScalar(wasm_value_to_scalar(graph, ty, value)?)) - } - AbiTypeKind::String => { - let WasmValue::String(text) = value else { bail!("expected string value") }; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::String { transport_type: ty, value: text.clone() }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Array { elem } => { - let WasmValue::Array(items) = value else { bail!("expected array value") }; - let elements = ArrayElements::Values( - items - .iter() - .map(|item| encode_wasm_value_ref(graph, *elem, item, nodes, handles)) - .collect::>>()?, - ); - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Array { transport_type: ty, elements }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Tuple { elems } => { - let WasmValue::Tuple(items) = value else { bail!("expected tuple value") }; - let fields = elems - .iter() - .zip(items.iter()) - .map(|(&field_ty, item)| { - encode_wasm_value_ref(graph, field_ty, item, nodes, handles) - }) - .collect::>>()?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Tuple { transport_type: ty, fields }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Record { fields: shape } => { - let WasmValue::Record(items) = value else { bail!("expected record value") }; - let map = items.iter().cloned().collect::>(); - let fields = shape - .iter() - .map(|field| { - let name = field_name(graph, field.name)?; - let item = - map.get(name).ok_or_else(|| anyhow!("missing record field `{name}`"))?; - encode_wasm_value_ref(graph, field.ty, item, nodes, handles) - }) - .collect::>>()?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Record { transport_type: ty, fields }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Struct { fields: shape, .. } => { - let WasmValue::Struct(items) = value else { bail!("expected struct value") }; - let map = items.iter().cloned().collect::>(); - let fields = shape - .iter() - .map(|field| { - let name = field_name(graph, field.name)?; - let item = - map.get(name).ok_or_else(|| anyhow!("missing struct field `{name}`"))?; - encode_wasm_value_ref(graph, field.ty, item, nodes, handles) - }) - .collect::>>()?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Struct { transport_type: ty, fields }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Enum { variants, .. } => { - let WasmValue::Enum { variant, fields } = value else { bail!("expected enum value") }; - let variant_index = variants - .iter() - .position(|candidate| { - variant_name(graph, candidate.name).is_ok_and(|name| name == variant) - }) - .ok_or_else(|| anyhow!("unknown enum variant `{variant}`"))?; - let variant_shape = &variants[variant_index]; - let payload = variant_shape - .fields - .iter() - .zip(fields.iter()) - .map(|(&field_ty, value)| { - encode_wasm_value_ref(graph, field_ty, value, nodes, handles) - }) - .collect::>>()?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Enum { - transport_type: ty, - variant_index: variant_index as u32, - fields: payload, - }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Union { members } => { - let WasmValue::Union { arm_index, value } = value else { - bail!("expected union value") - }; - let payload_ty = *members - .get(*arm_index as usize) - .ok_or_else(|| anyhow!("union arm index `{arm_index}` was out of range"))?; - let payload = encode_wasm_value_ref(graph, payload_ty, value, nodes, handles)?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Union { transport_type: ty, arm_index: *arm_index, payload }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Intersection { carrier, facet_plan, .. } => { - let WasmValue::Intersection { carrier: carrier_value, facets } = value else { - bail!("expected intersection value") - }; - let live_entries = live_facet_entries(graph, *facet_plan)?; - if live_entries.is_empty() { - return encode_wasm_value_ref(graph, *carrier, carrier_value, nodes, handles); - } - if live_entries.len() != facets.len() { - bail!("intersection facet count did not match the facet plan"); - } - let carrier = encode_wasm_value_ref(graph, *carrier, carrier_value, nodes, handles)?; - let facet_values = live_entries - .iter() - .zip(facets.iter()) - .map(|(entry, facet)| { - encode_wasm_value_ref(graph, entry.member, facet, nodes, handles) - }) - .collect::>>()?; - let node_id = mitki_abi::NodeId(nodes.len() as u32); - nodes.push(CanonicalNode::Intersection { - transport_type: ty, - carrier, - facets: facet_values, - }); - Ok(ValueRef::NodeRef(node_id)) - } - AbiTypeKind::Function { .. } | AbiTypeKind::Opaque { .. } => { - let WasmValue::Handle { type_id, handle_id } = value else { - bail!("expected handle value") - }; - if *type_id != ty.0 { - bail!("expected handle value for type `{}`", ty.0); - } - let slot_id = mitki_abi::HandleSlotId(handles.len() as u32); - handles.push(mitki_abi::HandleSlot { type_id: ty, handle_id: *handle_id }); - Ok(ValueRef::HandleRef(slot_id)) - } - } -} - -#[cfg(test)] -fn decode_packed_array( - graph: &SemanticTypeGraph, - elem_ty: TypeId, - kind: mitki_abi::PackedScalarKind, - len: u32, - bytes: &[u8], -) -> anyhow::Result> { - let _ = graph; - match kind { - mitki_abi::PackedScalarKind::Bool => { - Ok(bytes.iter().take(len as usize).map(|byte| WasmValue::Bool(*byte != 0)).collect()) - } - mitki_abi::PackedScalarKind::I32 => Ok(bytes - .chunks_exact(4) - .take(len as usize) - .map(|chunk| WasmValue::I32(i32::from_le_bytes(chunk.try_into().expect("i32 chunk")))) - .collect()), - mitki_abi::PackedScalarKind::F64 => Ok(bytes - .chunks_exact(8) - .take(len as usize) - .map(|chunk| WasmValue::F64(f64::from_le_bytes(chunk.try_into().expect("f64 chunk")))) - .collect()), - mitki_abi::PackedScalarKind::Char => Ok(bytes - .chunks_exact(4) - .take(len as usize) - .map(|chunk| { - let scalar = u32::from_le_bytes(chunk.try_into().expect("char chunk")); - WasmValue::Char(char::from_u32(scalar).expect("packed scalar char")) - }) - .collect()), - mitki_abi::PackedScalarKind::I64 | mitki_abi::PackedScalarKind::F32 => { - bail!( - "ABI v2 baseline runtime facade cannot decode packed scalar array kind `{kind:?}` \ - for element type `{}`", - elem_ty.0 - ) - } - } -} - -fn type_kind(graph: &SemanticTypeGraph, ty: TypeId) -> anyhow::Result<&mitki_abi::TypeNode> { - graph.types.get(ty.0 as usize).ok_or_else(|| anyhow!("unknown ABI v2 type `{}`", ty.0)) -} - -#[cfg(test)] -fn live_facet_entries( - graph: &SemanticTypeGraph, - plan_id: Option, -) -> anyhow::Result> { - let Some(plan_id) = plan_id else { - return Ok(Vec::new()); - }; - let plan = facet_plan(graph, plan_id)?; - Ok(plan - .entries - .iter() - .filter(|entry| entry.kind != FacetPlanEntryKind::Erased) - .cloned() - .collect()) -} - -#[cfg(test)] -fn facet_plan(graph: &SemanticTypeGraph, id: mitki_abi::FacetPlanId) -> anyhow::Result<&FacetPlan> { - graph - .facet_plans - .get(id.0 as usize) - .ok_or_else(|| anyhow!("unknown ABI v2 facet plan `{}`", id.0)) -} - -fn abi_value_to_handle_lane(transport: &TransportRef, value: &AbiValue) -> anyhow::Result { - let AbiValue::Handle { type_id, handle_id } = value else { - bail!("ABI v2 capability transport expected a handle value"); - }; - let carrier = transport_carrier_type(transport); - if *type_id != carrier { - bail!("ABI v2 handle type mismatch: expected `{}`, found `{}`", carrier.0, type_id.0); - } - i32::try_from(*handle_id).map_err(|_error| anyhow!("ABI v2 handle id exceeded i32 range")) -} - -fn i32_to_handle_id(raw: i32) -> anyhow::Result { - u32::try_from(raw).map_err(|_error| anyhow!("ABI v2 handle id was negative")) -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - - use mitki_abi::{ - AbiTypeKind, FieldNameId, RecordField, TransportRef, encode_semantic_type_graph, - }; - use mitki_comptime_wasm::compile_file_to_wasm; - use mitki_db::RootDatabase; - use mitki_inputs::File; - - use super::*; - use crate::{RunConfig, WasmRuntime, describe_module_abi}; - - fn compile_fixture(fixture: &str) -> Vec { - let db = RootDatabase::default(); - let file = File::new(&db, "runtime_v2_tests.mitki".into(), fixture.to_owned()); - compile_file_to_wasm(&db, file).unwrap_or_else(|diagnostics| { - let messages = - diagnostics.iter().map(|diag| diag.message().to_owned()).collect::>(); - panic!("expected Wasm compilation to succeed, got diagnostics: {messages:#?}"); - }) - } - - fn rewrite_v2_metadata(bytes: &[u8], mutate: impl FnOnce(&mut SemanticTypeGraph)) -> Vec { - let mut module = wasm_encoder::Module::new(); - let mut mutated = false; - let mut mutate = Some(mutate); - - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload.expect("valid wasm payload"); - match payload { - wasmparser::Payload::CustomSection(reader) - if reader.name() == MITKI_ABI_V2_CUSTOM_SECTION => - { - let mut graph = - decode_semantic_type_graph(reader.data()).expect("valid v2 metadata"); - mutate.take().expect("v2 metadata mutate closure")(&mut graph); - let encoded = - encode_semantic_type_graph(&graph).expect("re-encoded v2 metadata"); - module.section(&wasm_encoder::CustomSection { - name: reader.name().into(), - data: Cow::Owned(encoded), - }); - mutated = true; - } - other => { - if let Some((id, range)) = other.as_section() { - module.section(&wasm_encoder::RawSection { id, data: &bytes[range] }); - } - } - } - } - - assert!(mutated, "expected ABI v2 metadata section to exist"); - module.finish() - } - - fn opaque_handle_transport() -> (SemanticTypeGraph, TransportRef) { - let mut graph = SemanticTypeGraph::default(); - let opaque = - graph.push_type(AbiTypeKind::Opaque { capability_id: mitki_abi::CapabilityId(0) }); - ( - graph, - TransportRef { - semantic_type: opaque, - transport_class: TransportClass::CapabilityHandle, - transport_type: None, - }, - ) - } - - fn struct_with_handle_transport() -> (SemanticTypeGraph, TransportRef, TypeId) { - let mut graph = SemanticTypeGraph::default(); - let field_name = graph.insert_string("callback"); - graph.field_names.push(field_name); - let handle_ty = - graph.push_type(AbiTypeKind::Opaque { capability_id: mitki_abi::CapabilityId(0) }); - let struct_ty = graph.push_type(AbiTypeKind::Struct { - nominal: SymbolId(0), - fields: vec![RecordField { name: FieldNameId(0), ty: handle_ty }], - }); - ( - graph, - TransportRef { - semantic_type: struct_ty, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - handle_ty, - ) - } - - fn union_transport() -> (SemanticTypeGraph, TransportRef) { - let mut graph = SemanticTypeGraph::default(); - let int_ty = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let string_ty = graph.push_type(AbiTypeKind::String); - let union_ty = graph.push_type(AbiTypeKind::Union { members: vec![int_ty, string_ty] }); - ( - graph, - TransportRef { - semantic_type: union_ty, - transport_class: TransportClass::CanonicalValue, - transport_type: None, - }, - ) - } - - fn intersection_transport() -> (SemanticTypeGraph, TransportRef, TypeId) { - let mut graph = SemanticTypeGraph::default(); - let field_name = graph.insert_string("value"); - graph.field_names.push(field_name); - let int_ty = graph.push_type(AbiTypeKind::Int { signed: true, bits: 32 }); - let carrier_ty = graph.push_type(AbiTypeKind::Record { - fields: vec![RecordField { name: FieldNameId(0), ty: int_ty }], - }); - let facet_ty = - graph.push_type(AbiTypeKind::Opaque { capability_id: mitki_abi::CapabilityId(0) }); - let facet_plan = mitki_abi::FacetPlanId(graph.facet_plans.len() as u32); - graph.facet_plans.push(FacetPlan { - id: facet_plan, - entries: vec![ - FacetPlanEntry { member: carrier_ty, kind: FacetPlanEntryKind::Erased }, - FacetPlanEntry { member: facet_ty, kind: FacetPlanEntryKind::HandleFacet }, - ], - }); - let intersection_ty = graph.push_type(AbiTypeKind::Intersection { - members: vec![carrier_ty, facet_ty], - carrier: carrier_ty, - facet_plan: Some(facet_plan), - }); - ( - graph, - TransportRef { - semantic_type: intersection_ty, - transport_class: TransportClass::CanonicalValue, - transport_type: Some(carrier_ty), - }, - facet_ty, - ) - } - - #[test] - fn describe_module_abi_v2_exposes_typed_export_metadata() { - let bytes = compile_fixture( - r#" -export fun make_pair(): (int, int) { - val pair = (20, 22) - pair -} -"#, - ); - let summary = describe_module_abi(&bytes).expect("module ABI summary should decode"); - assert!(!summary.exports.iter().any(|export| export.name == "make_pair")); - assert!(summary.exports.iter().any(|export| export.name == "mitki:typed/2/f$0")); - - let abi_v2 = describe_module_abi_v2(&bytes).expect("v2 ABI metadata should decode"); - let graph = abi_v2.metadata.expect("expected v2 metadata"); - assert_eq!(graph.function_instances.len(), 1); - let instance = &graph.function_instances[0]; - assert_eq!( - symbol_name(&graph, instance.logical_symbol).expect("logical symbol"), - "make_pair" - ); - assert_eq!( - string_value(&graph, instance.wasm_field_name.expect("typed export name")) - .expect("typed export linkage"), - "mitki:typed/2/f$0" - ); - assert!(graph.raw_exports.iter().any(|export| { - string_value(&graph, export.name).is_ok_and(|name| name == "mitki:typed/2/f$0") - })); - } - - #[test] - fn invoke_export_v2_round_trips_canonical_arrays() { - let bytes = compile_fixture( - r#" -export fun echo(value: [int]): [int] { - value -} -"#, - ); - let mut runtime = WasmRuntime::new(&bytes, RunConfig::without_wasi()) - .expect("runtime should instantiate"); - let metadata = runtime.abi_v2.metadata.clone().expect("expected v2 metadata"); - let instance = find_export_instance(&metadata, "echo").expect("echo export instance"); - let signature = signature(&metadata, instance.signature).expect("signature"); - let input = wasm_value_to_abi_value( - &metadata, - &signature.params[0], - &WasmValue::Array(vec![WasmValue::I32(20), WasmValue::I32(22)]), - ) - .expect("input should canonicalize"); - - let output = - runtime.invoke_export_v2("echo", &[input]).expect("v2 invocation should succeed"); - let result = output.result.expect("echo should return a value"); - let decoded = abi_value_to_wasm_value(&metadata, &signature.result, &result) - .expect("result should decode through v2 facade"); - assert_eq!(decoded, WasmValue::Array(vec![WasmValue::I32(20), WasmValue::I32(22)])); - } - - #[test] - fn describe_module_abi_v2_rejects_unsupported_required_features() { - let bytes = compile_fixture( - r#" -export fun answer(): int { - 42 -} -"#, - ); - let bytes = rewrite_v2_metadata(&bytes, |graph| { - graph.required_features |= 1 << 63; - }); - - let error = describe_module_abi_v2(&bytes).expect_err("unsupported feature bits"); - assert!(error.to_string().contains("required ABI v2 feature bits")); - } - - #[test] - fn describe_module_abi_v2_rejects_invalid_boundary_memory_export() { - let bytes = compile_fixture( - r#" -export fun answer(): int { - 42 -} -"#, - ); - let bytes = rewrite_v2_metadata(&bytes, |graph| { - let memory = graph.insert_string("not_the_exported_memory"); - graph.boundary_memory.export_name = Some(memory); - }); - - let error = match WasmRuntime::new(&bytes, RunConfig::without_wasi()) { - Ok(_) => panic!("invalid boundary memory should be rejected"), - Err(error) => error, - }; - assert!(error.to_string().contains("boundary memory export")); - } - - #[test] - fn describe_module_abi_v2_rejects_mismatched_raw_export_records() { - let bytes = compile_fixture( - r#" -export fun answer(): int { - 42 -} -"#, - ); - let bytes = rewrite_v2_metadata(&bytes, |graph| { - let bogus = graph.insert_string("missing:v2/export"); - graph.raw_exports.push(mitki_abi::RawExportRecord { - name: bogus, - symbol: None, - signature: None, - }); - }); - - let error = describe_module_abi_v2(&bytes).expect_err("bogus raw export should fail"); - assert!(error.to_string().contains("raw export")); - } - - #[test] - fn tuple_blob_encoding_preserves_all_fields() { - let bytes = compile_fixture( - r#" -export fun make_pair(): (int, int) { - (20, 22) -} -"#, - ); - let mut runtime = WasmRuntime::new(&bytes, RunConfig::without_wasi()) - .expect("runtime should instantiate"); - let metadata = runtime.abi_v2.metadata.clone().expect("expected v2 metadata"); - let instance = find_export_instance(&metadata, "make_pair").expect("make_pair export"); - let typed_export_name = - string_value(&metadata, instance.wasm_field_name.expect("typed export linkage")) - .expect("typed export name") - .to_owned(); - let mut results = vec![wasmtime::Val::I32(0)]; - runtime - .call_dynamic_export(&typed_export_name, &[], &mut results) - .expect("typed export should execute"); - let ptr = results[0].i32().expect("typed export should return i32"); - let blob = runtime.read_canonical_blob(ptr).expect("blob should read"); - let value = decode_canonical_blob(&blob).expect("blob should decode"); - let AbiValue::Canonical { graph, .. } = value else { - panic!("expected canonical tuple result"); - }; - let ValueRef::NodeRef(root) = graph.root else { - panic!("expected tuple node root"); - }; - let CanonicalNode::Tuple { fields, .. } = - graph.nodes.get(root.0 as usize).expect("tuple root node") - else { - panic!("expected tuple root"); - }; - assert_eq!( - fields, - &[ - ValueRef::InlineScalar(AbiScalar::Int { signed: true, bits: 32, value: 20 }), - ValueRef::InlineScalar(AbiScalar::Int { signed: true, bits: 32, value: 22 }), - ] - ); - } - - #[test] - fn capability_handles_round_trip_through_runtime_facade() { - let (graph, transport) = opaque_handle_transport(); - let value = AbiValue::Handle { type_id: transport.semantic_type, handle_id: 77 }; - - let lowered = - abi_value_to_wasm_value(&graph, &transport, &value).expect("handle should lower"); - assert_eq!( - lowered, - WasmValue::Handle { type_id: transport.semantic_type.0, handle_id: 77 } - ); - - let lifted = - wasm_value_to_abi_value(&graph, &transport, &lowered).expect("handle should lift"); - assert_eq!(lifted, value); - } - - #[test] - fn canonical_facade_preserves_nested_handle_slots() { - let (graph, transport, handle_ty) = struct_with_handle_transport(); - let value = WasmValue::Struct(vec![( - "callback".to_owned(), - WasmValue::Handle { type_id: handle_ty.0, handle_id: 91 }, - )]); - - let abi = wasm_value_to_abi_value(&graph, &transport, &value) - .expect("canonical value should preserve nested handles"); - let AbiValue::Canonical { graph: canonical, .. } = &abi else { - panic!("expected canonical value"); - }; - assert_eq!( - canonical.handles, - vec![mitki_abi::HandleSlot { type_id: handle_ty, handle_id: 91 }] - ); - let decoded = abi_value_to_wasm_value(&graph, &transport, &abi) - .expect("canonical value should decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_facade_round_trips_union_nodes() { - let (graph, transport) = union_transport(); - let value = - WasmValue::Union { arm_index: 1, value: Box::new(WasmValue::String("hi".to_owned())) }; - - let abi = - wasm_value_to_abi_value(&graph, &transport, &value).expect("union should canonicalize"); - let decoded = - abi_value_to_wasm_value(&graph, &transport, &abi).expect("union should decode"); - assert_eq!(decoded, value); - } - - #[test] - fn canonical_facade_round_trips_intersection_nodes() { - let (graph, transport, facet_ty) = intersection_transport(); - let value = WasmValue::Intersection { - carrier: Box::new(WasmValue::Record(vec![("value".to_owned(), WasmValue::I32(7))])), - facets: vec![WasmValue::Handle { type_id: facet_ty.0, handle_id: 12 }], - }; - - let abi = wasm_value_to_abi_value(&graph, &transport, &value) - .expect("intersection should canonicalize"); - let AbiValue::Canonical { transport_type, .. } = &abi else { - panic!("expected canonical intersection value"); - }; - assert_eq!(*transport_type, transport.transport_type.expect("carrier type")); - let decoded = - abi_value_to_wasm_value(&graph, &transport, &abi).expect("intersection should decode"); - assert_eq!(decoded, value); - } -} diff --git a/crates/mitki-yellow/Cargo.toml b/crates/mitki-yellow/Cargo.toml deleted file mode 100644 index 012ee6f..0000000 --- a/crates/mitki-yellow/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "mitki-yellow" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[dependencies] -salsa.workspace = true -text-size.workspace = true diff --git a/crates/mitki-yellow/src/ast.rs b/crates/mitki-yellow/src/ast.rs deleted file mode 100644 index 0918a97..0000000 --- a/crates/mitki-yellow/src/ast.rs +++ /dev/null @@ -1,1965 +0,0 @@ -//! Typed AST wrappers over the raw syntax tree. - -use text_size::TextRange; - -use crate::SyntaxKind::*; -use crate::{SyntaxElement, SyntaxNode, SyntaxToken}; - -/// Typed wrapper around a syntax node. -pub trait Node<'db>: Sized { - /// Attempts to cast a raw syntax node into this typed wrapper. - fn cast(syntax: SyntaxNode<'db>) -> Option; - - /// Returns the underlying syntax node. - fn syntax(&self) -> &SyntaxNode<'db>; -} - -/// Nodes that expose a name child. -pub trait HasName<'db>: Node<'db> { - /// Returns the name child, if present. - fn name(&self) -> Option> { - child(self.syntax()) - } -} - -/// Root module node. -pub struct Module<'db>(pub SyntaxNode<'db>); - -impl<'db> Module<'db> { - /// Wraps a root syntax node as a module. - pub fn new(root: SyntaxNode<'db>) -> Self { - Self(root) - } - - /// Iterates items contained in the module. - pub fn items(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Item::cast) - } -} - -impl<'db> Node<'db> for Module<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - (syntax.kind() == MODULE).then_some(Self(syntax)) - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Top-level items. -pub enum Item<'db> { - Function(Function<'db>), - Instance(InstanceItem<'db>), - Module(ModuleItem<'db>), - Use(UseItem<'db>), - Struct(StructDef<'db>), - Enum(EnumDef<'db>), -} - -impl<'db> Node<'db> for Item<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - FN => Item::Function(Function(syntax)).into(), - INSTANCE_ITEM => Item::Instance(InstanceItem(syntax)).into(), - MOD_ITEM => Item::Module(ModuleItem(syntax)).into(), - USE_ITEM => Item::Use(UseItem(syntax)).into(), - STRUCT_DEF => Item::Struct(StructDef(syntax)).into(), - ENUM_DEF => Item::Enum(EnumDef(syntax)).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - match self { - Item::Function(function) => function.syntax(), - Item::Instance(instance) => instance.syntax(), - Item::Module(module) => module.syntax(), - Item::Use(use_item) => use_item.syntax(), - Item::Struct(s) => s.syntax(), - Item::Enum(e) => e.syntax(), - } - } -} - -/// Function definition node. -pub struct Function<'db>(SyntaxNode<'db>); - -impl<'db> Function<'db> { - pub fn is_comptime(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != FUN_KW) - .any(|token| token.kind() == NAME && token.text_trimmed() == "comptime") - } - - pub fn is_exported(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != FUN_KW) - .any(|token| token.kind() == NAME && token.text_trimmed() == "export") - } - - pub fn is_unsafe(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != FUN_KW) - .any(|token| token.kind() == NAME && token.text_trimmed() == "unsafe") - } - - pub fn import_module(&self) -> Option<&'db str> { - let mut tokens = self - .0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != FUN_KW); - - while let Some(token) = tokens.next() { - if token.kind() == NAME && token.text_trimmed() == "import" { - let module = tokens.find(|next| next.kind() == STRING)?; - return Some(trim_string_quotes(module.text_trimmed())); - } - } - - None - } - - /// Iterates type parameters in the generic parameter list. - pub fn type_params(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(TypeParam::cast) - } - - /// Returns the parameter list. - pub fn params(&self) -> Option> { - child(&self.0) - } - - /// Returns the return type, if any. - pub fn ret_type(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the function body. - pub fn body(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for Function<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - FN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Return type node. -pub struct RetType<'db> { - pub(crate) syntax: SyntaxNode<'db>, -} - -impl<'db> RetType<'db> { - /// Returns the type node. - #[inline] - pub fn ty(&self) -> Option> { - child(&self.syntax) - } -} - -impl<'db> Node<'db> for RetType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - RETURN_TYPE => Some(Self { syntax }), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.syntax - } -} - -impl<'db> HasName<'db> for Function<'db> {} - -/// Generic argument list node in a boundary instance item. -pub struct GenericArgList<'db>(SyntaxNode<'db>); - -impl<'db> GenericArgList<'db> { - pub fn types(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Type::cast) - } -} - -impl<'db> Node<'db> for GenericArgList<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - GENERIC_ARG_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Boundary instance item (`export instance foo[str];`). -pub struct InstanceItem<'db>(SyntaxNode<'db>); - -impl<'db> InstanceItem<'db> { - pub fn is_exported(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| !(token.kind() == NAME && token.text_trimmed() == "instance")) - .any(|token| token.kind() == NAME && token.text_trimmed() == "export") - } - - pub fn is_imported(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| !(token.kind() == NAME && token.text_trimmed() == "instance")) - .any(|token| token.kind() == NAME && token.text_trimmed() == "import") - } - - pub fn arg_list(&self) -> Option> { - child(&self.0) - } - - pub fn type_args(&self) -> Vec> { - self.arg_list().map(|list| list.types().collect()).unwrap_or_default() - } -} - -impl<'db> Node<'db> for InstanceItem<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - INSTANCE_ITEM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for InstanceItem<'db> {} - -/// Module declaration item (`mod foo;`). -pub struct ModuleItem<'db>(SyntaxNode<'db>); - -impl<'db> ModuleItem<'db> { - pub fn is_public(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != MOD_KW) - .any(|token| token.kind() == PUB_KW) - } -} - -impl<'db> Node<'db> for ModuleItem<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - MOD_ITEM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for ModuleItem<'db> {} - -/// Use item (`use std::io::print_int;`). -pub struct UseItem<'db>(SyntaxNode<'db>); - -impl<'db> UseItem<'db> { - pub fn path(&self) -> Option> { - child(&self.0) - } - - pub fn alias(&self) -> Option> { - self.0.children().filter(|child| child.kind() == IDENT).find_map(Name::cast) - } -} - -impl<'db> Node<'db> for UseItem<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - USE_ITEM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Type parameter node in a generic parameter list. -pub struct TypeParam<'db>(SyntaxNode<'db>); - -impl<'db> TypeParam<'db> { - pub fn as_str(&self) -> &'db str { - first_non_trivia_token(&self.0).map_or("", |t| t.text_trimmed()) - } -} - -impl<'db> Node<'db> for TypeParam<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - TYPE_PARAM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Parameter list node. -pub struct Params<'db>(SyntaxNode<'db>); - -impl<'db> Params<'db> { - /// Iterates parameters in the list. - pub fn iter(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Param::cast) - } -} - -impl<'db> Node<'db> for Params<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PARAM_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Function parameter node. -pub struct Param<'db>(SyntaxNode<'db>); - -impl<'db> Param<'db> { - pub fn is_mutable(&self) -> bool { - first_non_trivia_token(&self.0).is_some_and(|token| token.kind() == VAR_KW) - } - - /// Returns the parameter pattern. - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the parameter type, if any. - pub fn ty(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for Param<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PARAM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Block node containing statements and an optional tail expression. -pub struct Block<'db>(SyntaxNode<'db>); - -impl<'db> Block<'db> { - /// Iterates statements in the block. - pub fn stmts(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Stmt::cast) - } - - /// Returns the tail expression, if any. - pub fn tail_expr(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for Block<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STMT_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Statement node. -pub enum Stmt<'db> { - Val(Val<'db>), - Assign(AssignStmt<'db>), - Return(ReturnStmt<'db>), - Expr(ExprStmt<'db>), -} - -impl<'db> Node<'db> for Stmt<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - VAL_STMT => Stmt::Val(Val(syntax)).into(), - ASSIGN_STMT => Stmt::Assign(AssignStmt(syntax)).into(), - RETURN_STMT => Stmt::Return(ReturnStmt(syntax)).into(), - EXPR_STMT => Stmt::Expr(ExprStmt(syntax)).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - match self { - Stmt::Val(val) => val.syntax(), - Stmt::Assign(assign) => assign.syntax(), - Stmt::Return(return_stmt) => return_stmt.syntax(), - Stmt::Expr(expr) => expr.syntax(), - } - } -} - -/// Value statement node. -pub struct Val<'db>(SyntaxNode<'db>); - -impl<'db> Val<'db> { - /// Returns `true` when this statement uses `var`. - pub fn is_mutable(&self) -> bool { - first_non_trivia_token(&self.0).is_some_and(|token| token.kind() == VAR_KW) - } - - /// Returns the binding pattern. - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the optional type annotation. - pub fn ty(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the initializer expression. - pub fn expr(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for Val<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - VAL_STMT => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Assignment statement node. -pub struct AssignStmt<'db>(SyntaxNode<'db>); - -impl<'db> AssignStmt<'db> { - pub fn target(&self) -> Option> { - child(self.syntax()) - } - - pub fn expr(&self) -> Option> { - self.syntax().children().filter_map(Expr::cast).nth(1) - } -} - -impl<'db> Node<'db> for AssignStmt<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ASSIGN_STMT => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Return statement node. -pub struct ReturnStmt<'db>(SyntaxNode<'db>); - -impl<'db> ReturnStmt<'db> { - /// Returns the optional return value. - pub fn expr(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for ReturnStmt<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - RETURN_STMT => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Expression statement node. -pub struct ExprStmt<'db>(SyntaxNode<'db>); - -impl<'db> ExprStmt<'db> { - /// Returns the inner expression. - pub fn expr(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the trailing semicolon token, if any. - pub fn semi(&self) -> Option> { - self.syntax() - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .find(|token| token.kind() == SEMICOLON) - } -} - -impl<'db> Node<'db> for ExprStmt<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - EXPR_STMT => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Expression node. -pub enum Expr<'db> { - Path(Path<'db>), - Field(FieldExpr<'db>), - Literal(Literal<'db>), - Paren(ParenExpr<'db>), - Tuple(TupleExpr<'db>), - Array(ArrayExpr<'db>), - BinOpSeq(BinOpSeq<'db>), - Postfix(Postfix<'db>), - Prefix(Prefix<'db>), - Loop(LoopExpr<'db>), - Break(BreakExpr<'db>), - Continue(ContinueExpr<'db>), - If(IfExpr<'db>), - Match(MatchExpr<'db>), - Unsafe(UnsafeExpr<'db>), - Closure(Closure<'db>), - Call(CallExpr<'db>), - Struct(StructExpr<'db>), -} - -impl<'db> Node<'db> for Expr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PATH_EXPR => Expr::Path(Path(syntax)).into(), - FIELD_EXPR => Expr::Field(FieldExpr(syntax)).into(), - LITERAL => Expr::Literal(Literal(syntax)).into(), - PAREN_EXPR => Expr::Paren(ParenExpr(syntax)).into(), - BIN_OP_SEQ => Expr::BinOpSeq(BinOpSeq(syntax)).into(), - ARRAY_EXPR => Expr::Array(ArrayExpr(syntax)).into(), - POSTFIX_EXPR => Expr::Postfix(Postfix(syntax)).into(), - PREFIX_EXPR => Expr::Prefix(Prefix(syntax)).into(), - LOOP_EXPR => Expr::Loop(LoopExpr(syntax)).into(), - BREAK_EXPR => Expr::Break(BreakExpr(syntax)).into(), - CONTINUE_EXPR => Expr::Continue(ContinueExpr(syntax)).into(), - IF_EXPR => Expr::If(IfExpr(syntax)).into(), - MATCH_EXPR => Expr::Match(MatchExpr(syntax)).into(), - UNSAFE_EXPR => Expr::Unsafe(UnsafeExpr(syntax)).into(), - CLOSURE_EXPR => Expr::Closure(Closure(syntax)).into(), - CALL_EXPR => Expr::Call(CallExpr(syntax)).into(), - TUPLE_EXPR => Expr::Tuple(TupleExpr(syntax)).into(), - STRUCT_EXPR => Expr::Struct(StructExpr(syntax)).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - match self { - Expr::Path(path) => path.syntax(), - Expr::Field(field) => field.syntax(), - Expr::Literal(literal) => &literal.0, - Expr::Paren(paren_expr) => paren_expr.syntax(), - Expr::Array(array_expr) => array_expr.syntax(), - Expr::BinOpSeq(seq) => &seq.0, - Expr::Postfix(postfix) => &postfix.0, - Expr::Prefix(prefix) => &prefix.0, - Expr::Loop(loop_expr) => loop_expr.syntax(), - Expr::Break(break_expr) => break_expr.syntax(), - Expr::Continue(continue_expr) => continue_expr.syntax(), - Expr::If(if_) => if_.syntax(), - Expr::Match(match_expr) => match_expr.syntax(), - Expr::Unsafe(unsafe_expr) => unsafe_expr.syntax(), - Expr::Closure(closure) => closure.syntax(), - Expr::Call(call) => call.syntax(), - Expr::Tuple(tuple_expr) => tuple_expr.syntax(), - Expr::Struct(struct_expr) => struct_expr.syntax(), - } - } -} - -/// Path expression node. -pub struct Path<'db>(SyntaxNode<'db>); - -impl<'db> Path<'db> { - pub fn path_text(&self) -> String { - path_text_without_generic_args(&self.0) - } -} - -impl<'db> Node<'db> for Path<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PATH_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for Path<'db> {} - -/// Field access expression node (`base.field`). -pub struct FieldExpr<'db>(SyntaxNode<'db>); - -impl<'db> FieldExpr<'db> { - /// Returns the base expression. - pub fn expr(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for FieldExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - FIELD_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for FieldExpr<'db> {} - -/// Literal expression node. -pub struct Literal<'db>(SyntaxNode<'db>); - -impl<'db> Literal<'db> { - /// Returns the literal kind derived from the first token. - pub fn kind(&self) -> LiteralKind<'db> { - let token = first_non_trivia_token(&self.0).unwrap(); - - match token.kind() { - INT_NUMBER => LiteralKind::Int(token), - FLOAT_NUMBER => LiteralKind::Float(token), - STRING => LiteralKind::String(token), - CHAR => LiteralKind::Char(token), - kind @ (TRUE_KW | FALSE_KW) => LiteralKind::Bool(kind == TRUE_KW), - _ => unreachable!(), - } - } -} - -/// Parenthesized expression node. -pub struct ParenExpr<'db>(SyntaxNode<'db>); - -impl<'db> ParenExpr<'db> { - /// Returns the wrapped expression. - pub fn expr(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for ParenExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PAREN_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Tuple expression node. -pub struct TupleExpr<'db>(SyntaxNode<'db>); - -impl<'db> TupleExpr<'db> { - /// Iterates tuple items. - pub fn exprs(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Expr::cast) - } -} - -impl<'db> Node<'db> for TupleExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - TUPLE_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Array expression node. -pub struct ArrayExpr<'db>(SyntaxNode<'db>); - -impl<'db> ArrayExpr<'db> { - /// Iterates array items as parsed. - pub fn exprs(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Expr::cast) - } -} - -impl<'db> Node<'db> for ArrayExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ARRAY_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// A flat sequence of interleaved expressions and binary operators. -pub struct BinOpSeq<'db>(SyntaxNode<'db>); - -impl<'db> BinOpSeq<'db> { - /// Iterates through elements (expressions and tokens) in the sequence. - pub fn elements(&self) -> impl Iterator> { - self.0.children_with_tokens() - } -} - -impl<'db> Node<'db> for BinOpSeq<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - BIN_OP_SEQ => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Postfix expression node. -pub struct Postfix<'db>(SyntaxNode<'db>); - -impl<'db> Postfix<'db> { - /// Returns the inner expression. - pub fn expr(&self) -> Option> { - self.0.children().next().and_then(Expr::cast) - } - - /// Returns the operator text, if any. - pub fn op(&self) -> Option<&'db str> { - first_non_trivia_token(&self.0).map(|syntax| syntax.text_trimmed()) - } -} - -/// Prefix expression node. -pub struct Prefix<'db>(SyntaxNode<'db>); - -impl<'db> Prefix<'db> { - /// Returns the operator text, if any. - pub fn op(&self) -> Option<&'db str> { - first_non_trivia_token(&self.0).map(|syntax| syntax.text_trimmed()) - } - - /// Returns the inner expression. - pub fn expr(&self) -> Option> { - self.0.children().next().and_then(Expr::cast) - } -} - -/// If expression node. -pub struct IfExpr<'db>(SyntaxNode<'db>); - -impl<'db> IfExpr<'db> { - /// Returns the condition expression. - pub fn condition(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the then branch block. - pub fn then_block(&self) -> Option> { - self.syntax().children().nth(1).and_then(Block::cast) - } - - /// Returns the else branch block, if any. - pub fn else_block(&self) -> Option> { - self.syntax().children().nth(2).and_then(Block::cast) - } - - /// Returns the nested `else if` branch, if any. - pub fn else_if(&self) -> Option> { - self.syntax().children().nth(2).and_then(IfExpr::cast) - } -} - -/// Unsafe block expression node (`unsafe { ... }`). -pub struct UnsafeExpr<'db>(SyntaxNode<'db>); - -impl<'db> UnsafeExpr<'db> { - pub fn body(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for UnsafeExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - UNSAFE_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Closure expression node. -pub struct Closure<'db>(SyntaxNode<'db>); - -impl<'db> Closure<'db> { - /// Returns the parameter list. - pub fn params(&self) -> Option> { - child(&self.0) - } - - /// Returns the closure body. - pub fn body(&self) -> Block<'db> { - child(self.syntax()).unwrap() - } -} - -impl<'db> Node<'db> for Closure<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - CLOSURE_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Call expression node. -pub struct CallExpr<'db>(SyntaxNode<'db>); - -impl<'db> CallExpr<'db> { - /// Returns the callee expression. - pub fn callee(&self) -> Option> { - child(self.syntax()) - } - - /// Returns the argument list, if any. - pub fn arg_list(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for CallExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - CALL_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Argument list node. -pub struct ArgList<'db> { - syntax: SyntaxNode<'db>, -} - -impl<'db> Node<'db> for ArgList<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ARG_LIST => Some(Self { syntax }), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.syntax - } -} - -impl<'db> ArgList<'db> { - /// Iterates argument expressions. - pub fn args(&self) -> impl Iterator> + '_ { - self.syntax.children().filter_map(Expr::cast) - } -} - -impl<'db> Node<'db> for IfExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - IF_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Match expression node. -pub struct MatchExpr<'db>(SyntaxNode<'db>); - -impl<'db> MatchExpr<'db> { - pub fn scrutinee(&self) -> Option> { - child(self.syntax()) - } - - pub fn arms(&self) -> impl Iterator> + '_ { - self.syntax().children().filter_map(MatchArm::cast) - } -} - -impl<'db> Node<'db> for MatchExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - MATCH_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Match arm node. -pub struct MatchArm<'db>(SyntaxNode<'db>); - -impl<'db> MatchArm<'db> { - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } - - pub fn expr(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for MatchArm<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - MATCH_ARM => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Loop expression node. -pub struct LoopExpr<'db>(SyntaxNode<'db>); - -impl<'db> LoopExpr<'db> { - pub fn body(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for LoopExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - LOOP_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Break expression node. -pub struct BreakExpr<'db>(SyntaxNode<'db>); - -impl<'db> Node<'db> for BreakExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - BREAK_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Continue expression node. -pub struct ContinueExpr<'db>(SyntaxNode<'db>); - -impl<'db> Node<'db> for ContinueExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - CONTINUE_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Concrete literal variants. -pub enum LiteralKind<'db> { - Bool(bool), - Int(SyntaxToken<'db>), - Float(SyntaxToken<'db>), - String(SyntaxToken<'db>), - Char(SyntaxToken<'db>), -} - -/// Identifier node. -pub struct Name<'db>(SyntaxNode<'db>); - -impl<'db> Name<'db> { - /// Returns the name token range. - pub fn text_range(&self) -> TextRange { - first_non_trivia_token(self.syntax()) - .map_or_else(|| self.syntax().text_range(), |token| token.trimmed_range()) - } - - /// Returns the identifier text. - pub fn as_str(&self) -> &'db str { - first_non_trivia_token(self.syntax()).map_or("", |token| token.text_trimmed()) - } -} - -impl<'db> Node<'db> for Name<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - IDENT | NAME_REF => Self(syntax).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Pattern node. -pub enum Pattern<'db> { - Binding(BindingPattern<'db>), - Wildcard(WildcardPattern<'db>), - Literal(LiteralPattern<'db>), - Typed(TypedPattern<'db>), - Paren(ParenPattern<'db>), - Tuple(TuplePattern<'db>), - Variant(VariantPattern<'db>), - Struct(StructPattern<'db>), -} - -impl<'db> Node<'db> for Pattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - BINDING_PATTERN => Pattern::Binding(BindingPattern(syntax)).into(), - WILDCARD_PATTERN => Pattern::Wildcard(WildcardPattern(syntax)).into(), - LITERAL_PATTERN => Pattern::Literal(LiteralPattern(syntax)).into(), - TYPED_PATTERN => Pattern::Typed(TypedPattern(syntax)).into(), - PAREN_PATTERN => Pattern::Paren(ParenPattern(syntax)).into(), - TUPLE_PATTERN => Pattern::Tuple(TuplePattern(syntax)).into(), - VARIANT_PATTERN => Pattern::Variant(VariantPattern(syntax)).into(), - STRUCT_PATTERN => Pattern::Struct(StructPattern(syntax)).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - match self { - Pattern::Binding(binding) => binding.syntax(), - Pattern::Wildcard(wildcard) => wildcard.syntax(), - Pattern::Literal(literal) => literal.syntax(), - Pattern::Typed(typed) => typed.syntax(), - Pattern::Paren(paren) => paren.syntax(), - Pattern::Tuple(tuple) => tuple.syntax(), - Pattern::Variant(variant) => variant.syntax(), - Pattern::Struct(struct_pattern) => struct_pattern.syntax(), - } - } -} - -/// Binding pattern node. -pub struct BindingPattern<'db>(SyntaxNode<'db>); - -impl<'db> BindingPattern<'db> { - pub fn name(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for BindingPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - BINDING_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Wildcard pattern node. -pub struct WildcardPattern<'db>(SyntaxNode<'db>); - -impl<'db> Node<'db> for WildcardPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - WILDCARD_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Literal pattern node. -pub struct LiteralPattern<'db>(SyntaxNode<'db>); - -impl<'db> LiteralPattern<'db> { - pub fn kind(&self) -> LiteralKind<'db> { - let token = first_non_trivia_token(&self.0).unwrap(); - - match token.kind() { - INT_NUMBER => LiteralKind::Int(token), - FLOAT_NUMBER => LiteralKind::Float(token), - STRING => LiteralKind::String(token), - CHAR => LiteralKind::Char(token), - kind @ (TRUE_KW | FALSE_KW) => LiteralKind::Bool(kind == TRUE_KW), - _ => unreachable!(), - } - } -} - -impl<'db> Node<'db> for LiteralPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - LITERAL_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Typed pattern node (`name: Type` or `_: Type`) in `match`. -pub struct TypedPattern<'db>(SyntaxNode<'db>); - -impl<'db> TypedPattern<'db> { - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } - - pub fn ty(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for TypedPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - TYPED_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Parenthesized pattern node. -pub struct ParenPattern<'db>(SyntaxNode<'db>); - -impl<'db> ParenPattern<'db> { - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for ParenPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PAREN_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Tuple pattern node. -pub struct TuplePattern<'db>(SyntaxNode<'db>); - -impl<'db> TuplePattern<'db> { - pub fn patterns(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Pattern::cast) - } -} - -impl<'db> Node<'db> for TuplePattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - TUPLE_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Variant pattern node. -pub struct VariantPattern<'db>(SyntaxNode<'db>); - -impl<'db> VariantPattern<'db> { - pub fn path(&self) -> Option> { - child(self.syntax()) - } - - pub fn patterns(&self) -> impl Iterator> + '_ { - self.syntax().children().filter_map(Pattern::cast) - } -} - -impl<'db> Node<'db> for VariantPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - VARIANT_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Struct pattern node. -pub struct StructPattern<'db>(SyntaxNode<'db>); - -impl<'db> StructPattern<'db> { - pub fn path(&self) -> Option> { - child(self.syntax()) - } - - pub fn fields(&self) -> impl Iterator> + '_ { - self.syntax().children().filter_map(StructPatternField::cast) - } -} - -impl<'db> Node<'db> for StructPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Struct pattern field node. -pub struct StructPatternField<'db>(SyntaxNode<'db>); - -impl<'db> StructPatternField<'db> { - pub fn pattern(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for StructPatternField<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_PATTERN_FIELD => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for StructPatternField<'db> {} - -/// Pattern path node. -pub struct PathPattern<'db>(SyntaxNode<'db>); - -impl<'db> Node<'db> for PathPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PATH_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for PathPattern<'db> {} - -/// Pattern field path node. -pub struct FieldPattern<'db>(SyntaxNode<'db>); - -impl<'db> FieldPattern<'db> { - pub fn base(&self) -> Option> { - child(self.syntax()) - } -} - -impl<'db> Node<'db> for FieldPattern<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - FIELD_PATTERN => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for FieldPattern<'db> {} - -/// Type node. -pub enum Type<'db> { - Path(PathType<'db>), - Array(ArrayType<'db>), - Tuple(TupleType<'db>), - Function(FunctionType<'db>), - Union(UnionType<'db>), - Inter(InterType<'db>), - Record(RecordType<'db>), - Pointer(PointerType<'db>), -} - -impl<'db> Node<'db> for Type<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PATH_TYPE => Type::Path(PathType(syntax)).into(), - ARRAY_TYPE => Type::Array(ArrayType(syntax)).into(), - TUPLE_TYPE => Type::Tuple(TupleType(syntax)).into(), - FUNCTION_TYPE => Type::Function(FunctionType(syntax)).into(), - UNION_TYPE => Type::Union(UnionType(syntax)).into(), - INTER_TYPE => Type::Inter(InterType(syntax)).into(), - RECORD_TYPE => Type::Record(RecordType(syntax)).into(), - POINTER_TYPE => Type::Pointer(PointerType(syntax)).into(), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - match self { - Type::Path(path_type) => &path_type.0, - Type::Array(array_type) => &array_type.0, - Type::Tuple(tuple_type) => &tuple_type.0, - Type::Function(function_type) => &function_type.0, - Type::Union(union_type) => &union_type.0, - Type::Inter(inter_type) => &inter_type.0, - Type::Record(record_type) => &record_type.0, - Type::Pointer(pointer_type) => &pointer_type.0, - } - } -} - -/// Raw pointer type node (`*const T` or `*mut T`). -pub struct PointerType<'db>(SyntaxNode<'db>); - -impl<'db> PointerType<'db> { - pub fn is_mut(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .any(|token| token.kind() == NAME && token.text_trimmed() == "mut") - } - - pub fn pointee(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for PointerType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - POINTER_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Path type node. -pub struct PathType<'db>(SyntaxNode<'db>); - -impl<'db> PathType<'db> { - pub fn path_text(&self) -> String { - path_text_without_generic_args(&self.0) - } - - pub fn arg_list(&self) -> Option> { - child(&self.0) - } - - pub fn type_args(&self) -> Vec> { - self.arg_list().map(|list| list.types().collect()).unwrap_or_default() - } -} - -impl<'db> Node<'db> for PathType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - PATH_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for PathType<'db> {} - -/// Array type node (`[T]`). -pub struct ArrayType<'db>(SyntaxNode<'db>); - -impl<'db> ArrayType<'db> { - pub fn item(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for ArrayType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ARRAY_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Tuple type node. -pub struct TupleType<'db>(SyntaxNode<'db>); - -impl<'db> TupleType<'db> { - /// Iterates types in the tuple type. - pub fn types(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(Type::cast) - } -} - -impl<'db> Node<'db> for TupleType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - TUPLE_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Function type node (`fun(T1, ...) -> Out`). -pub struct FunctionType<'db>(SyntaxNode<'db>); - -impl<'db> FunctionType<'db> { - pub fn inputs(&self) -> Option> { - child(&self.0) - } - - pub fn output(&self) -> Option> { - self.0.children().nth(1).and_then(Type::cast) - } -} - -impl<'db> Node<'db> for FunctionType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - FUNCTION_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Union type node (`A | B`). -pub struct UnionType<'db>(SyntaxNode<'db>); - -impl<'db> UnionType<'db> { - pub fn lhs(&self) -> Option> { - child(&self.0) - } - - pub fn rhs(&self) -> Option> { - self.0.children().nth(1).and_then(Type::cast) - } -} - -impl<'db> Node<'db> for UnionType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - UNION_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Intersection type node (`A & B`). -pub struct InterType<'db>(SyntaxNode<'db>); - -impl<'db> InterType<'db> { - pub fn lhs(&self) -> Option> { - child(&self.0) - } - - pub fn rhs(&self) -> Option> { - self.0.children().nth(1).and_then(Type::cast) - } -} - -impl<'db> Node<'db> for InterType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - INTER_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Structural record type (`{ field: Ty, ... }`). -pub struct RecordType<'db>(SyntaxNode<'db>); - -impl<'db> RecordType<'db> { - pub fn fields(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(StructField::cast) - } -} - -impl<'db> Node<'db> for RecordType<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - RECORD_TYPE => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Returns the first child node matching `N`. -fn child<'db, N: Node<'db>>(parent: &SyntaxNode<'db>) -> Option { - parent.children().find_map(N::cast) -} - -/// Returns the first non-trivia token among the node's children. -fn first_non_trivia_token<'db>(node: &SyntaxNode<'db>) -> Option> { - node.children_with_tokens().find_map(|child| { - let token = child.into_token()?; - if token.is_trivia() { None } else { Some(token) } - }) -} - -fn path_text_without_generic_args(node: &SyntaxNode<'_>) -> String { - let mut text = String::new(); - - for child in node.children_with_tokens() { - match child { - SyntaxElement::Node(node) if node.kind() == GENERIC_ARG_LIST => break, - SyntaxElement::Token(token) if !token.is_trivia() => { - text.push_str(token.text_trimmed()) - } - SyntaxElement::Node(_) | SyntaxElement::Token(_) => {} - } - } - - text -} - -/// Struct definition node. -pub struct StructDef<'db>(SyntaxNode<'db>); - -impl<'db> StructDef<'db> { - pub fn is_extern(&self) -> bool { - self.0 - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .filter(|token| !token.is_trivia()) - .take_while(|token| token.kind() != STRUCT_KW) - .any(|token| token.kind() == NAME && token.text_trimmed() == "extern") - } - - pub fn type_params(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(TypeParam::cast) - } - - pub fn field_list(&self) -> Option> { - child(&self.0) - } - - pub fn destructor(&self) -> Option> { - self.field_list()?.destructors().next() - } -} - -impl<'db> Node<'db> for StructDef<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_DEF => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for StructDef<'db> {} - -/// Struct field list node. -pub struct StructFieldList<'db>(SyntaxNode<'db>); - -impl<'db> StructFieldList<'db> { - pub fn fields(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(StructField::cast) - } - - pub fn destructors(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(DestructorDef::cast) - } -} - -impl<'db> Node<'db> for StructFieldList<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_FIELD_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Struct field node. -pub struct StructField<'db>(SyntaxNode<'db>); - -impl<'db> StructField<'db> { - pub fn ty(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for StructField<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_FIELD => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for StructField<'db> {} - -/// Enum definition node. -pub struct EnumDef<'db>(SyntaxNode<'db>); - -impl<'db> EnumDef<'db> { - pub fn type_params(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(TypeParam::cast) - } - - pub fn variant_list(&self) -> Option> { - child(&self.0) - } - - pub fn destructor(&self) -> Option> { - self.variant_list()?.destructors().next() - } -} - -impl<'db> Node<'db> for EnumDef<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ENUM_DEF => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for EnumDef<'db> {} - -/// Enum variant list node. -pub struct EnumVariantList<'db>(SyntaxNode<'db>); - -impl<'db> EnumVariantList<'db> { - pub fn variants(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(EnumVariant::cast) - } - - pub fn destructors(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(DestructorDef::cast) - } -} - -impl<'db> Node<'db> for EnumVariantList<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ENUM_VARIANT_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Enum variant node. -pub struct EnumVariant<'db>(SyntaxNode<'db>); - -impl<'db> EnumVariant<'db> { - pub fn field_types(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for EnumVariant<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - ENUM_VARIANT => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for EnumVariant<'db> {} - -/// Destructor member node inside a nominal type. -pub struct DestructorDef<'db>(SyntaxNode<'db>); - -impl<'db> DestructorDef<'db> { - pub fn params(&self) -> Option> { - child(&self.0) - } - - pub fn body(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for DestructorDef<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - DESTRUCTOR_DEF => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -fn trim_string_quotes(text: &str) -> &str { - text.strip_prefix('"').and_then(|text| text.strip_suffix('"')).unwrap_or(text) -} - -/// Struct expression node (e.g. `Point { x: 1, y: 2 }`). -pub struct StructExpr<'db>(SyntaxNode<'db>); - -impl<'db> StructExpr<'db> { - pub fn path(&self) -> Option> { - child(&self.0) - } - - pub fn field_list(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for StructExpr<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_EXPR => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Struct expression field list node. -pub struct StructExprFieldList<'db>(SyntaxNode<'db>); - -impl<'db> StructExprFieldList<'db> { - pub fn fields(&self) -> impl Iterator> + '_ { - self.0.children().filter_map(StructExprField::cast) - } -} - -impl<'db> Node<'db> for StructExprFieldList<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_EXPR_FIELD_LIST => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -/// Struct expression field node. -pub struct StructExprField<'db>(SyntaxNode<'db>); - -impl<'db> StructExprField<'db> { - pub fn expr(&self) -> Option> { - child(&self.0) - } -} - -impl<'db> Node<'db> for StructExprField<'db> { - fn cast(syntax: SyntaxNode<'db>) -> Option { - match syntax.kind() { - STRUCT_EXPR_FIELD => Some(Self(syntax)), - _ => None, - } - } - - fn syntax(&self) -> &SyntaxNode<'db> { - &self.0 - } -} - -impl<'db> HasName<'db> for StructExprField<'db> {} diff --git a/crates/mitki-yellow/src/builder.rs b/crates/mitki-yellow/src/builder.rs deleted file mode 100644 index 683717d..0000000 --- a/crates/mitki-yellow/src/builder.rs +++ /dev/null @@ -1,416 +0,0 @@ -//! Incremental builder for the immutable syntax tree. - -use std::marker::PhantomData; -use std::mem::{ManuallyDrop, MaybeUninit}; -use std::ptr::null; - -use text_size::TextSize; - -use crate::maybe_dangling::MaybeDangling; -use crate::nodes::{AttachedTrivia, TreeInner}; -use crate::{SyntaxKind, SyntaxTree, TriviaPiece, TriviaPieceKind}; - -struct Node { - parent: Option, - /// A `ManuallyDrop` so that `Node` doesn't have a `Drop` glue. - children: ManuallyDrop>, - children_ptr: usize, - children_len: u32, - kind: SyntaxKind, - first_last_token: Option<(u32, u32)>, -} - -struct List { - children: ManuallyDrop>, - children_ptr: usize, - children_len: usize, -} - -enum ChildKind { - Token(usize), - Node(usize), - List(usize), -} - -struct Token { - kind: SyntaxKind, - attached_trivia: AttachedTrivia, - end: TextSize, - parent: usize, -} - -/// Builds a `SyntaxTree` from parser events. -pub struct Builder<'db> { - nodes: Vec, - node_children: Vec, - lists: Vec, - list_children: Vec, - tokens: Vec, - text: Box, - _marker: PhantomData<&'db ()>, - - node_children_pool: Vec>, - list_children_pool: Vec>, - opened: Vec, - text_len: TextSize, - last_token_index: u32, -} - -#[derive(Debug, Clone, Copy)] -enum Opened { - Node(usize), - List(usize), -} - -impl Drop for Builder<'_> { - fn drop(&mut self) { - if !std::thread::panicking() && !self.opened.is_empty() { - panic!("you should call `Builder::finish()`"); - } - } -} - -const DEFAULT_TREE_DEPTH: usize = 128; -const DEFAULT_TREE_SIZE: usize = 1024; -const DEFAULT_CHILDREN_LEN: usize = 10; - -impl<'db> Builder<'db> { - /// Creates a new builder for `text`. - /// - /// The internal token buffer is seeded with a fake token at index 0 to make - /// token ranges uniform. - pub fn new(text: &str) -> Self { - let mut tokens = Vec::with_capacity(DEFAULT_TREE_SIZE); - tokens.push(Token { - kind: SyntaxKind::TOMBSTONE, - attached_trivia: AttachedTrivia::new(false, false, 0), - end: TextSize::new(0), - parent: 0, - }); - Self { - nodes: Vec::with_capacity(DEFAULT_TREE_SIZE), - node_children: Vec::with_capacity(DEFAULT_TREE_SIZE), - lists: Vec::with_capacity(DEFAULT_TREE_SIZE), - list_children: Vec::with_capacity(DEFAULT_TREE_SIZE), - tokens, - text: text.into(), - _marker: PhantomData, - - node_children_pool: Vec::with_capacity(DEFAULT_TREE_DEPTH), - list_children_pool: Vec::with_capacity(DEFAULT_TREE_DEPTH), - opened: Vec::with_capacity(DEFAULT_TREE_DEPTH), - text_len: TextSize::new(0), - last_token_index: 0, - } - } - - /// Retrieves a recycled node-children buffer or allocates a new one. - fn new_node_children_vec(&mut self) -> Vec { - self.node_children_pool.pop().unwrap_or_else(|| Vec::with_capacity(DEFAULT_CHILDREN_LEN)) - } - - /// Returns a node-children buffer to the pool. - fn recycle_node_children_vec(&mut self, vec: Vec) { - self.node_children_pool.push(vec); - } - - /// Retrieves a recycled list-children buffer or allocates a new one. - fn new_list_children_vec(&mut self) -> Vec { - self.list_children_pool.pop().unwrap_or_else(|| Vec::with_capacity(DEFAULT_CHILDREN_LEN)) - } - - /// Returns a list-children buffer to the pool. - fn recycle_list_children_vec(&mut self, vec: Vec) { - self.list_children_pool.push(vec); - } - - /// Returns the most recently opened node or list. - fn last_opened(&self) -> Opened { - *self.opened.last().expect("no opened nodes?") - } - - /// Returns the most recently opened node or panics if it's a list. - #[track_caller] - fn expect_last_opened_node(&self) -> usize { - match self.last_opened() { - Opened::Node(it) => it, - Opened::List(_) => panic!("expected an opened node, found an opened list"), - } - } - - /// Starts a new node of the given kind. - pub fn start_node(&mut self, kind: SyntaxKind) { - let parent = self.last_parent(); - let new_node = self.nodes.len(); - let new_children = self.new_node_children_vec(); - self.nodes.push(Node { - parent, - children: ManuallyDrop::new(new_children), - children_ptr: 0, - children_len: 0, - kind, - first_last_token: None, - }); - match self.last_opened_opt() { - Some(Opened::Node(direct_parent)) => { - self.nodes[direct_parent].children.push(ChildKind::Node(new_node)) - } - Some(Opened::List(direct_parent)) => self.lists[direct_parent].children.push(new_node), - None => { - // Root node. - } - } - self.opened.push(Opened::Node(new_node)); - } - - /// Returns the most recent opened node index, skipping lists. - fn last_parent(&mut self) -> Option { - self.opened.iter().rev().find_map(|opened| match *opened { - Opened::Node(node) => Some(node), - Opened::List(_) => None, - }) - } - - /// Returns the last opened entry, if any. - fn last_opened_opt(&self) -> Option { - self.opened.last().copied() - } - - /// Finishes the most recently started node. - pub fn finish_node(&mut self) { - let node = self.expect_last_opened_node(); - self.opened.pop(); - let node = &mut self.nodes[node]; - if node.first_last_token.is_none() { - node.first_last_token = Some((self.last_token_index, self.last_token_index)); - } - let mut children = std::mem::take(&mut *node.children); - node.children_ptr = self.node_children.len(); - node.children_len = children.len().try_into().unwrap(); - self.node_children.append(&mut children); - self.recycle_node_children_vec(children); - } - - /// Starts a list under the current node. - pub fn start_list(&mut self) { - let parent = self.expect_last_opened_node(); - let new_list = self.lists.len(); - let new_children = self.new_list_children_vec(); - self.lists.push(List { - children: ManuallyDrop::new(new_children), - children_ptr: 0, - children_len: 0, - }); - self.nodes[parent].children.push(ChildKind::List(new_list)); - self.opened.push(Opened::List(new_list)); - } - - /// Finishes the most recently started list. - pub fn finish_list(&mut self) { - let Opened::List(list) = self.last_opened() else { - panic!("expected an opened list, found an opened node"); - }; - self.opened.pop(); - let list = &mut self.lists[list]; - let mut children = std::mem::take(&mut *list.children); - list.children_ptr = self.list_children.len(); - list.children_len = children.len(); - self.list_children.append(&mut children); - self.recycle_list_children_vec(children); - } - - /// Adds a token with its leading and trailing trivia. - pub fn token( - &mut self, - leading_trivia: impl ExactSizeIterator, - kind: SyntaxKind, - text_len: TextSize, - trailing_trivia: impl ExactSizeIterator, - ) { - let parent = self.expect_last_opened_node(); - let (leading_trivia_len, leading_trivia) = ensure_exact_size_iter(leading_trivia); - let (trailing_trivia_len, trailing_trivia) = ensure_exact_size_iter(trailing_trivia); - let mut push_text_len = |text_len| { - self.text_len += text_len; - assert!(self.text.is_char_boundary(usize::from(self.text_len))); - self.text_len - }; - let first_token = self.tokens.len(); - self.tokens.extend(leading_trivia.map(|piece| Token { - kind: trivia_piece_kind(piece.kind), - attached_trivia: AttachedTrivia::new(false, false, 0), - end: push_text_len(piece.len), - parent, - })); - let token = self.tokens.len(); - self.tokens.push(Token { - kind, - attached_trivia: AttachedTrivia::new( - leading_trivia_len != 0, - trailing_trivia_len != 0, - leading_trivia_len, - ), - end: push_text_len(text_len), - parent, - }); - self.nodes[parent].children.push(ChildKind::Token(token)); - self.tokens.extend(trailing_trivia.map(|piece| Token { - kind: trivia_piece_kind(piece.kind), - attached_trivia: AttachedTrivia::new(false, false, trailing_trivia_len), - end: push_text_len(piece.len), - parent, - })); - let last_token = first_token + leading_trivia_len + trailing_trivia_len; - - self.update_first_last_tokens(first_token, last_token); - self.last_token_index = last_token.try_into().unwrap(); - } - - /// Updates token ranges for all open ancestor nodes. - fn update_first_last_tokens(&mut self, first_token: usize, last_token: usize) { - // Walk ancestors, update first and last token. - let first_token = first_token.try_into().unwrap(); - let last_token = last_token.try_into().unwrap(); - for ancestor in &self.opened { - let Opened::Node(node) = *ancestor else { continue }; - let node = &mut self.nodes[node]; - match &mut node.first_last_token { - // First token inside this node, so also first and last token. - None => node.first_last_token = Some((first_token, last_token)), - // There was already a token. It is the first token, but we're after it so (maybe) - // we're last. - Some((_f, l)) => *l = last_token, - } - } - } - - /// Finishes building and returns the immutable `SyntaxTree`. - pub fn finish(self) -> SyntaxTree { - assert!(self.opened.is_empty()); - assert!(!self.nodes.is_empty()); - let tree = self.finish_impl(); - SyntaxTree { tree } - } - - /// Finalizes buffers into stable tree storage. - #[expect(unsafe_code)] - fn finish_impl(mut self) -> TreeInner { - // Important: The addresses here must be stable, so we must preallocate. - let mut tokens = new_boxed_slice(&self.tokens); - let mut nodes = new_boxed_slice(&self.nodes); - let mut node_children = new_boxed_slice(&self.node_children); - let mut lists = new_boxed_slice(&self.lists); - let mut list_children = new_boxed_slice(&self.list_children); - - create_boxed_slice(&mut tokens, &self.tokens, |token, _| crate::nodes::Token { - kind: token.kind, - attached_trivia: token.attached_trivia, - end: token.end, - parent: ptr(&mut nodes, token.parent), - }); - create_boxed_slice(&mut nodes, &self.nodes, |node, nodes| { - let (first_token, last_token) = node.first_last_token.expect("node without tokens"); - crate::nodes::Node { - parent: node.parent.map_or_else(null, |parent| ptr(nodes, parent)), - children: ptr(&mut node_children, node.children_ptr), - children_len: node.children_len, - kind: node.kind, - first_token, - last_token, - } - }); - create_boxed_slice(&mut node_children, &self.node_children, |child, _| match *child { - ChildKind::Token(it) => { - crate::nodes::NodeOrListOrToken::new_token(ptr(&mut tokens, it)) - } - ChildKind::Node(it) => crate::nodes::NodeOrListOrToken::new_node(ptr(&mut nodes, it)), - ChildKind::List(it) => crate::nodes::NodeOrListOrToken::new_list(ptr(&mut lists, it)), - }); - create_boxed_slice(&mut lists, &self.lists, |list, _| crate::nodes::List { - children: std::ptr::slice_from_raw_parts( - ptr(&mut list_children, list.children_ptr), - list.children_len, - ), - }); - create_boxed_slice(&mut list_children, &self.list_children, |child, _| { - ptr(&mut nodes, *child) - }); - - let tree = unsafe { - TreeInner { - text: MaybeDangling::new(std::mem::take(&mut self.text)), - tokens: tokens.assume_init(), - nodes: crate::nodes::Nodes { - nodes: nodes.assume_init(), - node_children: node_children.assume_init(), - lists: lists.assume_init(), - list_children: list_children.assume_init(), - }, - } - }; - self.opened.clear(); - tree - } -} - -/// Returns a stable pointer to an element in the output buffer. -#[inline] -#[expect(unsafe_code)] -fn ptr(out: &mut MaybeDangling]>>, index: usize) -> *const T { - // Read the boxed slice pointer directly to avoid creating references into - // uninitialized memory (which Miri flags under Stacked Borrows). - let slice_ptr = unsafe { *out.as_mut_ptr().cast::<*mut [MaybeUninit]>() }; - if slice_ptr.len() == 0 { - debug_assert_eq!(index, 0); - return std::ptr::dangling(); - } - debug_assert!(index < slice_ptr.len()); - let data_ptr = slice_ptr.cast::>(); - unsafe { data_ptr.add(index).cast::() } -} - -/// Allocates an uninitialized boxed slice with the same length as `input`. -#[inline] -fn new_boxed_slice(input: &[impl Sized]) -> MaybeDangling]>> { - MaybeDangling::new(Box::new_uninit_slice(input.len())) -} - -/// Maps `input` into the output buffer using `mapper`. -#[expect(unsafe_code)] -fn create_boxed_slice( - out: &mut MaybeDangling]>>, - input: &[T], - mut mapper: impl FnMut(&T, &mut MaybeDangling]>>) -> U, -) { - let out_ptr = unsafe { &raw mut **out.as_mut_ptr() }; - assert_eq!(out_ptr.len(), input.len()); - let mut out_ptr = out_ptr.cast::>(); - for item in input { - unsafe { out_ptr.write(MaybeUninit::new(mapper(item, out))) }; - out_ptr = unsafe { out_ptr.add(1) }; - } -} - -/// Ensures an `ExactSizeIterator` has at least as reported items, for safety. -/// -/// Note: you must use the returned len, not a len you saved from before -/// (otherwise a malicious iterator could return different lengths). -#[inline] -fn ensure_exact_size_iter( - mut iter: impl ExactSizeIterator, -) -> (usize, impl Iterator) { - let len = iter.len(); - let iter = (0..len) - .map(move |_| iter.next().unwrap_or_else(|| panic!("iter should have {len} items"))); - (len, iter) -} - -/// Maps trivia piece kinds to syntax kinds. -#[inline] -fn trivia_piece_kind(kind: TriviaPieceKind) -> SyntaxKind { - match kind { - TriviaPieceKind::Whitespace => SyntaxKind::WHITESPACE, - TriviaPieceKind::Newline => SyntaxKind::NEWLINE, - TriviaPieceKind::SingleLineComment => SyntaxKind::LINE_COMMENT, - } -} diff --git a/crates/mitki-yellow/src/lib.rs b/crates/mitki-yellow/src/lib.rs deleted file mode 100644 index a5f53ab..0000000 --- a/crates/mitki-yellow/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Lossless, immutable syntax tree with parent pointers and attached trivia. -//! -//! The tree is built once and then navigated by offset-based, lifetime-guided -//! handles without allocation or refcounting. - -/// Typed AST wrappers around the raw syntax tree. -pub mod ast; -mod builder; -mod maybe_dangling; -mod nodes; -mod syntax; -mod syntax_kind; -mod syntax_set; -mod trivia; - -/// Incremental builder for constructing a `SyntaxTree`. -pub use builder::Builder; -/// Primary syntax tree API types and adapters. -pub use syntax::{ - NodeOrToken, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, SyntaxTree, TokenAtOffset, -}; -/// Token and node kinds used throughout the tree. -pub use syntax_kind::SyntaxKind; -/// Compact set for grouping `SyntaxKind` values. -pub use syntax_set::SyntaxSet; -/// Trivia pieces attached to tokens. -pub use trivia::{TriviaPiece, TriviaPieceKind}; diff --git a/crates/mitki-yellow/src/maybe_dangling.rs b/crates/mitki-yellow/src/maybe_dangling.rs deleted file mode 100644 index 5f63770..0000000 --- a/crates/mitki-yellow/src/maybe_dangling.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! `MaybeDangling` as specified in , -//! polyfilled via `MaybeUninit`. - -use std::mem::MaybeUninit; -use std::ops::Deref; - -/// A wrapper that keeps a value initialized but allows creating dangling -/// pointers. -/// -/// This matches the semantics proposed in RFC 3336 and is used to build -/// self-referential layouts safely with raw pointers. -#[repr(transparent)] -pub(crate) struct MaybeDangling { - value: MaybeUninit, -} - -impl MaybeDangling { - /// Wraps an initialized value. - #[inline] - pub(crate) const fn new(value: T) -> Self { - Self { value: MaybeUninit::new(value) } - } - - /// Returns a mutable raw pointer to the wrapped value. - #[inline] - pub(crate) fn as_mut_ptr(&mut self) -> *mut T { - self.value.as_mut_ptr() - } -} - -impl Deref for MaybeDangling { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { self.value.assume_init_ref() } - } -} - -impl Drop for MaybeDangling { - #[inline] - fn drop(&mut self) { - unsafe { - self.value.as_mut_ptr().drop_in_place(); - } - } -} - -impl MaybeDangling]>> { - /// Converts a boxed slice of `MaybeUninit` into a boxed slice of `T`. - /// - /// # Safety - /// All elements must be fully initialized. - pub(crate) unsafe fn assume_init(self) -> MaybeDangling> { - // SAFETY: The layouts match, we are `#[repr(transparent)]`, `Box` has the - // layout of a pointer, and `MaybeUninit` is `#[repr(transparent)]`. - unsafe { std::mem::transmute::>>(self) } - } -} diff --git a/crates/mitki-yellow/src/nodes.rs b/crates/mitki-yellow/src/nodes.rs deleted file mode 100644 index 7851d31..0000000 --- a/crates/mitki-yellow/src/nodes.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Low-level tree storage with tagged pointers and raw slices. - -mod node; -mod token; -mod tree; - -pub(crate) use node::{ChildKind, List, Node, NodeOrListOrToken, Nodes}; -pub(crate) use token::{AttachedTrivia, Token, TokenRef, TokenRefIter}; -pub(crate) use tree::TreeInner; diff --git a/crates/mitki-yellow/src/nodes/node.rs b/crates/mitki-yellow/src/nodes/node.rs deleted file mode 100644 index 465add9..0000000 --- a/crates/mitki-yellow/src/nodes/node.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! Node storage and tagged child pointers. -//! -//! Child pointers encode their kind in the low bits, so the pointed-to types -//! are aligned to 4 bytes to keep those bits available. - -use std::marker::PhantomData; - -use text_size::{TextRange, TextSize}; - -use crate::maybe_dangling::MaybeDangling; -use crate::nodes::TreeInner; -use crate::nodes::token::{Token, TokenRef}; -use crate::{NodeOrToken, SyntaxKind, TokenAtOffset}; - -/// Owning storage for all nodes, lists, and child arrays. -/// -/// Raw pointers stored in nodes and tokens are only valid while this storage -/// lives. -#[expect(unused, reason = "the fields are referenced by raw pointers")] -pub(crate) struct Nodes { - pub(crate) nodes: MaybeDangling>, - pub(crate) node_children: MaybeDangling>, - pub(crate) lists: MaybeDangling>, - pub(crate) list_children: MaybeDangling>, -} - -impl Nodes { - /// Returns the root node (always index 0). - #[inline] - pub(crate) fn root(&self) -> &Node { - &self.nodes[0] - } -} - -unsafe impl Send for Nodes {} -unsafe impl Sync for Nodes {} - -/// Raw node stored in the tree arena. -#[repr(align(4))] -pub(crate) struct Node { - pub(crate) parent: *const Self, - pub(crate) children: *const NodeOrListOrToken, - pub(crate) children_len: u32, - pub(crate) kind: SyntaxKind, - pub(crate) first_token: u32, - pub(crate) last_token: u32, -} - -impl Node { - /// Returns the text range covered by this node. - #[inline] - pub(crate) fn text_range(&self, tree: &TreeInner) -> TextRange { - TextRange::new(self.first_token(tree).start(), self.last_token(tree).end()) - } - - /// Returns the node text slice from the backing source. - #[inline] - pub(crate) fn text<'a>(&'a self, tree: &'a TreeInner) -> &'a str { - let range = self.text_range(tree); - unsafe { tree.text.get_unchecked(usize::from(range.start())..usize::from(range.end())) } - } - - /// Returns the first token spanned by this node. - #[inline] - pub(crate) fn first_token(&self, tree: &TreeInner) -> TokenRef<'_> { - TokenRef { - ptr: unsafe { tree.tokens.as_ptr().add(self.first_token as usize) }, - _marker: PhantomData, - } - } - - /// Returns the last token spanned by this node. - #[inline] - pub(crate) fn last_token(&self, tree: &TreeInner) -> TokenRef<'_> { - TokenRef { - ptr: unsafe { tree.tokens.as_ptr().add(self.last_token as usize) }, - _marker: PhantomData, - } - } - - /// Returns the parent node if present. - #[inline] - pub(crate) fn parent(&self) -> Option<&Self> { - unsafe { self.parent.as_ref() } - } - - /// Returns the child slice (nodes, lists, and tokens). - #[inline] - pub(crate) fn children(&self) -> &[NodeOrListOrToken] { - unsafe { std::slice::from_raw_parts(self.children, self.children_len as usize) } - } - - /// Returns the contiguous token slice covered by this node. - #[inline] - fn tokens_range<'a>(&self, tree: &'a TreeInner) -> &'a [Token] { - let start = self.first_token(tree).ptr; - let len = self.last_token - self.first_token; - unsafe { std::slice::from_raw_parts(start, len as usize) } - } - - /// Finds the token at the given offset within this node. - #[inline] - pub(crate) fn token_at_offset<'a>( - &self, - tree: &'a TreeInner, - offset: TextSize, - ) -> TokenAtOffset> { - let tokens_range = self.tokens_range(tree); - let index = tokens_range.partition_point(|token| token.end <= offset); - if index >= tree.tokens.len() { - return TokenAtOffset::None; - } - let second_token = unsafe { tree.tokens.as_ptr().add(self.first_token as usize + index) }; - let second_token = TokenRef { ptr: second_token, _marker: PhantomData }; - if second_token.end() <= offset { - return TokenAtOffset::None; - } - if let Some(first_token) = second_token.prev_token(tree) - && first_token.end() == offset - { - TokenAtOffset::Between(first_token, second_token) - } else { - TokenAtOffset::Single(second_token) - } - } - - /// Returns the smallest element that fully covers `range`. - #[inline] - pub(crate) fn covering_element<'a>( - &'a self, - tree: &'a TreeInner, - range: TextRange, - ) -> NodeOrToken<&'a Self, TokenRef<'a>> { - let token = self - .token_at_offset(tree, range.start()) - .right_biased() - .expect("range is not inside the node"); - if token.text_range().contains_range(range) { - return NodeOrToken::Token(token); - } - let mut ancestors = std::iter::successors(Some(token.parent()), |it| it.parent()); - let result = ancestors - .find(|ancestor| ancestor.text_range(tree).contains_range(range)) - .expect("range is not inside the node"); - NodeOrToken::Node(result) - } -} - -/// The typed view of a tagged child pointer. -pub(crate) enum ChildKind<'a> { - Token(TokenRef<'a>), - Node(&'a Node), - List(&'a List), -} - -/// Tagged pointer to either a node, list, or token. -#[derive(Clone, Copy)] -pub(crate) struct NodeOrListOrToken(*const ()); - -impl NodeOrListOrToken { - const TAG_MASK: usize = 0b11; - const PTR_MASK: usize = !Self::TAG_MASK; - const TOKEN_TAG: usize = 0b00; - const NODE_TAG: usize = 0b01; - const LIST_TAG: usize = 0b10; - - /// Builds a tagged pointer from a token pointer. - #[inline] - pub(crate) fn new_token(ptr: *const Token) -> Self { - Self(ptr.map_addr(|addr| addr | Self::TOKEN_TAG).cast()) - } - - /// Builds a tagged pointer from a node pointer. - #[inline] - pub(crate) fn new_node(ptr: *const Node) -> Self { - Self(ptr.map_addr(|addr| addr | Self::NODE_TAG).cast()) - } - - /// Builds a tagged pointer from a list pointer. - #[inline] - pub(crate) fn new_list(ptr: *const List) -> Self { - Self(ptr.map_addr(|addr| addr | Self::LIST_TAG).cast()) - } - - /// Returns the raw pointer with the tag bits cleared. - #[inline] - pub(super) fn untagged_ptr(self) -> *const () { - self.0.map_addr(|addr| addr & Self::PTR_MASK) - } - - /// Returns the token if this child is a token. - #[inline] - #[allow(dead_code)] - pub(crate) fn as_token(&self) -> Option> { - if (self.0.addr() & Self::TAG_MASK) == Self::TOKEN_TAG { - Some(TokenRef { ptr: self.untagged_ptr().cast::(), _marker: PhantomData }) - } else { - None - } - } - - /// Returns the node if this child is a node. - #[inline] - #[allow(dead_code)] - pub(crate) fn as_node(&self) -> Option<&Node> { - if (self.0.addr() & Self::TAG_MASK) == Self::NODE_TAG { - Some(unsafe { &*self.untagged_ptr().cast::() }) - } else { - None - } - } - - /// Returns the list if this child is a list. - #[inline] - #[allow(dead_code)] - pub(crate) fn as_list(&self) -> Option<&List> { - if (self.0.addr() & Self::TAG_MASK) == Self::LIST_TAG { - Some(unsafe { &*self.untagged_ptr().cast::() }) - } else { - None - } - } - - /// Returns the typed child variant for this pointer. - #[inline] - pub(crate) fn kind(&self) -> ChildKind<'_> { - let ptr = self.untagged_ptr(); - match self.0.addr() & Self::TAG_MASK { - Self::TOKEN_TAG => { - ChildKind::Token(TokenRef { ptr: ptr.cast::(), _marker: PhantomData }) - } - Self::NODE_TAG => unsafe { ChildKind::Node(&*ptr.cast::()) }, - Self::LIST_TAG => unsafe { ChildKind::List(&*ptr.cast::()) }, - _ => unsafe { std::hint::unreachable_unchecked() }, - } - } -} - -/// List node containing child nodes only. -#[repr(align(4))] -pub(crate) struct List { - pub(crate) children: *const [*const Node], -} - -impl List { - /// Returns the list children as references. - #[inline] - pub(crate) fn children(&self) -> &[&Node] { - // SAFETY: Layout is equivalent. - unsafe { std::mem::transmute::<&[*const Node], &[&Node]>(&*self.children) } - } -} diff --git a/crates/mitki-yellow/src/nodes/token.rs b/crates/mitki-yellow/src/nodes/token.rs deleted file mode 100644 index fa63e48..0000000 --- a/crates/mitki-yellow/src/nodes/token.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Token storage and trivia attachment helpers. - -use std::marker::PhantomData; - -use text_size::{TextRange, TextSize}; - -use super::node::Node; -use super::tree::TreeInner; -use crate::SyntaxKind; - -/// Raw token stored in the tree arena. -#[derive(Clone, Copy)] -#[repr(align(4))] -pub(crate) struct Token { - pub(crate) kind: SyntaxKind, - pub(crate) attached_trivia: AttachedTrivia, - pub(crate) end: TextSize, - pub(crate) parent: *const Node, -} - -unsafe impl Send for Token {} -unsafe impl Sync for Token {} - -/// Compact encoding for trivia attachment metadata. -#[derive(Clone, Copy)] -pub(crate) struct AttachedTrivia { - /// Encodes leading/trailing presence and trivia length in a single `u16`. - /// - /// Layout: - /// - bit 0: has leading trivia - /// - bit 1: has trailing trivia - /// - bits 2..: trivia length (leading for real tokens, trailing for first - /// trailing token) - raw: u16, -} - -impl AttachedTrivia { - const MAX_TRIVIA_LEN: usize = (1 << u16::BITS) - 1; - - /// Packs trivia metadata into the compact encoding. - #[inline] - pub(crate) fn new( - has_leading_trivia: bool, - has_trailing_trivia: bool, - trivia_len: usize, - ) -> Self { - assert!(trivia_len <= Self::MAX_TRIVIA_LEN); - Self { - raw: ((trivia_len << 2) - | (usize::from(has_trailing_trivia) << 1) - | usize::from(has_leading_trivia)) as u16, - } - } - - /// Returns `true` when leading trivia is attached. - #[inline] - pub(crate) fn has_leading_trivia(self) -> bool { - (self.raw & 0b01) != 0 - } - - /// Returns `true` when trailing trivia is attached. - #[inline] - pub(crate) fn has_trailing_trivia(self) -> bool { - (self.raw & 0b10) != 0 - } - - /// Returns the number of attached trivia tokens. - #[inline] - pub(crate) fn trivia_len(self) -> usize { - (self.raw >> 2) as usize - } -} - -/// Raw token handle; not a real reference to satisfy Stacked Borrows. -#[derive(Clone, Copy)] -pub(crate) struct TokenRef<'a> { - pub(super) ptr: *const Token, - pub(super) _marker: PhantomData<&'a ()>, -} - -impl<'a> TokenRef<'a> { - /// Returns the underlying token reference. - #[inline] - pub(crate) fn get(self) -> &'a Token { - unsafe { &*self.ptr } - } - - /// Returns the raw token pointer for identity comparisons. - #[inline] - pub(crate) fn ptr(self) -> *const Token { - self.ptr - } - - /// Returns the previous token even if it is the fake sentinel. - #[inline] - fn prev_maybe_fake_token(self) -> &'a Token { - unsafe { &*self.ptr.sub(1) } - } - - /// Returns the token start offset. - #[inline] - pub(crate) fn start(self) -> TextSize { - self.prev_maybe_fake_token().end - } - - /// Returns the token end offset. - #[inline] - pub(crate) fn end(self) -> TextSize { - self.get().end - } - - /// Returns the full text range for the token. - #[inline] - pub(crate) fn text_range(self) -> TextRange { - let start = self.prev_maybe_fake_token().end; - TextRange::new(start, self.get().end) - } - - /// Returns the token text slice from the tree. - #[inline] - pub(crate) fn text(self, tree: &'a TreeInner) -> &'a str { - let range = self.text_range(); - unsafe { tree.text.get_unchecked(usize::from(range.start())..usize::from(range.end())) } - } - - /// Returns the previous token if it exists within the tree. - #[inline] - pub(crate) fn prev_token(self, tree: &TreeInner) -> Option { - let prev_token = unsafe { self.ptr.sub(1) }; - if tree.tokens.as_ptr() == prev_token { - None - } else { - Some(Self { ptr: prev_token, _marker: PhantomData }) - } - } - - /// Returns the next token if it exists within the tree. - #[inline] - pub(crate) fn next_token(self, tree: &TreeInner) -> Option { - let prev_token = unsafe { self.ptr.add(1) }; - if tree.tokens.as_ptr_range().end == prev_token { - None - } else { - Some(Self { ptr: prev_token, _marker: PhantomData }) - } - } - - /// Returns an iterator over leading trivia tokens. - #[inline] - pub(crate) fn leading_trivia(self) -> TokenRefIter<'a> { - if !self.get().attached_trivia.has_leading_trivia() { - return unsafe { TokenRefIter::new(std::ptr::dangling(), 0) }; - } - - let trivia_len = self.get().attached_trivia.trivia_len(); - let trivia_start = unsafe { self.ptr.sub(trivia_len) }; - unsafe { TokenRefIter::new(trivia_start, trivia_len) } - } - - /// Returns an iterator over trailing trivia tokens. - #[inline] - pub(crate) fn trailing_trivia(self) -> TokenRefIter<'a> { - if !self.get().attached_trivia.has_trailing_trivia() { - return unsafe { TokenRefIter::new(std::ptr::dangling(), 0) }; - } - - let trivia_start = unsafe { self.ptr.add(1) }; - let trivia_len = unsafe { (*trivia_start).attached_trivia.trivia_len() }; - unsafe { TokenRefIter::new(trivia_start, trivia_len) } - } - - /// Returns the parent node for this token. - #[inline] - pub(crate) fn parent(self) -> &'a Node { - unsafe { &*self.get().parent } - } -} - -/// Iterator over trivia tokens attached to a token. -#[derive(Clone)] -pub(crate) struct TokenRefIter<'a> { - start: *const Token, - end: *const Token, - _marker: PhantomData<&'a ()>, -} - -impl<'a> TokenRefIter<'a> { - /// Creates an iterator over `len` contiguous tokens starting at `start`. - /// - /// # Safety - /// `start` must point to `len` valid tokens. - #[inline] - unsafe fn new(start: *const Token, len: usize) -> Self { - Self { start, end: unsafe { start.add(len) }, _marker: PhantomData } - } - - /// Returns the number of tokens remaining in the iterator. - #[inline] - pub(crate) fn len(&self) -> usize { - unsafe { self.end.offset_from(self.start) as usize } - } - - /// Returns `true` when the iterator is empty. - #[inline] - fn is_empty(&self) -> bool { - self.start == self.end - } -} - -impl<'a> Iterator for TokenRefIter<'a> { - type Item = TokenRef<'a>; - - #[inline] - fn next(&mut self) -> Option { - if self.is_empty() { - None - } else { - let result = TokenRef { ptr: self.start, _marker: PhantomData }; - unsafe { - self.start = self.start.add(1); - } - Some(result) - } - } -} - -impl<'a> DoubleEndedIterator for TokenRefIter<'a> { - #[inline] - fn next_back(&mut self) -> Option { - if self.is_empty() { - None - } else { - unsafe { - self.end = self.end.sub(1); - } - Some(TokenRef { ptr: self.end, _marker: PhantomData }) - } - } -} diff --git a/crates/mitki-yellow/src/nodes/tree.rs b/crates/mitki-yellow/src/nodes/tree.rs deleted file mode 100644 index d7cbe6e..0000000 --- a/crates/mitki-yellow/src/nodes/tree.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Owning tree storage. - -use super::token::Token; -use crate::maybe_dangling::MaybeDangling; -use crate::nodes::Nodes; - -/// Backing storage for a syntax tree. -pub(crate) struct TreeInner { - /// The source text for all nodes/tokens. - pub(crate) text: MaybeDangling>, - /// Token storage; index 0 is a fake sentinel to make ranges uniform. - pub(crate) tokens: MaybeDangling>, - /// Node and list storage plus child arrays. - pub(crate) nodes: Nodes, -} diff --git a/crates/mitki-yellow/src/syntax.rs b/crates/mitki-yellow/src/syntax.rs deleted file mode 100644 index fe80c6b..0000000 --- a/crates/mitki-yellow/src/syntax.rs +++ /dev/null @@ -1,943 +0,0 @@ -//! Public syntax tree API built on immutable, parent-linked nodes. - -use std::fmt; - -use text_size::{TextRange, TextSize}; - -use crate::SyntaxKind; -use crate::nodes::{ChildKind, List, Node, NodeOrListOrToken, TokenRef, TokenRefIter, TreeInner}; - -/// Owned syntax tree for a single source text. -pub struct SyntaxTree { - pub(crate) tree: TreeInner, -} - -impl SyntaxTree { - /// Returns the root syntax node. - #[inline] - pub fn root(&self) -> SyntaxNode<'_> { - SyntaxNode { tree: &self.tree, node: self.tree.nodes.root() } - } - - /// Returns the full source text for this tree. - #[inline] - pub fn text(&self) -> &str { - &self.tree.text - } -} - -impl fmt::Debug for SyntaxTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SyntaxTree").field("text_len", &self.text().len()).finish_non_exhaustive() - } -} - -unsafe impl salsa::Update for SyntaxTree { - unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool { - let old_value = unsafe { &mut *old_pointer }; - if old_value.text() == new_value.text() { - false - } else { - *old_value = new_value; - true - } - } -} - -/// Token handle tied to the lifetime of the tree. -#[derive(Clone, Copy)] -pub struct SyntaxToken<'a> { - tree: &'a TreeInner, - token: TokenRef<'a>, -} - -impl<'a> SyntaxToken<'a> { - /// Returns this token's kind. - #[inline] - pub fn kind(self) -> SyntaxKind { - self.token.get().kind - } - - /// Returns `true` if this token is trivia. - #[inline] - pub fn is_trivia(self) -> bool { - self.kind().is_trivia() - } - - /// Returns the token text range including attached trivia. - #[inline] - pub fn text_range(self) -> TextRange { - let first_token = self.leading_trivia().next().unwrap_or(self); - let last_token = self.trailing_trivia().next_back().unwrap_or(self); - TextRange::new(first_token.token.start(), last_token.token.end()) - } - - /// Returns the token's own text range, excluding attached trivia. - #[inline] - pub fn trimmed_range(self) -> TextRange { - self.token.text_range() - } - - /// Returns the token text including trivia. - #[inline] - pub fn text(self) -> &'a str { - &self.tree.text[self.text_range()] - } - - /// Returns the token text for `trimmed_range`, excluding attached trivia. - #[inline] - pub fn text_trimmed(self) -> &'a str { - self.token.text(self.tree) - } - - /// Returns the previous token if any. - #[inline] - pub fn prev_token(self) -> Option { - Some(Self { tree: self.tree, token: self.token.prev_token(self.tree)? }) - } - - /// Returns the next token if any. - #[inline] - pub fn next_token(self) -> Option { - Some(Self { tree: self.tree, token: self.token.next_token(self.tree)? }) - } - - /// Iterates over leading trivia tokens. - #[inline] - pub fn leading_trivia(self) -> TriviaIter<'a> { - TriviaIter { tree: self.tree, tokens: self.token.leading_trivia() } - } - - /// Iterates over trailing trivia tokens. - #[inline] - pub fn trailing_trivia(self) -> TriviaIter<'a> { - TriviaIter { tree: self.tree, tokens: self.token.trailing_trivia() } - } - - /// Returns the parent node. - #[inline] - pub fn parent(self) -> SyntaxNode<'a> { - SyntaxNode { tree: self.tree, node: self.token.parent() } - } - - /// Returns an iterator of parent nodes, starting from the immediate parent. - #[inline] - pub fn parent_ancestors(self) -> impl Iterator> + Clone { - self.parent().ancestors() - } -} - -/// Iterator over trivia tokens. -#[derive(Clone)] -pub struct TriviaIter<'a> { - tree: &'a TreeInner, - tokens: TokenRefIter<'a>, -} - -impl<'a> Iterator for TriviaIter<'a> { - type Item = SyntaxToken<'a>; - - #[inline] - fn next(&mut self) -> Option { - Some(SyntaxToken { tree: self.tree, token: self.tokens.next()? }) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - let len = self.tokens.len(); - (len, Some(len)) - } - - #[inline] - fn last(mut self) -> Option - where - Self: Sized, - { - self.next_back() - } -} - -impl<'a> DoubleEndedIterator for TriviaIter<'a> { - #[inline] - fn next_back(&mut self) -> Option { - Some(SyntaxToken { tree: self.tree, token: self.tokens.next_back()? }) - } -} - -impl ExactSizeIterator for TriviaIter<'_> { - #[inline] - fn len(&self) -> usize { - self.tokens.len() - } -} - -/// Node handle tied to the lifetime of the tree. -#[derive(Clone, Copy)] -pub struct SyntaxNode<'a> { - tree: &'a TreeInner, - node: &'a Node, -} - -impl<'a> SyntaxNode<'a> { - /// Returns this node's kind. - #[inline] - pub fn kind(self) -> SyntaxKind { - self.node.kind - } - - /// Returns the first token spanned by this node. - #[inline] - pub fn first_token(self) -> SyntaxToken<'a> { - SyntaxToken { tree: self.tree, token: self.node.first_token(self.tree) } - } - - /// Returns the last token spanned by this node. - #[inline] - pub fn last_token(self) -> SyntaxToken<'a> { - SyntaxToken { tree: self.tree, token: self.node.last_token(self.tree) } - } - - /// Returns the text range covered by this node, including trivia within the - /// node. - #[inline] - pub fn text_range(self) -> TextRange { - self.node.text_range(self.tree) - } - - /// Returns the non-trivia range of this node. - /// - /// This is derived from the first and last non-trivia tokens and returns an - /// empty range at the node start if no such tokens exist. - #[inline] - pub fn trimmed_range(self) -> TextRange { - let first = self.first_non_trivia_token(); - let last = self.last_non_trivia_token(); - match (first, last) { - (Some(first), Some(last)) => { - TextRange::new(first.trimmed_range().start(), last.trimmed_range().end()) - } - _ => TextRange::empty(self.text_range().start()), - } - } - - /// Returns the text slice covered by this node, including trivia. - #[inline] - pub fn text(self) -> &'a str { - self.node.text(self.tree) - } - - /// Returns the text slice for `trimmed_range`, excluding leading/trailing - /// trivia. - #[inline] - pub fn text_trimmed(self) -> &'a str { - &self.tree.text[self.trimmed_range()] - } - - /// Returns the parent node if any. - #[inline] - pub fn parent(self) -> Option { - Some(Self { tree: self.tree, node: self.node.parent()? }) - } - - /// Returns an iterator of ancestors starting from this node. - #[inline] - pub fn ancestors(self) -> impl Iterator> + Clone { - std::iter::successors(Some(self), |it| it.parent()) - } - - /// Iterates children including tokens and list nodes. - #[inline] - pub fn children_with_tokens_and_lists(self) -> ChildrenWithTokensAndLists<'a> { - ChildrenWithTokensAndLists { tree: self.tree, children: self.node.children().iter() } - } - - /// Iterates children including list nodes, skipping tokens. - #[inline] - pub fn children_with_lists(self) -> ChildrenWithLists<'a> { - ChildrenWithLists { inner: self.children_with_tokens_and_lists() } - } - - /// Iterates children including tokens, flattening lists. - #[inline] - pub fn children_with_tokens(self) -> ChildrenWithTokens<'a> { - ChildrenWithTokens { - active_list_iter: None, - children: self.children_with_tokens_and_lists(), - } - } - - #[inline] - /// Iterates child nodes, skipping tokens. - pub fn children(self) -> Children<'a> { - Children { inner: self.children_with_tokens() } - } - - /// Returns the token at the given offset, if any. - #[inline] - pub fn token_at_offset(self, offset: TextSize) -> TokenAtOffset> { - self.node - .token_at_offset(self.tree, offset) - .map(|token| SyntaxToken { tree: self.tree, token }) - } - - /// Returns the smallest element that fully covers `range`. - #[inline] - pub fn covering_element(self, range: TextRange) -> SyntaxElement<'a> { - match self.node.covering_element(self.tree, range) { - NodeOrToken::Node(node) => NodeOrToken::Node(SyntaxNode { tree: self.tree, node }), - NodeOrToken::Token(token) => NodeOrToken::Token(SyntaxToken { tree: self.tree, token }), - } - } -} - -/// Node or token element inside the tree. -pub type SyntaxElement<'a> = NodeOrToken, SyntaxToken<'a>>; - -/// List node wrapper that provides indexed access to child nodes. -#[derive(Clone, Copy)] -pub struct SyntaxList<'a> { - list: &'a List, - tree: &'a TreeInner, -} - -impl<'a> SyntaxList<'a> { - /// Returns the number of children in the list. - #[inline] - pub fn len(self) -> usize { - self.list.children().len() - } - - /// Returns `true` when the list is empty. - #[inline] - pub fn is_empty(self) -> bool { - self.len() == 0 - } - - /// Returns the child at `idx`, if present. - #[inline] - pub fn get(self, idx: usize) -> Option> { - Some(SyntaxNode { tree: self.tree, node: self.list.children().get(idx)? }) - } - - /// Returns the child at `idx`, panicking if out of bounds. - #[inline] - #[track_caller] - pub fn at(self, idx: usize) -> SyntaxNode<'a> { - SyntaxNode { tree: self.tree, node: self.list.children()[idx] } - } - - /// Returns an iterator over list children. - #[inline] - pub fn iter(self) -> SyntaxListIter<'a> { - SyntaxListIter { tree: self.tree, iter: self.list.children().iter() } - } -} - -/// Iterator over `SyntaxList` children. -pub struct SyntaxListIter<'a> { - tree: &'a TreeInner, - iter: std::slice::Iter<'a, &'a Node>, -} - -impl Clone for SyntaxListIter<'_> { - #[inline] - fn clone(&self) -> Self { - Self { tree: self.tree, iter: self.iter.clone() } - } -} - -impl<'a> Iterator for SyntaxListIter<'a> { - type Item = SyntaxNode<'a>; - - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|&node| SyntaxNode { tree: self.tree, node }) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - #[inline] - fn last(mut self) -> Option - where - Self: Sized, - { - self.next_back() - } -} - -impl<'a> DoubleEndedIterator for SyntaxListIter<'a> { - #[inline] - fn next_back(&mut self) -> Option { - self.iter.next_back().map(|&node| SyntaxNode { tree: self.tree, node }) - } -} - -impl ExactSizeIterator for SyntaxListIter<'_> { - #[inline] - fn len(&self) -> usize { - self.iter.len() - } -} - -/// Child element that may be a node, token, or list. -#[derive(Clone, Copy)] -pub enum NodeOrTokenOrList<'a> { - Node(SyntaxNode<'a>), - Token(SyntaxToken<'a>), - List(SyntaxList<'a>), -} - -impl<'a> NodeOrTokenOrList<'a> { - /// Returns the child as a node or list, ignoring tokens. - #[inline] - fn into_node_or_list(self) -> Option> { - match self { - NodeOrTokenOrList::Node(it) => Some(NodeOrList::Node(it)), - NodeOrTokenOrList::List(it) => Some(NodeOrList::List(it)), - NodeOrTokenOrList::Token(_) => None, - } - } -} - -/// Iterator over children including tokens and lists. -pub struct ChildrenWithTokensAndLists<'a> { - tree: &'a TreeInner, - children: std::slice::Iter<'a, NodeOrListOrToken>, -} - -impl Clone for ChildrenWithTokensAndLists<'_> { - #[inline] - fn clone(&self) -> Self { - Self { tree: self.tree, children: self.children.clone() } - } -} - -impl<'a> ChildrenWithTokensAndLists<'a> { - /// Maps a raw child pointer to its typed wrapper. - #[inline] - fn map_child(&self, child: Option<&'a NodeOrListOrToken>) -> Option> { - let tree = self.tree; - child.map(|child| match child.kind() { - ChildKind::Token(token) => NodeOrTokenOrList::Token(SyntaxToken { tree, token }), - ChildKind::Node(node) => NodeOrTokenOrList::Node(SyntaxNode { tree, node }), - ChildKind::List(list) => NodeOrTokenOrList::List(SyntaxList { list, tree }), - }) - } -} - -impl<'a> Iterator for ChildrenWithTokensAndLists<'a> { - type Item = NodeOrTokenOrList<'a>; - - #[inline] - fn next(&mut self) -> Option { - let child = self.children.next(); - self.map_child(child) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.children.size_hint() - } - - #[inline] - fn last(mut self) -> Option - where - Self: Sized, - { - self.next_back() - } -} - -impl<'a> DoubleEndedIterator for ChildrenWithTokensAndLists<'a> { - #[inline] - fn next_back(&mut self) -> Option { - let child = self.children.next_back(); - self.map_child(child) - } -} - -impl ExactSizeIterator for ChildrenWithTokensAndLists<'_> { - #[inline] - fn len(&self) -> usize { - self.children.len() - } -} - -/// Child element that is either a node or list. -pub enum NodeOrList<'a> { - Node(SyntaxNode<'a>), - List(SyntaxList<'a>), -} - -/// Iterator over children including lists but skipping tokens. -pub struct ChildrenWithLists<'a> { - inner: ChildrenWithTokensAndLists<'a>, -} - -impl Clone for ChildrenWithLists<'_> { - #[inline] - fn clone(&self) -> Self { - Self { inner: self.inner.clone() } - } -} - -impl<'a> ChildrenWithLists<'a> { - /// Returns a double-ended iterator over node and list children. - #[inline] - fn iter(self) -> impl DoubleEndedIterator> { - self.inner.filter_map(NodeOrTokenOrList::into_node_or_list) - } -} - -impl<'a> Iterator for ChildrenWithLists<'a> { - type Item = NodeOrList<'a>; - - #[inline] - fn next(&mut self) -> Option { - self.inner.find_map(NodeOrTokenOrList::into_node_or_list) - } - - #[inline] - fn fold(self, init: B, f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter().fold(init, f) - } - - #[inline] - fn for_each(self, f: F) - where - Self: Sized, - F: FnMut(Self::Item), - { - self.iter().for_each(f); - } - - #[inline] - fn last(mut self) -> Option - where - Self: Sized, - { - self.next_back() - } -} - -impl<'a> DoubleEndedIterator for ChildrenWithLists<'a> { - #[inline] - fn next_back(&mut self) -> Option { - self.inner.by_ref().rev().find_map(NodeOrTokenOrList::into_node_or_list) - } - - #[inline] - fn rfold(self, init: B, f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter().rfold(init, f) - } -} - -/// Iterator over children including tokens, flattening list nodes. -pub struct ChildrenWithTokens<'a> { - active_list_iter: Option>, - children: ChildrenWithTokensAndLists<'a>, -} - -impl Clone for ChildrenWithTokens<'_> { - #[inline] - fn clone(&self) -> Self { - Self { active_list_iter: self.active_list_iter.clone(), children: self.children.clone() } - } -} - -impl<'a> Iterator for ChildrenWithTokens<'a> { - type Item = SyntaxElement<'a>; - - #[inline] - fn next(&mut self) -> Option { - loop { - if let Some(list_iter) = &mut self.active_list_iter { - match list_iter.next() { - Some(list_item) => return Some(SyntaxElement::Node(list_item)), - None => self.active_list_iter = None, - } - } - - match self.children.next()? { - NodeOrTokenOrList::Node(it) => return Some(SyntaxElement::Node(it)), - NodeOrTokenOrList::Token(it) => return Some(SyntaxElement::Token(it)), - NodeOrTokenOrList::List(it) => self.active_list_iter = Some(it.iter()), - } - } - } - - #[inline] - fn for_each(self, mut f: F) - where - Self: Sized, - F: FnMut(Self::Item), - { - self.fold((), |(), item| f(item)) - } - - #[inline] - fn fold(self, mut init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - for child in self.children { - match child { - NodeOrTokenOrList::Node(it) => init = f(init, SyntaxElement::Node(it)), - NodeOrTokenOrList::Token(it) => init = f(init, SyntaxElement::Token(it)), - NodeOrTokenOrList::List(it) => { - for child in it.iter() { - init = f(init, SyntaxElement::Node(child)) - } - } - } - } - init - } -} - -/// Iterator over child nodes only. -pub struct Children<'a> { - inner: ChildrenWithTokens<'a>, -} - -impl<'a> Children<'a> { - /// Returns an iterator over node children only. - #[inline] - fn iter(self) -> impl Iterator> { - self.inner.filter_map(NodeOrToken::into_node) - } -} - -impl<'a> Iterator for Children<'a> { - type Item = SyntaxNode<'a>; - - #[inline] - fn next(&mut self) -> Option { - self.inner.find_map(NodeOrToken::into_node) - } - - #[inline] - fn fold(self, init: B, f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter().fold(init, f) - } - - #[inline] - fn for_each(self, f: F) - where - Self: Sized, - F: FnMut(Self::Item), - { - self.iter().for_each(f); - } -} - -/// Preorder traversal over nodes. -#[derive(Clone)] -pub struct Preorder<'a> { - inner: PreorderWithTokens<'a>, -} - -impl<'a> Preorder<'a> { - #[inline] - /// Creates a preorder traversal starting at `start`. - fn new(start: SyntaxNode<'a>) -> Preorder<'a> { - Preorder { inner: PreorderWithTokens::new(start) } - } - - /// Skips the current subtree during traversal. - #[inline] - pub fn skip_subtree(&mut self) { - self.inner.skip_subtree(); - } -} - -impl<'a> Iterator for Preorder<'a> { - type Item = WalkEvent<'a>; - - #[inline] - fn next(&mut self) -> Option { - self.inner.find_map(|item| match item { - WalkEventWithTokens::EnterNode(it) => Some(WalkEvent::Enter(it)), - WalkEventWithTokens::LeaveNode(it) => Some(WalkEvent::Leave(it)), - WalkEventWithTokens::Token(_) => None, - }) - } -} - -/// Preorder walk event for nodes. -#[derive(Clone, Copy)] -pub enum WalkEvent<'a> { - Enter(SyntaxNode<'a>), - Leave(SyntaxNode<'a>), -} - -/// Preorder traversal over nodes and tokens. -#[derive(Clone)] -pub struct PreorderWithTokens<'a> { - stack: Vec<(SyntaxNode<'a>, ChildrenWithTokens<'a>)>, - root: Option>, -} - -impl<'a> PreorderWithTokens<'a> { - #[inline] - /// Creates a preorder traversal starting at `start`. - fn new(start: SyntaxNode<'a>) -> PreorderWithTokens<'a> { - PreorderWithTokens { stack: Vec::with_capacity(128), root: Some(start) } - } - - /// Skips the current subtree during traversal. - #[inline] - pub fn skip_subtree(&mut self) { - assert!(self.stack.pop().is_some(), "must have a subtree to skip"); - } -} - -impl<'a> Iterator for PreorderWithTokens<'a> { - type Item = WalkEventWithTokens<'a>; - - #[inline] - fn next(&mut self) -> Option { - let Some((_, active_node)) = self.stack.last_mut() else { - let root = self.root?; - self.root = None; - return Some(WalkEventWithTokens::EnterNode(root)); - }; - match active_node.next() { - Some(SyntaxElement::Node(child)) => { - self.stack.push((child, child.children_with_tokens())); - Some(WalkEventWithTokens::EnterNode(child)) - } - Some(SyntaxElement::Token(child)) => Some(WalkEventWithTokens::Token(child)), - None => { - let (exited_node, _) = self.stack.pop().expect("should have an exited-from node"); - Some(WalkEventWithTokens::LeaveNode(exited_node)) - } - } - } -} - -/// Preorder walk event including tokens. -#[derive(Clone, Copy)] -pub enum WalkEventWithTokens<'a> { - EnterNode(SyntaxNode<'a>), - LeaveNode(SyntaxNode<'a>), - Token(SyntaxToken<'a>), -} - -/// Stable identifier for a node by kind and non-trivia text range. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, salsa::Update)] -pub struct SyntaxNodePtr { - /// Node kind used for lookup. - pub kind: SyntaxKind, - /// Non-trivia text range used for lookup. - pub range: TextRange, -} - -impl SyntaxNodePtr { - /// Builds a pointer from a concrete node. - pub fn new(node: &SyntaxNode<'_>) -> Self { - Self { kind: node.kind(), range: node.trimmed_range() } - } - - /// Attempts to resolve this pointer within `root`. - pub fn try_to_node<'a>(&self, root: &SyntaxNode<'a>) -> Option> { - if root.parent().is_some() { - return None; - } - - let element = root.covering_element(self.range); - let start_node = match element { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(token) => token.parent(), - }; - - std::iter::successors(Some(start_node), |node| node.parent()) - .find(|node| node.kind() == self.kind && node.trimmed_range() == self.range) - } - - #[track_caller] - /// Resolves this pointer within `root`, panicking on failure. - pub fn to_node<'a>(&self, root: &SyntaxNode<'a>) -> SyntaxNode<'a> { - self.try_to_node(root).unwrap() - } -} - -impl<'a> SyntaxNode<'a> { - /// Returns the first non-trivia token within this node. - fn first_non_trivia_token(self) -> Option> { - let mut token = self.first_token(); - let last_ptr = self.node.last_token(self.tree).ptr(); - loop { - if !token.is_trivia() { - return Some(token); - } - if token.token.ptr() == last_ptr { - return None; - } - token = token.next_token()?; - } - } - - /// Returns the last non-trivia token within this node. - fn last_non_trivia_token(self) -> Option> { - let mut token = self.last_token(); - let first_ptr = self.node.first_token(self.tree).ptr(); - loop { - if !token.is_trivia() { - return Some(token); - } - if token.token.ptr() == first_ptr { - return None; - } - token = token.prev_token()?; - } - } -} - -impl<'a> SyntaxNode<'a> { - /// Returns a preorder iterator over nodes. - #[inline] - pub fn preorder(self) -> Preorder<'a> { - Preorder::new(self) - } - - /// Returns a preorder iterator over nodes and tokens. - #[inline] - pub fn preorder_with_tokens(self) -> PreorderWithTokens<'a> { - PreorderWithTokens::new(self) - } -} - -/// Node-or-token wrapper used throughout the API. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum NodeOrToken { - Node(N), - Token(T), -} - -impl NodeOrToken { - /// Converts into the node variant, if any. - pub fn into_node(self) -> Option { - match self { - NodeOrToken::Node(node) => Some(node), - NodeOrToken::Token(_) => None, - } - } - - /// Converts into the token variant, if any. - pub fn into_token(self) -> Option { - match self { - NodeOrToken::Node(_) => None, - NodeOrToken::Token(token) => Some(token), - } - } - - /// Returns a shared reference to the node, if any. - pub fn as_node(&self) -> Option<&N> { - match self { - NodeOrToken::Node(node) => Some(node), - NodeOrToken::Token(_) => None, - } - } - - /// Returns a shared reference to the token, if any. - pub fn as_token(&self) -> Option<&T> { - match self { - NodeOrToken::Node(_) => None, - NodeOrToken::Token(token) => Some(token), - } - } -} - -impl fmt::Display for NodeOrToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - NodeOrToken::Node(node) => fmt::Display::fmt(node, f), - NodeOrToken::Token(token) => fmt::Display::fmt(token, f), - } - } -} - -/// There might be zero, one or two tokens at a given offset. -#[derive(Clone, Debug)] -pub enum TokenAtOffset { - /// No tokens at offset. - None, - /// Only a single token at offset. - Single(T), - /// Offset is exactly between two tokens. - Between(T, T), -} - -impl TokenAtOffset { - /// Maps tokens to a different type. - pub fn map U, U>(self, f: F) -> TokenAtOffset { - match self { - TokenAtOffset::None => TokenAtOffset::None, - TokenAtOffset::Single(it) => TokenAtOffset::Single(f(it)), - TokenAtOffset::Between(l, r) => TokenAtOffset::Between(f(l), f(r)), - } - } - - /// Convert to option, preferring the right token in case of a tie. - pub fn right_biased(self) -> Option { - match self { - Self::None => None, - Self::Single(node) => Some(node), - Self::Between(_, right) => Some(right), - } - } - - /// Convert to option, preferring the left token in case of a tie. - pub fn left_biased(self) -> Option { - match self { - Self::None => None, - Self::Single(node) => Some(node), - Self::Between(left, _) => Some(left), - } - } -} - -impl Iterator for TokenAtOffset { - type Item = T; - - fn next(&mut self) -> Option { - match std::mem::replace(self, Self::None) { - Self::None => None, - Self::Single(node) => { - *self = Self::None; - Some(node) - } - Self::Between(left, right) => { - *self = Self::Single(right); - Some(left) - } - } - } - - fn size_hint(&self) -> (usize, Option) { - match self { - Self::None => (0, Some(0)), - Self::Single(_) => (1, Some(1)), - Self::Between(_, _) => (2, Some(2)), - } - } -} - -impl ExactSizeIterator for TokenAtOffset {} diff --git a/crates/mitki-yellow/src/syntax_kind.rs b/crates/mitki-yellow/src/syntax_kind.rs deleted file mode 100644 index 2f79445..0000000 --- a/crates/mitki-yellow/src/syntax_kind.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Syntax and token kinds for the parser and tree. - -use std::fmt::Display; - -/// All token and node kinds produced by the parser. -#[allow(non_camel_case_types)] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum SyntaxKind { - LEFT_PAREN, - RIGHT_PAREN, - LEFT_BRACKET, - RIGHT_BRACKET, - LEFT_BRACE, - RIGHT_BRACE, - EQ, - FAT_ARROW, - DOT, - DOUBLE_COLON, - COMMA, - COLON, - SEMICOLON, - FUN_KW, - IN_KW, - IF_KW, - ELSE_KW, - MATCH_KW, - LOOP_KW, - VAL_KW, - VAR_KW, - WHILE_KW, - RETURN_KW, - BREAK_KW, - CONTINUE_KW, - PUB_KW, - USE_KW, - AS_KW, - STRUCT_KW, - ENUM_KW, - MOD_KW, - NAME, - TRUE_KW, - FALSE_KW, - INT_NUMBER, - FLOAT_NUMBER, - STRING, - CHAR, - BINARY_OPERATOR, - POSTFIX_OPERATOR, - PREFIX_OPERATOR, - UNKNOWN, - EOF, - - BIN_OP_SEQ, - ARG_LIST, - LOOP_EXPR, - BREAK_EXPR, - CONTINUE_EXPR, - IF_EXPR, - MATCH_EXPR, - UNSAFE_EXPR, - MATCH_ARM, - ERROR, - EXPR_STMT, - FIELD_EXPR, - CLOSURE_EXPR, - FN, - STRUCT_DEF, - STRUCT_FIELD, - STRUCT_FIELD_LIST, - DESTRUCTOR_DEF, - STRUCT_EXPR, - STRUCT_EXPR_FIELD, - STRUCT_EXPR_FIELD_LIST, - ENUM_DEF, - ENUM_VARIANT, - ENUM_VARIANT_LIST, - RETURN_TYPE, - IDENT, - PATH_EXPR, - NAME_REF, - TYPE_PARAM, - LITERAL, - TUPLE_EXPR, - ARRAY_EXPR, - MODULE, - PARAM, - PARAM_LIST, - GENERIC_PARAM_LIST, - GENERIC_ARG_LIST, - PAREN_EXPR, - POSTFIX_EXPR, - PATH_PATTERN, - FIELD_PATTERN, - BINDING_PATTERN, - WILDCARD_PATTERN, - LITERAL_PATTERN, - TYPED_PATTERN, - PAREN_PATTERN, - TUPLE_PATTERN, - VARIANT_PATTERN, - STRUCT_PATTERN, - STRUCT_PATTERN_FIELD, - PATH_TYPE, - ARRAY_TYPE, - FUNCTION_TYPE, - UNION_TYPE, - INTER_TYPE, - RECORD_TYPE, - TUPLE_TYPE, - POINTER_TYPE, - INSTANCE_ITEM, - MOD_ITEM, - USE_ITEM, - PREFIX_EXPR, - CALL_EXPR, - STMT_LIST, - VAL_STMT, - ASSIGN_STMT, - RETURN_STMT, - TOMBSTONE, - WHITESPACE, - NEWLINE, - LINE_COMMENT, -} - -impl Display for SyntaxKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::LEFT_PAREN => "`(`", - Self::RIGHT_PAREN => "`)`", - Self::LEFT_BRACKET => "`[`", - Self::RIGHT_BRACKET => "`]`", - Self::LEFT_BRACE => "`{`", - Self::RIGHT_BRACE => "`}`", - Self::EQ => "`=`", - Self::FAT_ARROW => "`=>`", - Self::DOT => "`.`", - Self::DOUBLE_COLON => "`::`", - Self::COMMA => "`,`", - Self::COLON => "`:`", - Self::SEMICOLON => "`;`", - Self::FUN_KW => "`fun`", - Self::IF_KW => "`if`", - Self::ELSE_KW => "`else`", - Self::MATCH_KW => "`match`", - Self::LOOP_KW => "`loop`", - Self::VAL_KW => "`val`", - Self::VAR_KW => "`var`", - Self::WHILE_KW => "`while`", - Self::RETURN_KW => "`return`", - Self::BREAK_KW => "`break`", - Self::CONTINUE_KW => "`continue`", - Self::PUB_KW => "`pub`", - Self::USE_KW => "`use`", - Self::AS_KW => "`as`", - Self::STRUCT_KW => "`struct`", - Self::ENUM_KW => "`enum`", - Self::MOD_KW => "`mod`", - Self::NAME => "identifier", - Self::INT_NUMBER => "int", - Self::FLOAT_NUMBER => "float", - Self::STRING => "string", - Self::CHAR => "char", - Self::BINARY_OPERATOR => "binary operator", - Self::POSTFIX_OPERATOR => "postfix operator", - Self::PREFIX_OPERATOR => "prefix operator", - Self::UNKNOWN => "unknown", - Self::EOF => "EOF", - Self::WHITESPACE => "whitespace", - Self::NEWLINE => "newline", - Self::LINE_COMMENT => "line comment", - _ => "", - }) - } -} - -impl SyntaxKind { - /// Returns `true` if this kind represents trivia (whitespace or comments). - #[inline] - pub fn is_trivia(self) -> bool { - matches!(self, Self::WHITESPACE | Self::NEWLINE | Self::LINE_COMMENT) - } -} diff --git a/crates/mitki-yellow/src/syntax_set.rs b/crates/mitki-yellow/src/syntax_set.rs deleted file mode 100644 index 28a930c..0000000 --- a/crates/mitki-yellow/src/syntax_set.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Compact bitset for `SyntaxKind` values. - -use crate::SyntaxKind; - -const SIZE: usize = 2; - -/// Fixed-size set for grouping `SyntaxKind` values. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct SyntaxSet { - bits: [u64; SIZE], -} - -impl SyntaxSet { - /// An empty set. - pub const EMPTY: Self = Self { bits: [0; SIZE] }; - const BITS_PER_SLOT: u16 = u64::BITS as u16; - - /// Builds a set containing a single kind. - const fn from_kind(kind: SyntaxKind) -> Self { - let kind = kind as u16; - - let slot_index = (kind / Self::BITS_PER_SLOT) as usize; - - debug_assert!( - slot_index < Self::EMPTY.bits.len(), - "Index out of bounds. Increase the size of the bitset array." - ); - - let bit_index = kind % Self::BITS_PER_SLOT; - let mask = 1 << bit_index; - - let mut bits = Self::EMPTY.bits; - bits[slot_index] = mask; - - Self { bits } - } - - /// Returns the union of two sets. - pub const fn union(mut self, other: &Self) -> Self { - let mut i = 0; - - while i < self.bits.len() { - self.bits[i] |= other.bits[i]; - i += 1; - } - - self - } - - /// Creates a set from a list of kinds. - pub const fn new(kinds: [SyntaxKind; N]) -> Self { - let mut set = Self::EMPTY; - - let mut i = 0; - while i < kinds.len() { - set = set.union(&Self::from_kind(kinds[i])); - i += 1; - } - - set - } - - /// Returns `true` if the set contains `kind`. - pub const fn contains(&self, kind: SyntaxKind) -> bool { - let kind = kind as u16; - let slot_index = (kind / Self::BITS_PER_SLOT) as usize; - let bit_index = kind % Self::BITS_PER_SLOT; - let mask = 1 << bit_index; - - self.bits[slot_index] & mask != 0 - } -} diff --git a/crates/mitki-yellow/src/trivia.rs b/crates/mitki-yellow/src/trivia.rs deleted file mode 100644 index f649530..0000000 --- a/crates/mitki-yellow/src/trivia.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Trivia pieces attached to tokens. - -use text_size::TextSize; - -/// Kinds of trivia stored alongside tokens. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum TriviaPieceKind { - Whitespace, - Newline, - SingleLineComment, -} - -/// A trivia fragment with its kind and length. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct TriviaPiece { - pub kind: TriviaPieceKind, - pub len: TextSize, -} - -impl TriviaPiece { - /// Creates a new trivia piece with the given kind and length. - pub fn new(kind: TriviaPieceKind, len: TextSize) -> Self { - Self { kind, len } - } -} diff --git a/crates/mitki/Cargo.toml b/crates/mitki/Cargo.toml deleted file mode 100644 index 88bcba2..0000000 --- a/crates/mitki/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "mitki" -version = "0.1.0" -license.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] -anyhow.workspace = true -camino.workspace = true -clap = { version = "4.5", features = ["derive"] } -mimalloc = "0.1" -mitki-abi.workspace = true -mitki-comptime-wasm.workspace = true -mitki-db.workspace = true -mitki-errors.workspace = true -mitki-hir.workspace = true -mitki-inputs.workspace = true -mitki-lower.workspace = true -mitki-lsp-server.workspace = true -mitki-resolve.workspace = true -mitki-typeck.workspace = true -mitki-wasm-runtime.workspace = true -mitki-yellow.workspace = true -salsa.workspace = true -wasmparser.workspace = true - -[dev-dependencies] -assert_cmd = "2.0" -predicates = "3.1" -tempfile = "3.27" -wasmparser.workspace = true diff --git a/crates/mitki/src/main.rs b/crates/mitki/src/main.rs deleted file mode 100644 index 6ae6fad..0000000 --- a/crates/mitki/src/main.rs +++ /dev/null @@ -1,526 +0,0 @@ -use mitki_abi::{ - AbiScalar, AbiValue, ArrayElements, CanonicalGraph, CanonicalNode, PackedScalarKind, ValueRef, -}; -use mitki_hir::hir::WasmLinkage; -use mitki_hir::ty::{Ty, TyKind}; -use mitki_lower::HasItemDecls as _; -use mitki_lower::hir::HasFunction as _; -use mitki_lower::item::scope::{Declaration, FunctionLocation}; -use mitki_resolve::SignatureTypeResolver; -use mitki_typeck::infer::Inferable as _; -use mitki_yellow::ast::HasName as _; - -#[cfg(not(miri))] -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -#[derive(clap::Parser)] -enum Options { - #[command(alias = "r")] - Run { - path: camino::Utf8PathBuf, - }, - Build { - #[arg(long, value_enum)] - target: BuildTarget, - #[arg(short = 'o', long)] - output: Option, - path: camino::Utf8PathBuf, - }, - #[command(name = "run-wasm")] - RunWasm { - path: camino::Utf8PathBuf, - }, - Lsp, -} - -#[derive(Clone, Copy, clap::ValueEnum)] -enum BuildTarget { - Wasm32, -} - -fn main() -> anyhow::Result<()> { - use clap::Parser as _; - - match Options::parse() { - Options::Run { path } => { - let db = mitki_db::RootDatabase::default(); - let file = load_file(&db, path)?; - render_diagnostics(&db, file, mitki_db::check_file(&db, file))?; - Ok(()) - } - Options::Build { target, output, path } => { - let db = mitki_db::RootDatabase::default(); - let file = load_file(&db, path.clone())?; - - match target { - BuildTarget::Wasm32 => match mitki_comptime_wasm::compile_file_to_wasm(&db, file) { - Ok(bytes) => { - let output = output.unwrap_or_else(|| path.with_extension("wasm")); - std::fs::write(&output, bytes).map_err(|error| { - anyhow::anyhow!("failed to write `{output}`: {error}") - })?; - } - Err(diagnostics) => render_diagnostics(&db, file, &diagnostics)?, - }, - } - - Ok(()) - } - Options::RunWasm { path } => { - let db = mitki_db::RootDatabase::default(); - let file = load_file(&db, path)?; - - match mitki_comptime_wasm::compile_file_to_wasm(&db, file) { - Ok(bytes) => run_wasm(&db, file, &bytes)?, - Err(diagnostics) => render_diagnostics(&db, file, &diagnostics)?, - } - - Ok(()) - } - Options::Lsp => mitki_lsp_server::Server::new()?.run(), - } -} - -fn load_file( - db: &mitki_db::RootDatabase, - path: camino::Utf8PathBuf, -) -> anyhow::Result { - use anyhow::Context as _; - - let text = - std::fs::read_to_string(&path).with_context(|| format!("failed to read `{path}`"))?; - Ok(mitki_inputs::File::new(db, path, text)) -} - -fn render_diagnostics( - db: &mitki_db::RootDatabase, - file: mitki_inputs::File, - diagnostics: &[mitki_errors::Diagnostic], -) -> anyhow::Result<()> { - use std::io::Write as _; - - let default_path = file.path(db).as_str(); - let default_text = file.text(db); - let renderer = mitki_errors::Renderer::styled(); - let mut stderr = std::io::stderr().lock(); - - for diagnostic in diagnostics { - let (path, text) = if let Some(path) = diagnostic.file() { - match std::fs::read_to_string(path) { - Ok(text) => (path.to_owned(), text), - Err(_) => (default_path.to_owned(), default_text.to_owned()), - } - } else { - (default_path.to_owned(), default_text.to_owned()) - }; - - let range = diagnostic.range(); - let start = u32::from(range.start()); - let end = u32::from(range.end()); - let text_len = text.len() as u32; - - if start <= end && end <= text_len { - writeln!(stderr, "{}", diagnostic.render(&renderer, &path, &text))?; - } else { - writeln!( - stderr, - "{:?}: {} [{}..{}] (outside `{}`; likely from another file/module)", - diagnostic.level(), - diagnostic.message(), - start, - end, - path, - )?; - } - } - - Ok(()) -} - -fn resolve_run_wasm_main( - db: &mitki_db::RootDatabase, - file: mitki_inputs::File, -) -> anyhow::Result> { - for declaration in file.item_decls(db).declarations().iter().rev() { - let Declaration::Function(function) = *declaration else { - continue; - }; - let source = function.source(db); - if source.name().is_none_or(|name| name.as_str() != "main") { - continue; - } - - let lowered = function.hir_function(db).function(db); - if matches!(lowered.linkage(), WasmLinkage::Import { .. } | WasmLinkage::RawImport { .. }) { - continue; - } - if !lowered.params().is_empty() { - anyhow::bail!("run-wasm requires `main` to have no parameters"); - } - - return classify_main_return(db, function); - } - - anyhow::bail!("run-wasm requires an exported top-level `main` function") -} - -fn classify_main_return<'db>( - db: &'db mitki_db::RootDatabase, - location: FunctionLocation<'db>, -) -> anyhow::Result> { - let signature = location.signature(db); - let return_ty = if signature.ret_type(db).is_zero() { - let hir_function = location.hir_function(db); - let function = hir_function.function(db); - let inference = location.infer(db); - inference - .type_of_node(function.body()) - .unwrap_or_else(|| Ty::new(db, TyKind::Tuple(Vec::new()))) - } else { - SignatureTypeResolver::new(db, location, signature) - .resolve(signature.ret_type(db)) - .unwrap_or_else(|_error| Ty::new(db, TyKind::Unknown)) - }; - if ty_has_unresolved_parts(db, return_ty) { - anyhow::bail!( - "run-wasm requires `main` to return a printable runtime value, found `{}`", - return_ty.display(db) - ) - } - Ok(return_ty) -} - -fn ty_has_unresolved_parts(db: &mitki_db::RootDatabase, ty: Ty<'_>) -> bool { - match ty.kind(db) { - TyKind::Unknown | TyKind::Var(_) => true, - TyKind::Array(item) => ty_has_unresolved_parts(db, *item), - TyKind::Pointer { pointee, .. } => ty_has_unresolved_parts(db, *pointee), - TyKind::Tuple(items) | TyKind::Union(items) | TyKind::Inter(items) => { - items.iter().any(|&item| ty_has_unresolved_parts(db, item)) - } - TyKind::Record(fields) => { - fields.iter().any(|(_, field_ty)| ty_has_unresolved_parts(db, *field_ty)) - } - TyKind::Function { inputs, output } => { - inputs.iter().any(|&input| ty_has_unresolved_parts(db, input)) - || ty_has_unresolved_parts(db, *output) - } - TyKind::Rec(_, body) => ty_has_unresolved_parts(db, *body), - TyKind::Bool - | TyKind::Int - | TyKind::ExactInt(_) - | TyKind::Float - | TyKind::String - | TyKind::Char - | TyKind::Struct(_) - | TyKind::ExternStruct(_) - | TyKind::Enum(_) => false, - } -} - -fn run_wasm( - db: &mitki_db::RootDatabase, - file: mitki_inputs::File, - bytes: &[u8], -) -> anyhow::Result<()> { - let abi = mitki_wasm_runtime::describe_module_abi(bytes)?; - let start_export = abi.exports.iter().any(|export| export.name == "_start"); - let output = if start_export { - mitki_wasm_runtime::invoke_raw_export_with_config( - bytes, - "_start", - mitki_wasm_runtime::RunConfig::wasi_default(), - )? - } else { - mitki_wasm_runtime::invoke_export_with_config( - bytes, - "main", - &[], - mitki_wasm_runtime::RunConfig::wasi_default(), - )? - }; - print!("{}", output.stdout); - - if !start_export && let Some(result) = output.result { - let _main = resolve_run_wasm_main(db, file)?; - println!("{}", format_abi_value(abi.metadata.as_ref(), &result)); - } - - Ok(()) -} - -fn format_abi_value(graph: Option<&mitki_abi::SemanticTypeGraph>, value: &AbiValue) -> String { - match value { - AbiValue::Immediate(scalar) => format_abi_scalar(graph, scalar), - AbiValue::Handle { type_id, handle_id } => format!("", type_id.0, handle_id), - AbiValue::Canonical { transport_type, graph: canonical } => { - format_value_ref(graph, canonical, *transport_type, &canonical.root) - } - } -} - -fn format_abi_scalar(graph: Option<&mitki_abi::SemanticTypeGraph>, scalar: &AbiScalar) -> String { - match scalar { - AbiScalar::Unit => "()".to_owned(), - AbiScalar::Bool(value) => value.to_string(), - AbiScalar::Int { value, .. } => value.to_string(), - AbiScalar::Float { raw_bits, .. } => f64::from_bits(*raw_bits).to_string(), - AbiScalar::Char { unicode_scalar } => char::from_u32(*unicode_scalar) - .map_or_else(|| format!("char({unicode_scalar})"), |value| value.to_string()), - AbiScalar::EnumTag { type_id, variant_index } => { - if let Some(graph) = graph - && let Some(node) = graph.types.get(type_id.0 as usize) - && let mitki_abi::AbiTypeKind::Enum { variants, .. } = &node.kind - && let Some(variant) = variants.get(*variant_index as usize) - && let Some(name_id) = graph.variant_names.get(variant.name.0 as usize) - && let Some(name) = graph.strings.get(name_id.0 as usize) - { - name.clone() - } else { - format!("enum_tag({}, {})", type_id.0, variant_index) - } - } - } -} - -fn format_value_ref( - graph: Option<&mitki_abi::SemanticTypeGraph>, - canonical: &CanonicalGraph, - transport_type: mitki_abi::TypeId, - value: &ValueRef, -) -> String { - match value { - ValueRef::InlineScalar(scalar) => format_abi_scalar(graph, scalar), - ValueRef::HandleRef(slot) => canonical.handles.get(slot.0 as usize).map_or_else( - || format!("", slot.0), - |handle| format!("", handle.type_id.0, handle.handle_id), - ), - ValueRef::NodeRef(node_id) => canonical.nodes.get(node_id.0 as usize).map_or_else( - || format!("", node_id.0), - |node| format_node(graph, canonical, transport_type, node), - ), - } -} - -fn format_node( - graph: Option<&mitki_abi::SemanticTypeGraph>, - canonical: &CanonicalGraph, - transport_type: mitki_abi::TypeId, - node: &CanonicalNode, -) -> String { - match node { - CanonicalNode::String { value, .. } => value.clone(), - CanonicalNode::Array { elements, .. } => match elements { - ArrayElements::Values(values) => format!( - "[{}]", - values - .iter() - .map(|value| format_value_ref(graph, canonical, transport_type, value)) - .collect::>() - .join(", ") - ), - ArrayElements::PackedScalars { kind, len, bytes } => { - format!("[{}]", format_packed_scalars(*kind, *len, bytes).join(", ")) - } - }, - CanonicalNode::Tuple { fields, .. } => format!( - "({})", - fields - .iter() - .map(|value| format_value_ref(graph, canonical, transport_type, value)) - .collect::>() - .join(", ") - ), - CanonicalNode::Record { fields, .. } => format!( - "{{ {} }}", - fields - .iter() - .enumerate() - .map(|(index, value)| { - let name = field_name(graph, transport_type, index) - .unwrap_or_else(|| format!("field{index}")); - format!("{name}: {}", format_value_ref(graph, canonical, transport_type, value)) - }) - .collect::>() - .join(", ") - ), - CanonicalNode::Struct { fields, .. } => format!( - "{{ {} }}", - fields - .iter() - .enumerate() - .map(|(index, value)| { - let name = field_name(graph, transport_type, index) - .unwrap_or_else(|| format!("field{index}")); - format!("{name}: {}", format_value_ref(graph, canonical, transport_type, value)) - }) - .collect::>() - .join(", ") - ), - CanonicalNode::Enum { variant_index, fields, .. } => { - let variant_name = enum_variant_name(graph, transport_type, *variant_index) - .unwrap_or_else(|| format!("variant{variant_index}")); - if fields.is_empty() { - variant_name - } else { - format!( - "{variant_name}({})", - fields - .iter() - .map(|value| format_value_ref(graph, canonical, transport_type, value)) - .collect::>() - .join(", ") - ) - } - } - CanonicalNode::Union { arm_index, payload, .. } => format!( - "union#{arm_index}({})", - format_value_ref(graph, canonical, transport_type, payload) - ), - CanonicalNode::Intersection { carrier, facets, .. } => { - let carrier_text = format_value_ref(graph, canonical, transport_type, carrier); - if facets.is_empty() { - format!("intersection({carrier_text})") - } else { - format!( - "intersection({}, [{}])", - carrier_text, - facets - .iter() - .map(|facet| format_value_ref(graph, canonical, transport_type, facet)) - .collect::>() - .join(", ") - ) - } - } - } -} - -fn format_packed_scalars(kind: PackedScalarKind, len: u32, bytes: &[u8]) -> Vec { - let len = usize::try_from(len).expect("packed scalar array length should fit in usize"); - match kind { - PackedScalarKind::Bool => bytes - .iter() - .take(len) - .map(|byte| if *byte == 0 { "false".to_owned() } else { "true".to_owned() }) - .collect(), - PackedScalarKind::I32 => bytes - .chunks_exact(4) - .take(len) - .map(|chunk| { - i32::from_le_bytes(chunk.try_into().expect("packed i32 chunk")).to_string() - }) - .collect(), - PackedScalarKind::I64 => bytes - .chunks_exact(8) - .take(len) - .map(|chunk| { - i64::from_le_bytes(chunk.try_into().expect("packed i64 chunk")).to_string() - }) - .collect(), - PackedScalarKind::F32 => bytes - .chunks_exact(4) - .take(len) - .map(|chunk| { - f32::from_bits(u32::from_le_bytes(chunk.try_into().expect("packed f32 chunk"))) - .to_string() - }) - .collect(), - PackedScalarKind::F64 => bytes - .chunks_exact(8) - .take(len) - .map(|chunk| { - f64::from_bits(u64::from_le_bytes(chunk.try_into().expect("packed f64 chunk"))) - .to_string() - }) - .collect(), - PackedScalarKind::Char => bytes - .chunks_exact(4) - .take(len) - .map(|chunk| { - let scalar = u32::from_le_bytes(chunk.try_into().expect("packed char chunk")); - char::from_u32(scalar) - .map_or_else(|| format!("\\u{{{scalar:x}}}"), |value| value.to_string()) - }) - .collect(), - } -} - -fn field_name( - graph: Option<&mitki_abi::SemanticTypeGraph>, - transport_type: mitki_abi::TypeId, - index: usize, -) -> Option { - let graph = graph?; - let node = graph.types.get(transport_type.0 as usize)?; - let field = match &node.kind { - mitki_abi::AbiTypeKind::Record { fields } - | mitki_abi::AbiTypeKind::Struct { fields, .. } => fields.get(index)?, - _ => return None, - }; - let string_id = *graph.field_names.get(field.name.0 as usize)?; - graph.strings.get(string_id.0 as usize).cloned() -} - -fn enum_variant_name( - graph: Option<&mitki_abi::SemanticTypeGraph>, - transport_type: mitki_abi::TypeId, - variant_index: u32, -) -> Option { - let graph = graph?; - let node = graph.types.get(transport_type.0 as usize)?; - let variant = match &node.kind { - mitki_abi::AbiTypeKind::Enum { variants, .. } => variants.get(variant_index as usize)?, - _ => return None, - }; - let string_id = *graph.variant_names.get(variant.name.0 as usize)?; - graph.strings.get(string_id.0 as usize).cloned() -} - -#[cfg(test)] -mod tests { - use mitki_hir::ty::TyKind; - use mitki_inputs::File; - - use super::resolve_run_wasm_main; - - #[test] - fn run_wasm_main_prefers_declared_return_type() { - let db = mitki_db::RootDatabase::default(); - let file = File::new( - &db, - "declared_main_return.mitki".into(), - r#" -fun main(): int | bool { - 42 -} -"# - .to_owned(), - ); - - let ty = resolve_run_wasm_main(&db, file).expect("main should resolve"); - let TyKind::Union(items) = ty.kind(&db) else { - panic!("expected declared return type to stay a union, found `{}`", ty.display(&db)); - }; - assert!(items.iter().any(|item| matches!(item.kind(&db), TyKind::Int))); - assert!(items.iter().any(|item| matches!(item.kind(&db), TyKind::Bool))); - } - - #[test] - fn run_wasm_main_preserves_unannotated_unit_return_type() { - let db = mitki_db::RootDatabase::default(); - let file = File::new( - &db, - "unit_main_return.mitki".into(), - r#" -fun main() { -} -"# - .to_owned(), - ); - - let ty = resolve_run_wasm_main(&db, file).expect("main should resolve"); - assert!(matches!(ty.kind(&db), TyKind::Tuple(items) if items.is_empty())); - } -} diff --git a/crates/mitki/tests/cli_wasm.rs b/crates/mitki/tests/cli_wasm.rs deleted file mode 100644 index ff1a9b4..0000000 --- a/crates/mitki/tests/cli_wasm.rs +++ /dev/null @@ -1,1381 +0,0 @@ -use std::fs; - -use assert_cmd::Command; -use mitki_abi::{LinkageKind, REQUIRED_FEATURE_HANDLES}; -use mitki_wasm_runtime::describe_module_abi; - -#[test] -fn build_writes_default_wasm_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("answer.mitki"); - let output = tempdir.path().join("answer.wasm"); - - fs::write( - &input, - r#" -fun answer(): int { - 42 -} - -fun main() { - val x = answer() -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args([ - "build", - "--target", - "wasm32", - "-o", - output.to_str().expect("utf8 path"), - input.to_str().expect("utf8 path"), - ]) - .assert() - .success(); - - let bytes = fs::read(output).expect("expected `build` to write a wasm file"); - wasmparser::Validator::new().validate_all(&bytes).expect("emitted Wasm should validate"); -} - -#[test] -fn build_wasm_emits_start_for_unit_main() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("unit_main.mitki"); - let output = tempdir.path().join("unit_main.wasm"); - - fs::write( - &input, - r#" -fun main() { -} -"#, - ) - .expect("write unit program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success(); - - let bytes = fs::read(output).expect("expected `build` to write a wasm file"); - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - assert!(abi.exports.iter().any(|export| export.name == "_start")); -} - -#[test] -fn run_wasm_suppresses_unit_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let unit_program = tempdir.path().join("print_unit.mitki"); - - fs::write( - &unit_program, - r#" -fun main() { -} -"#, - ) - .expect("write unit program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", unit_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout(""); -} - -#[test] -fn run_wasm_prints_i32_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let int_program = tempdir.path().join("print_i32.mitki"); - - fs::write( - &int_program, - r#" -fun main(): int { - 42 -} -"#, - ) - .expect("write int program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", int_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn run_wasm_prints_runtime_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let runtime_program = tempdir.path().join("runtime_output.mitki"); - - fs::write( - &runtime_program, - r#" -fun main() { - std::io::print_str("hello"); - std::io::print_int(7) -} -"#, - ) - .expect("write runtime program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", runtime_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("hello7"); -} - -#[test] -fn run_wasm_supports_std_io_wasi_helpers() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("stdio_helpers.mitki"); - - fs::write( - &program, - r#" -fun main() { - std::io::println_str("hello"); - std::io::println_str("world"); - std::io::eprintln_str("oops"); -} -"#, - ) - .expect("write stdio helper program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("hello\nworld\n") - .stderr("oops\n"); -} - -#[test] -fn run_wasm_supports_child_modules_via_crate_paths() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let root_program = tempdir.path().join("main.mitki"); - let math_program = tempdir.path().join("math.mitki"); - - fs::write( - &root_program, - r#" -mod math; - -fun main() { - std::io::print_int(crate::math::answer()) -} -"#, - ) - .expect("write root program"); - fs::write( - &math_program, - r#" -fun answer(): int { - 42 -} -"#, - ) - .expect("write child module"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", root_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42"); -} - -#[test] -fn run_wasm_supports_use_imports_and_module_aliases() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("use_imports.mitki"); - - fs::write( - &program, - r#" -use std::io::print_int; -use std::io as io; - -fun main() { - print_int(4); - io::print_int(2) -} -"#, - ) - .expect("write use-import program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42"); -} - -#[test] -fn run_wasm_orders_runtime_output_before_main_result() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let runtime_program = tempdir.path().join("runtime_and_main.mitki"); - - fs::write( - &runtime_program, - r#" -fun main(): int { - std::io::print_str("answer="); - std::io::print_int(4); - 2 -} -"#, - ) - .expect("write runtime program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", runtime_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("answer=42\n"); -} - -#[test] -fn build_wasm_library_without_main() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("library.mitki"); - let output = tempdir.path().join("library.wasm"); - - fs::write( - &input, - r#" -export fun answer(): int { - 42 -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success(); - - let bytes = fs::read(output).expect("expected `build` to write a wasm file"); - wasmparser::Validator::new().validate_all(&bytes).expect("emitted Wasm should validate"); -} - -#[test] -fn build_wasm_supports_function_typed_boundaries() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("function_handles.mitki"); - let output = tempdir.path().join("function_handles.wasm"); - - fs::write( - &input, - r#" -import "env" fun round_trip(f: fun(int) -> int): fun(int) -> int; - -export fun id(f: fun(int) -> int): fun(int) -> int { - round_trip(f) -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success(); - - let bytes = fs::read(output).expect("expected `build` to write a wasm file"); - wasmparser::Validator::new().validate_all(&bytes).expect("emitted Wasm should validate"); - - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI metadata"); - assert_ne!(metadata.required_features & REQUIRED_FEATURE_HANDLES, 0); - assert!(metadata.function_instances.iter().any(|instance| { - instance.linkage == LinkageKind::WasmExport && instance.wasm_field_name.is_some() - })); -} - -#[test] -fn build_wasm_preserves_typed_host_imports() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("typed_host_import.mitki"); - let output = tempdir.path().join("typed_host_import.wasm"); - - fs::write( - &input, - r#" -import "env" fun round_trip(xs: [int]): [int]; - -export fun main(): [int] { - round_trip([1, 2, 3]) -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success(); - - let bytes = fs::read(output).expect("expected `build` to write a wasm file"); - let abi = describe_module_abi(&bytes).expect("ABI metadata should decode"); - let metadata = abi.metadata.as_ref().expect("expected ABI metadata"); - assert!(metadata.function_instances.iter().any(|instance| { - instance.linkage == LinkageKind::WasmImport - && instance.wasm_module_name.is_some_and(|id| metadata.strings[id.0 as usize] == "env") - })); -} - -#[test] -fn run_wasm_prints_bool_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let bool_program = tempdir.path().join("print_bool.mitki"); - - fs::write( - &bool_program, - r#" -fun main(): bool { - true -} -"#, - ) - .expect("write bool program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", bool_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("true\n"); -} - -#[test] -fn run_wasm_prints_parenthesized_logical_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let bool_program = tempdir.path().join("print_parenthesized_bool.mitki"); - - fs::write( - &bool_program, - r#" -fun main(): bool { - (1 < 2 && 2 < 3) || false -} -"#, - ) - .expect("write bool program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", bool_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("true\n"); -} - -#[test] -fn run_wasm_prints_float_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let float_program = tempdir.path().join("print_float.mitki"); - - fs::write( - &float_program, - r#" -fun main(): float { - 42.5 -} -"#, - ) - .expect("write float program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", float_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42.5\n"); -} - -#[test] -fn run_wasm_prints_char_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let char_program = tempdir.path().join("print_char.mitki"); - - fs::write( - &char_program, - r#" -fun main(): char { - 'x' -} -"#, - ) - .expect("write char program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", char_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("x\n"); -} - -#[test] -fn run_wasm_supports_char_comparisons() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let char_program = tempdir.path().join("char_compare.mitki"); - - fs::write( - &char_program, - r#" -fun main(): bool { - 'a' < 'b' -} -"#, - ) - .expect("write char compare program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", char_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("true\n"); -} - -#[test] -fn run_wasm_supports_char_match_patterns() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let char_program = tempdir.path().join("char_match.mitki"); - - fs::write( - &char_program, - r#" -fun classify(ch: char): int { - match ch { - 'a' => 1, - 'b' => 2, - _ => 0, - } -} - -fun main(): int { - classify('b') -} -"#, - ) - .expect("write char match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", char_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("2\n"); -} - -#[test] -fn run_wasm_prints_string_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let string_program = tempdir.path().join("print_string.mitki"); - - fs::write( - &string_program, - r#" -fun main(): str { - "hello" -} -"#, - ) - .expect("write string program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", string_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("hello\n"); -} - -#[test] -fn run_wasm_prints_array_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let array_program = tempdir.path().join("print_array.mitki"); - - fs::write( - &array_program, - r#" -fun main(): [int] { - [1, 2, 3] -} -"#, - ) - .expect("write array program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", array_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("[1, 2, 3]\n"); -} - -#[test] -fn run_wasm_prints_string_array_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let array_program = tempdir.path().join("print_string_array.mitki"); - - fs::write( - &array_program, - r#" -fun main(): [str] { - ["a", "b"] -} -"#, - ) - .expect("write string array program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", array_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("[a, b]\n"); -} - -#[test] -fn run_wasm_prints_struct_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let struct_program = tempdir.path().join("print_struct.mitki"); - - fs::write( - &struct_program, - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - Point { x: 20, y: 22 } -} -"#, - ) - .expect("write struct program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", struct_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("{ x: 20, y: 22 }\n"); -} - -#[test] -fn run_wasm_supports_struct_field_shorthand() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let struct_program = tempdir.path().join("struct_shorthand.mitki"); - - fs::write( - &struct_program, - r#" -struct Point { - x: int, - y: int, -} - -fun main(): Point { - val x = 20 - Point { x, y: 22 } -} -"#, - ) - .expect("write struct shorthand program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", struct_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("{ x: 20, y: 22 }\n"); -} - -#[test] -fn run_wasm_prints_tuple_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let tuple_program = tempdir.path().join("print_tuple.mitki"); - - fs::write( - &tuple_program, - r#" -fun main(): (int, int) { - (20, 22) -} -"#, - ) - .expect("write tuple program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", tuple_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("(20, 22)\n"); -} - -#[test] -fn run_wasm_prints_nested_enum_output() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let enum_program = tempdir.path().join("print_enum.mitki"); - - fs::write( - &enum_program, - r#" -enum Words { - Some([str]), - Empty, -} - -fun main(): Words { - Words.Some(["a", "b"]) -} -"#, - ) - .expect("write enum program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", enum_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("Some([a, b])\n"); -} - -#[test] -fn run_wasm_matches_literal_patterns_over_union() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let union_program = tempdir.path().join("union_literal_match.mitki"); - - fs::write( - &union_program, - r#" -fun main(): int { - val a: int | bool = if true { true } else { 0 } - val b: int | bool = if true { false } else { 0 } - val c: int | bool = if true { 7 } else { false } - - val x: int = match a { - true => 1, - false => 0, - _ => 2, - } - val y: int = match b { - true => 1, - false => 0, - _ => 2, - } - val z: int = match c { - true => 1, - false => 0, - _ => 2, - } - x + y + z -} -"#, - ) - .expect("write union literal match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", union_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("3\n"); -} - -#[test] -fn run_wasm_matches_literal_patterns_for_union_parameters() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let union_program = tempdir.path().join("union_param_match.mitki"); - - fs::write( - &union_program, - r#" -fun score(value: int | bool): int { - match value { - 42 => 10, - false => 20, - _ => 30, - } -} - -fun main(): int { - score(42) + score(false) -} -"#, - ) - .expect("write union parameter match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", union_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("30\n"); -} - -#[test] -fn run_wasm_matches_union_tuple_struct_and_enum_members() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let union_program = tempdir.path().join("union_member_match.mitki"); - - fs::write( - &union_program, - r#" -struct Box { - value: int, -} - -enum Choice { - Some(int), - None, -} - -fun main(): int { - tuple_case() + struct_case() + enum_case() -} - -fun tuple_case(): int { - val tuple_value: int | (bool, int) = if true { (true, 5) } else { 0 } - match tuple_value { - (flag, n) => if flag { n } else { 0 }, - _ => 0, - } -} - -fun struct_case(): int { - val struct_value: int | Box = if true { Box { value: 7 } } else { 0 } - match struct_value { - Box { value } => value, - _ => 0, - } -} - -fun enum_case(): int { - val enum_value: int | Choice = if true { Choice.Some(11) } else { 0 } - match enum_value { - .Some(n) => n, - .None => 0, - _ => 0, - } -} -"#, - ) - .expect("write union member match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", union_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("23\n"); -} - -#[test] -fn run_wasm_matches_typed_patterns_over_union() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let union_program = tempdir.path().join("union_typed_match.mitki"); - - fs::write( - &union_program, - r#" -fun describe(value: int | str): str { - match value { - s: str => s, - _: int => "int", - } -} - -fun main(): [str] { - val a: int | str = if true { "hello" } else { 0 } - val b: int | str = if true { 7 } else { "unused" } - [describe(a), describe(b)] -} -"#, - ) - .expect("write typed union match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", union_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("[hello, int]\n"); -} - -#[test] -fn run_wasm_matches_nested_typed_union_members() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let union_program = tempdir.path().join("union_nested_typed_match.mitki"); - - fs::write( - &union_program, - r#" -fun describe(value: int | (int, str)): str { - match value { - (n: int, _: str) => "pair", - _: int => "int", - } -} - -fun main(): [str] { - val a: int | (int, str) = if true { (7, "s") } else { 0 } - val b: int | (int, str) = if true { 9 } else { (1, "x") } - [describe(a), describe(b)] -} -"#, - ) - .expect("write nested typed union match program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", union_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("[pair, int]\n"); -} - -#[test] -fn run_wasm_evaluates_nested_comptime() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let comptime_program = tempdir.path().join("nested_comptime.mitki"); - - fs::write( - &comptime_program, - r#" -comptime fun base(): int { - 40 -} - -comptime fun build(): int { - comptime(base()) + 2 -} - -fun main(): int { - comptime(build()) -} -"#, - ) - .expect("write comptime program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", comptime_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn run_wasm_links_wasi_preview1_imports() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let wasi_program = tempdir.path().join("wasi_main.mitki"); - - fs::write( - &wasi_program, - r#" -import "wasi_snapshot_preview1" fun sched_yield(): int; - -fun main(): int { - sched_yield() -} -"#, - ) - .expect("write wasi program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", wasi_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("0\n"); -} - -#[test] -fn run_wasm_opens_a_file_via_raw_wasi_preview1() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let wasi_program = tempdir.path().join("wasi_open_file.mitki"); - let message_path = tempdir.path().join("message.txt"); - - fs::write(&message_path, "Hello from WASI").expect("write message file"); - fs::write( - &wasi_program, - r#" -extern struct ByteSlice { - ptr: *const u8, - len: u32, -} - -import "wasi_snapshot_preview1" unsafe fun path_open( - dirfd: u32, - dirflags: u32, - path_ptr: *const u8, - path_len: u32, - oflags: u16, - rights_base: u64, - rights_inheriting: u64, - fdflags: u16, - opened_fd: *mut u32, -): u16; - -import "wasi_snapshot_preview1" unsafe fun fd_close(fd: u32): u16; - -unsafe fun open_status(path: str): int { - val opened_fd: *mut u32 = stack_alloc(1) - ptr_write(opened_fd, 0); - val path_bytes = str_bytes(path) - if path_bytes.len != 11 { - 1100 - } else { - val open_errno = path_open(3, 0, path_bytes.ptr, path_bytes.len, 0, 2, 0, 0, opened_fd) - if open_errno != 0 { - 1000 - } else { - val fd = ptr_read(opened_fd) - if fd == 0 { - 1300 - } else { - if fd_close(fd) != 0 { - 1400 - } else { - 1 - } - } - } - } -} - -fun main(): int { - unsafe { - open_status("message.txt") - } -} -"#, - ) - .expect("write wasi read program"); - - Command::cargo_bin("mitki") - .expect("binary") - .current_dir(tempdir.path()) - .args(["run-wasm", wasi_program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("1\n"); -} - -#[test] -fn run_wasm_rejects_non_wasi_imports() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let env_program = tempdir.path().join("env_main.mitki"); - - fs::write( - &env_program, - r#" -import "env" fun host(): int; - -fun main(): int { - host() -} -"#, - ) - .expect("write env program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", env_program.to_str().expect("utf8 path")]) - .assert() - .failure() - .stderr(predicates::str::contains("env::host")); -} - -#[test] -fn run_wasm_supports_std_alloc_int_module() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("alloc_int.mitki"); - - fs::write( - &program, - r#" -use std::alloc::int as alloc; - -fun main(): int { - unsafe { - val ptr: *mut int = alloc::alloc(4, 4) - ptr_write(ptr, 42); - val value: int = ptr_read(ptr) - alloc::dealloc(ptr, 4, 4); - value - } -} -"#, - ) - .expect("write alloc program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn run_wasm_supports_std_lexer_new() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("main.mitki"); - - fs::write( - &program, - r#" -use std::lexer; - -fun main() { - var n = lexer::new("def"); -} -"#, - ) - .expect("write std lexer program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout(""); -} - -#[test] -fn run_wasm_supports_std_alloc_copy_and_realloc_helpers() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("alloc_realloc_int.mitki"); - - fs::write( - &program, - r#" -use std::alloc::int as int_alloc; - -fun main(): int { - unsafe { - val src: *mut int = int_alloc::alloc_items(2) - ptr_write(src, 20); - ptr_write(ptr_add(src, 1), 22); - - val dst: *mut int = int_alloc::alloc_items(2) - int_alloc::copy_nonoverlapping(dst, src, 2); - - val grown: *mut int = int_alloc::realloc(dst, 2, 3) - ptr_write(ptr_add(grown, 2), 7); - - val total: int = - ptr_read(grown) + ptr_read(ptr_add(grown, 1)) + ptr_read(ptr_add(grown, 2)) - int_alloc::dealloc_items(src, 2); - int_alloc::dealloc_items(grown, 3); - total - } -} -"#, - ) - .expect("write alloc program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("49\n"); -} - -#[test] -fn run_wasm_supports_std_vec_int_mutation() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("vec_int.mitki"); - - fs::write( - &program, - r#" -use std::vec::int as vec; - -fun main(): int { - var xs: vec::Vec = vec::new() - xs.push(20); - xs.push(22); - xs.reserve(8); - xs.set(1, 23); - - val first: int = match xs.get(0) { - .Some(value) => value, - .None => 0, - } - val second: int = match xs.get(1) { - .Some(value) => value, - .None => 0, - } - - xs.clear(); - val empty_bonus: int = if xs.is_empty() { 1 } else { 0 } - xs.free(); - - first + second + empty_bonus -} -"#, - ) - .expect("write vec program"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("44\n"); -} - -#[test] -fn run_wasm_supports_method_call_syntax_for_module_functions() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("main.mitki"); - let counter_module = tempdir.path().join("counter.mitki"); - - fs::write( - &program, - r#" -mod counter; - -fun main(): int { - crate::counter::new(40).bump(2).get() -} -"#, - ) - .expect("write method program"); - - fs::write( - &counter_module, - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun bump(counter: Counter, delta: int): Counter { - Counter { value: counter.value + delta } -} - -fun get(counter: Counter): int { - counter.value -} -"#, - ) - .expect("write counter module"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn run_wasm_supports_in_place_method_updates_on_var_locals() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("main.mitki"); - let counter_module = tempdir.path().join("counter.mitki"); - - fs::write( - &program, - r#" -mod counter; - -fun main(): int { - var counter: crate::counter::Counter = crate::counter::new(40) - counter.bump(2); - counter.get() -} -"#, - ) - .expect("write mutable counter program"); - - fs::write( - &counter_module, - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun bump(var counter: Counter, delta: int) { - counter.value = counter.value + delta -} - -fun get(counter: Counter): int { - counter.value -} -"#, - ) - .expect("write counter module"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn run_wasm_supports_method_calls_inside_loop_bodies() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let program = tempdir.path().join("main.mitki"); - let counter_module = tempdir.path().join("counter.mitki"); - - fs::write( - &program, - r#" -mod counter; - -fun read(counter: crate::counter::Counter): int { - var result: int = 0 - loop { - val current: int = counter.get() - result = current - break - } - result -} - -fun main(): int { - read(crate::counter::new(42)) -} -"#, - ) - .expect("write loop program"); - - fs::write( - &counter_module, - r#" -struct Counter { - value: int, -} - -fun new(value: int): Counter { - Counter { value: value } -} - -fun get(counter: Counter): int { - counter.value -} -"#, - ) - .expect("write counter module"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["run-wasm", program.to_str().expect("utf8 path")]) - .assert() - .success() - .stdout("42\n"); -} - -#[test] -fn build_wasm_rejects_recursive_boundary_types() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("recursive_boundary.mitki"); - let output = tempdir.path().join("recursive_boundary.wasm"); - - fs::write( - &input, - r#" -enum List { - Nil, - Cons(int, List), -} - -export fun echo(xs: List): List { - xs -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success() - .stderr(predicates::str::contains("non-copy types like `List`")); - - assert!( - !output.exists(), - "build should not leave behind a wasm artifact when it emitted boundary diagnostics" - ); -} - -#[test] -fn build_wasm_rejects_by_value_use_of_non_copy_values() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let input = tempdir.path().join("non_copy_value_use.mitki"); - let output = tempdir.path().join("non_copy_value_use.wasm"); - - fs::write( - &input, - r#" -struct Token { - items: [str], - - drop(var self) { - } -} - -fun take(token: Token) { -} - -fun main() { - val token = Token { items: ["a"] } - take(token) -} -"#, - ) - .expect("write input"); - - Command::cargo_bin("mitki") - .expect("binary") - .args(["build", "--target", "wasm32", input.to_str().expect("utf8 path")]) - .assert() - .success() - .stderr(predicates::str::contains("non-copy value `Token` cannot be used by value yet")); - - assert!( - !output.exists(), - "build should not leave behind a wasm artifact when it emitted non-copy diagnostics" - ); -} diff --git a/deny.toml b/deny.toml deleted file mode 100644 index f42e1c2..0000000 --- a/deny.toml +++ /dev/null @@ -1,14 +0,0 @@ -[licenses] -allow = [ - "MIT", - "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", - "MPL-2.0", - "Zlib", - "Unicode-3.0", -] - -[sources] -allow-git = ["https://github.com/salsa-rs/salsa/"] diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 5d56faf..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 362c40c..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,9 +0,0 @@ -format_code_in_doc_comments = true -format_strings = true -group_imports = "StdExternalCrate" -imports_granularity = "Module" -merge_derives = false -use_field_init_shorthand = true -use_small_heuristics = "Max" -style_edition = "2024" -wrap_comments = true diff --git a/stdlib/src/alloc.mitki b/stdlib/src/alloc.mitki deleted file mode 100644 index 48092a5..0000000 --- a/stdlib/src/alloc.mitki +++ /dev/null @@ -1,23 +0,0 @@ -pub mod int; - -import "mitki" unsafe fun alloc(size: u32, align: u32): *mut u8; -import "mitki" unsafe fun dealloc(ptr: *mut u8, size: u32, align: u32); -import "mitki" unsafe fun _copy_nonoverlapping(dst: *mut u8, src: *const u8, count: u32); - -unsafe fun copy_nonoverlapping(dst: *mut u8, src: *const u8, count: u32) { - _copy_nonoverlapping(dst, src, count) -} - -unsafe fun realloc( - ptr: *mut u8, - old_size: u32, - old_align: u32, - new_size: u32, - new_align: u32, -): *mut u8 { - val next: *mut u8 = alloc(new_size, new_align) - val copy_size: u32 = if old_size < new_size { old_size } else { new_size } - copy_nonoverlapping(next, ptr, copy_size); - dealloc(ptr, old_size, old_align); - next -} diff --git a/stdlib/src/alloc/int.mitki b/stdlib/src/alloc/int.mitki deleted file mode 100644 index a39db05..0000000 --- a/stdlib/src/alloc/int.mitki +++ /dev/null @@ -1,27 +0,0 @@ -import "mitki" unsafe fun alloc(size: u32, align: u32): *mut int; -import "mitki" unsafe fun dealloc(ptr: *mut int, size: u32, align: u32); - -unsafe fun alloc_items(count: u32): *mut int { - alloc(count * 4, 4) -} - -unsafe fun dealloc_items(ptr: *mut int, count: u32) { - dealloc(ptr, count * 4, 4) -} - -unsafe fun copy_nonoverlapping(dst: *mut int, src: *const int, count: u32) { - if count == 0 { - () - } else { - ptr_write(dst, ptr_read(src)); - copy_nonoverlapping(ptr_add(dst, 1), ptr_add(src, 1), count - 1) - } -} - -unsafe fun realloc(ptr: *mut int, old_count: u32, new_count: u32): *mut int { - val next: *mut int = alloc_items(new_count) - val copy_count: u32 = if old_count < new_count { old_count } else { new_count } - copy_nonoverlapping(next, ptr, copy_count); - dealloc_items(ptr, old_count); - next -} diff --git a/stdlib/src/env.mitki b/stdlib/src/env.mitki deleted file mode 100644 index fc929fa..0000000 --- a/stdlib/src/env.mitki +++ /dev/null @@ -1,102 +0,0 @@ -use std::alloc as bytes_alloc; -use std::str as strings; - -extern struct EnvEntryPtr { - ptr: *const u8, -} - -import "wasi_snapshot_preview1" unsafe fun environ_sizes_get( - environ_count: *mut u32, - environ_buf_size: *mut u32, -): u16; -import "wasi_snapshot_preview1" unsafe fun environ_get( - environ: *mut EnvEntryPtr, - environ_buf: *mut u8, -): u16; - -import "mitki" unsafe fun alloc(size: u32, align: u32): *mut EnvEntryPtr; -import "mitki" unsafe fun dealloc(ptr: *mut EnvEntryPtr, size: u32, align: u32); - -unsafe fun alloc_entry_ptrs(count: u32): *mut EnvEntryPtr { - val actual_count: u32 = if count == 0 { 1 } else { count } - alloc(actual_count * 4, 4) -} - -unsafe fun dealloc_entry_ptrs(ptr: *mut EnvEntryPtr, count: u32) { - val actual_count: u32 = if count == 0 { 1 } else { count } - dealloc(ptr, actual_count * 4, 4) -} - -unsafe fun entry_len(ptr: *const u8, index: u32): u32 { - if ptr_read(ptr_add(ptr, index)) == 0 { - index - } else { - entry_len(ptr, index + 1) - } -} - -unsafe fun entry_eq_index(ptr: *const u8, index: u32): u32 { - val byte: u8 = ptr_read(ptr_add(ptr, index)) - if byte == 61 || byte == 0 { - index - } else { - entry_eq_index(ptr, index + 1) - } -} - -unsafe fun scan_var( - entries: *mut EnvEntryPtr, - count: u32, - index: u32, - name: str, -): (bool, str) { - if index == count { - (false, "") - } else { - val entry: *const u8 = ptr_read(ptr_add(entries, index)).ptr - val len: u32 = entry_len(entry, 0) - val eq_index: u32 = entry_eq_index(entry, 0) - val key: str = strings::from_raw_parts_unchecked(entry, eq_index) - - if eq_index < len && key == name { - ( - true, - strings::from_raw_parts_unchecked( - ptr_add(entry, eq_index + 1), - len - eq_index - 1, - ), - ) - } else { - scan_var(entries, count, index + 1, name) - } - } -} - -fun var(name: str): (bool, str) { - unsafe { - val count_out: *mut u32 = stack_alloc(1) - val buf_size_out: *mut u32 = stack_alloc(1) - - ptr_write(count_out, 0); - ptr_write(buf_size_out, 0); - - if environ_sizes_get(count_out, buf_size_out) != 0 { - (false, "") - } else { - val count: u32 = ptr_read(count_out) - val buf_size: u32 = ptr_read(buf_size_out) - val actual_buf_size: u32 = if buf_size == 0 { 1 } else { buf_size } - val entries: *mut EnvEntryPtr = alloc_entry_ptrs(count) - val buf: *mut u8 = bytes_alloc::alloc(actual_buf_size, 1) - val result: (bool, str) = if environ_get(entries, buf) != 0 { - (false, "") - } else { - scan_var(entries, count, 0, name) - } - - dealloc_entry_ptrs(entries, count); - bytes_alloc::dealloc(buf, actual_buf_size, 1); - result - } - } -} diff --git a/stdlib/src/io.mitki b/stdlib/src/io.mitki deleted file mode 100644 index 649e222..0000000 --- a/stdlib/src/io.mitki +++ /dev/null @@ -1,53 +0,0 @@ -extern struct Ciovec { - buf: *const u8, - buf_len: u32, -} - -import "mitki" fun _print_i32(value: int); -import "wasi_snapshot_preview1" unsafe fun fd_write( - fd: u32, - iovs: *const Ciovec, - iovs_len: u32, - nwritten: *mut u32, -): u16; - -unsafe fun write_str(fd: u32, text: str): u16 { - val bytes = str_bytes(text) - val iov_out: *mut Ciovec = stack_alloc(1) - val nwritten_out: *mut u32 = stack_alloc(1) - - ptr_write(nwritten_out, 0); - ptr_write(iov_out, Ciovec { buf: bytes.ptr, buf_len: bytes.len }); - fd_write(fd, iov_out, 1, nwritten_out) -} - -fun print_str(text: str) { - unsafe { - write_str(1, text); - } -} - -fun println_str(text: str) { - print_str(text); - print_str("\n") -} - -fun eprint_str(text: str) { - unsafe { - write_str(2, text); - } -} - -fun eprintln_str(text: str) { - eprint_str(text); - eprint_str("\n") -} - -fun print_int(value: int) { - _print_i32(value) -} - -fun println_int(value: int) { - print_int(value); - print_str("\n") -} diff --git a/stdlib/src/lexer.mitki b/stdlib/src/lexer.mitki deleted file mode 100644 index ef0e1f2..0000000 --- a/stdlib/src/lexer.mitki +++ /dev/null @@ -1,603 +0,0 @@ -use std::io; -use std::str as strings; - -enum TokenKind { - Name, - Int, - String, - LeftParen, - RightParen, - LeftBracket, - RightBracket, - LeftBrace, - RightBrace, - Comma, - Colon, - Semicolon, - Dot, - Eq, - DoubleColon, - FatArrow, - FunKw, - UseKw, - StructKw, - EnumKw, - ModKw, - IfKw, - ElseKw, - ReturnKw, - ValKw, - VarKw, - Operator, - Trivia, - Error, - Eof, -} - -struct Token { - kind: TokenKind, - start: u32, - end: u32, -} - -struct Lexer { - src: str, - pos: u32, - token_kind: u32, - token_start: u32, - token_end: u32, -} - -fun new(src: str): Lexer { - Lexer { src, pos: 0, token_kind: 29, token_start: 0, token_end: 0 } -} - -fun debug_dump(src: str) { - debug_dump_from(src, 0) -} - -fun debug_dump_from(src: str, pos: u32) { - val first: u8 = byte_at(src, pos) - if is_whitespace(first) || starts_line_comment(src, pos) { - val next: u32 = skip_whitespace_and_line_comments_from(src, pos) - print_token(src, pos, next, 27); - debug_dump_from(src, next) - } else if first == 0 { - print_token(src, pos, pos, 29) - } else if is_ident_start(first) { - val next: u32 = scan_ident_end(src, pos + 1) - print_token(src, pos, next, keyword_code(slice(src, pos, next))); - debug_dump_from(src, next) - } else if is_digit(first) { - val next: u32 = scan_number_end(src, pos + 1) - print_token(src, pos, next, 1); - debug_dump_from(src, next) - } else if first == 34 { - val next: u32 = scan_string_end(src, pos + 1) - val kind: u32 = string_kind(src, pos + 1, next) - print_token(src, pos, next, kind); - debug_dump_from(src, next) - } else { - val next: u32 = punct_or_operator_end(src, pos + 1, first) - val kind: u32 = punct_or_operator_kind(src, pos + 1, first) - print_token(src, pos, next, kind); - debug_dump_from(src, next) - } -} - -fun print_token(src: str, start: u32, end: u32, kind: u32) { - io::print_str(kind_name_code(kind)); - io::print_str(" "); - io::print_str(display_text(src, start, end, kind)); - io::print_str("\n") -} - -fun starts_line_comment(src: str, pos: u32): bool { - byte_at(src, pos) == 47 && byte_at(src, pos + 1) == 47 -} - -fun skip_whitespace_and_line_comments_from(src: str, start: u32): u32 { - val byte: u8 = byte_at(src, start) - if is_whitespace(byte) { - skip_whitespace_and_line_comments_from(src, start + 1) - } else if starts_line_comment(src, start) { - skip_whitespace_and_line_comments_from(src, skip_line_comment_from(src, start + 2)) - } else { - start - } -} - -fun skip_line_comment_from(src: str, start: u32): u32 { - val byte: u8 = byte_at(src, start) - if byte == 0 || byte == 10 || byte == 13 { - start - } else { - skip_line_comment_from(src, start + 1) - } -} - -fun scan_ident_end(src: str, start: u32): u32 { - if is_ident_continue(byte_at(src, start)) { - scan_ident_end(src, start + 1) - } else { - start - } -} - -fun scan_number_end(src: str, start: u32): u32 { - if is_digit(byte_at(src, start)) { - scan_number_end(src, start + 1) - } else { - start - } -} - -fun scan_string_end(src: str, start: u32): u32 { - val byte: u8 = byte_at(src, start) - if byte == 0 { - start - } else if byte == 34 { - start + 1 - } else if byte == 92 { - if byte_at(src, start + 1) == 0 { - start + 1 - } else { - scan_string_end(src, start + 2) - } - } else { - scan_string_end(src, start + 1) - } -} - -fun string_kind(src: str, start: u32, end: u32): u32 { - if byte_at(src, start) == 0 { - 28 - } else if byte_at(src, end - 1) == 34 { - 2 - } else { - 28 - } -} - -fun punct_or_operator_end(src: str, after_first: u32, byte: u8): u32 { - if byte == 40 { - after_first - } else if byte == 41 { - after_first - } else if byte == 91 { - after_first - } else if byte == 93 { - after_first - } else if byte == 123 { - after_first - } else if byte == 125 { - after_first - } else if byte == 44 { - after_first - } else if byte == 58 { - if byte_at(src, after_first) == 58 { - after_first + 1 - } else { - after_first - } - } else if byte == 59 { - after_first - } else if byte == 46 { - after_first - } else if byte == 61 { - if byte_at(src, after_first) == 62 { - after_first + 1 - } else { - after_first - } - } else if is_operator_byte(byte) { - scan_operator_end(src, after_first) - } else { - after_first - } -} - -fun punct_or_operator_kind(src: str, after_first: u32, byte: u8): u32 { - if byte == 40 { - 3 - } else if byte == 41 { - 4 - } else if byte == 91 { - 5 - } else if byte == 93 { - 6 - } else if byte == 123 { - 7 - } else if byte == 125 { - 8 - } else if byte == 44 { - 9 - } else if byte == 58 { - if byte_at(src, after_first) == 58 { 14 } else { 10 } - } else if byte == 59 { - 11 - } else if byte == 46 { - 12 - } else if byte == 61 { - if byte_at(src, after_first) == 62 { 15 } else { 13 } - } else if is_operator_byte(byte) { - 26 - } else { - 28 - } -} - -fun scan_operator_end(src: str, start: u32): u32 { - if is_operator_byte(byte_at(src, start)) { - scan_operator_end(src, start + 1) - } else { - start - } -} - -fun scan(var lexer: Lexer) { - val trivia_start: u32 = lexer.pos - lexer.skip_whitespace_and_line_comments(); - - if trivia_start != lexer.pos { - lexer.set_token(27, trivia_start, lexer.pos); - } else if lexer.at_eof() { - lexer.set_token(29, lexer.pos, lexer.pos); - } else { - val start: u32 = lexer.pos - val byte: u8 = lexer.bump() - - if is_ident_start(byte) { - lexer.scan_ident_or_keyword(start); - } else if is_digit(byte) { - lexer.scan_number(start); - } else if byte == 34 { - lexer.scan_string(start); - } else { - lexer.scan_punct_or_operator(start, byte); - } - } -} - -fun set_token(var lexer: Lexer, kind: u32, start: u32, end: u32) { - lexer.token_kind = kind; - lexer.token_start = start; - lexer.token_end = end -} - -fun at_eof(lexer: Lexer): bool { - lexer.pos >= source_len(lexer.src) -} - -fun peek(lexer: Lexer): u8 { - lexer.peek_n(0) -} - -fun peek_n(lexer: Lexer, n: u32): u8 { - val index: u32 = lexer.pos + n - if index >= source_len(lexer.src) { - 0 - } else { - unsafe { - ptr_read(ptr_add(str_bytes(lexer.src).ptr, index)) - } - } -} - -fun bump(var lexer: Lexer): u8 { - val byte: u8 = lexer.peek() - if byte != 0 { - lexer.pos = lexer.pos + 1 - } - byte -} - -fun skip_whitespace_and_line_comments(var lexer: Lexer) { - loop { - val byte: u8 = lexer.peek() - - if is_whitespace(byte) { - lexer.bump(); - } else if byte == 47 && lexer.peek_n(1) == 47 { - lexer.bump(); - lexer.bump(); - lexer.skip_line_comment(); - } else { - break - } - } -} - -fun skip_line_comment(var lexer: Lexer) { - loop { - val byte: u8 = lexer.peek() - if byte == 0 || byte == 10 || byte == 13 { - break - } - lexer.bump(); - } -} - -fun scan_ident_or_keyword(var lexer: Lexer, start: u32) { - loop { - val byte: u8 = lexer.peek() - if is_ident_continue(byte) { - lexer.bump(); - } else { - break - } - } - - lexer.set_token(keyword_code(slice(lexer.src, start, lexer.pos)), start, lexer.pos) -} - -fun scan_number(var lexer: Lexer, start: u32) { - loop { - val byte: u8 = lexer.peek() - if is_digit(byte) { - lexer.bump(); - } else { - break - } - } - - lexer.set_token(1, start, lexer.pos) -} - -fun scan_string(var lexer: Lexer, start: u32) { - loop { - val byte: u8 = lexer.peek() - - if byte == 0 { - lexer.set_token(28, start, lexer.pos); - break - } - - lexer.bump(); - - if byte == 34 { - lexer.set_token(2, start, lexer.pos); - break - } - - if byte == 92 && !lexer.at_eof() { - lexer.bump(); - } - } -} - -fun scan_punct_or_operator(var lexer: Lexer, start: u32, byte: u8) { - if byte == 40 { - lexer.set_token(3, start, lexer.pos); - } else if byte == 41 { - lexer.set_token(4, start, lexer.pos); - } else if byte == 91 { - lexer.set_token(5, start, lexer.pos); - } else if byte == 93 { - lexer.set_token(6, start, lexer.pos); - } else if byte == 123 { - lexer.set_token(7, start, lexer.pos); - } else if byte == 125 { - lexer.set_token(8, start, lexer.pos); - } else if byte == 44 { - lexer.set_token(9, start, lexer.pos); - } else if byte == 58 { - if lexer.peek() == 58 { - lexer.bump(); - lexer.set_token(14, start, lexer.pos); - } else { - lexer.set_token(10, start, lexer.pos); - } - } else if byte == 59 { - lexer.set_token(11, start, lexer.pos); - } else if byte == 46 { - lexer.set_token(12, start, lexer.pos); - } else if byte == 61 { - if lexer.peek() == 62 { - lexer.bump(); - lexer.set_token(15, start, lexer.pos); - } else { - lexer.set_token(13, start, lexer.pos); - } - } else if is_operator_byte(byte) { - loop { - val next: u8 = lexer.peek() - if is_operator_byte(next) { - lexer.bump(); - } else { - break - } - } - lexer.set_token(26, start, lexer.pos); - } else { - lexer.set_token(28, start, lexer.pos); - } -} - -fun source_len(src: str): u32 { - str_bytes(src).len -} - -fun byte_at(src: str, index: u32): u8 { - if index >= source_len(src) { - 0 - } else { - unsafe { - ptr_read(ptr_add(str_bytes(src).ptr, index)) - } - } -} - -fun slice(src: str, start: u32, end: u32): str { - unsafe { - strings::from_raw_parts_unchecked(ptr_add(str_bytes(src).ptr, start), end - start) - } -} - -fun display_text(src: str, start: u32, end: u32, kind: u32): str { - if kind == 27 { - "" - } else if kind == 29 { - "" - } else { - slice(src, start, end) - } -} - -fun keyword_code(text: str): u32 { - if text == "fun" { - 16 - } else if text == "use" { - 17 - } else if text == "struct" { - 18 - } else if text == "enum" { - 19 - } else if text == "mod" { - 20 - } else if text == "if" { - 21 - } else if text == "else" { - 22 - } else if text == "return" { - 23 - } else if text == "val" { - 24 - } else if text == "var" { - 25 - } else { - 0 - } -} - -fun kind_name_code(code: u32): str { - if code == 0 { - "Name" - } else if code == 1 { - "Int" - } else if code == 2 { - "String" - } else if code == 3 { - "LeftParen" - } else if code == 4 { - "RightParen" - } else if code == 5 { - "LeftBracket" - } else if code == 6 { - "RightBracket" - } else if code == 7 { - "LeftBrace" - } else if code == 8 { - "RightBrace" - } else if code == 9 { - "Comma" - } else if code == 10 { - "Colon" - } else if code == 11 { - "Semicolon" - } else if code == 12 { - "Dot" - } else if code == 13 { - "Eq" - } else if code == 14 { - "DoubleColon" - } else if code == 15 { - "FatArrow" - } else if code == 16 { - "FunKw" - } else if code == 17 { - "UseKw" - } else if code == 18 { - "StructKw" - } else if code == 19 { - "EnumKw" - } else if code == 20 { - "ModKw" - } else if code == 21 { - "IfKw" - } else if code == 22 { - "ElseKw" - } else if code == 23 { - "ReturnKw" - } else if code == 24 { - "ValKw" - } else if code == 25 { - "VarKw" - } else if code == 26 { - "Operator" - } else if code == 27 { - "Trivia" - } else if code == 28 { - "Error" - } else { - "Eof" - } -} - -fun is_ident_start(byte: u8): bool { - is_ascii_alpha(byte) || byte == 95 -} - -fun is_ident_continue(byte: u8): bool { - is_ident_start(byte) || is_digit(byte) -} - -fun is_ascii_alpha(byte: u8): bool { - (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) -} - -fun is_digit(byte: u8): bool { - byte >= 48 && byte <= 57 -} - -fun is_whitespace(byte: u8): bool { - byte == 32 || byte == 9 || byte == 10 || byte == 13 -} - -fun is_operator_byte(byte: u8): bool { - byte == 33 - || byte == 37 - || byte == 38 - || byte == 42 - || byte == 43 - || byte == 45 - || byte == 47 - || byte == 60 - || byte == 62 - || byte == 63 - || byte == 94 - || byte == 124 - || byte == 126 -} - -fun print_u32(value: u32) { - if value >= 10 { - print_u32(value / 10); - } - - io::print_str(digit_name(value % 10)) -} - -fun digit_name(value: u32): str { - if value == 0 { - "0" - } else if value == 1 { - "1" - } else if value == 2 { - "2" - } else if value == 3 { - "3" - } else if value == 4 { - "4" - } else if value == 5 { - "5" - } else if value == 6 { - "6" - } else if value == 7 { - "7" - } else if value == 8 { - "8" - } else { - "9" - } -} diff --git a/stdlib/src/lib.mitki b/stdlib/src/lib.mitki deleted file mode 100644 index 3b66cf6..0000000 --- a/stdlib/src/lib.mitki +++ /dev/null @@ -1,6 +0,0 @@ -pub mod io; -pub mod env; -pub mod str; -pub mod alloc; -pub mod vec; -pub mod lexer; diff --git a/stdlib/src/str.mitki b/stdlib/src/str.mitki deleted file mode 100644 index 8ab7614..0000000 --- a/stdlib/src/str.mitki +++ /dev/null @@ -1,3 +0,0 @@ -unsafe fun from_raw_parts_unchecked(ptr: *const u8, len: u32): str { - str_from_utf8_unchecked(ptr, len) -} diff --git a/stdlib/src/vec.mitki b/stdlib/src/vec.mitki deleted file mode 100644 index 8aa5d8f..0000000 --- a/stdlib/src/vec.mitki +++ /dev/null @@ -1 +0,0 @@ -pub mod int; diff --git a/stdlib/src/vec/int.mitki b/stdlib/src/vec/int.mitki deleted file mode 100644 index 4fd2a56..0000000 --- a/stdlib/src/vec/int.mitki +++ /dev/null @@ -1,106 +0,0 @@ -use std::alloc::int as alloc; - -struct Vec { - ptr: *mut int, - len: u32, - cap: u32, -} - -enum GetResult { - Some(int), - None, -} - -fun new(): Vec { - with_capacity(4) -} - -fun with_capacity(cap: u32): Vec { - val actual_cap: u32 = if cap == 0 { 1 } else { cap } - unsafe { - Vec { ptr: alloc::alloc_items(actual_cap), len: 0, cap: actual_cap } - } -} - -fun len(vec: Vec): u32 { - vec.len -} - -fun capacity(vec: Vec): u32 { - vec.cap -} - -fun is_empty(vec: Vec): bool { - vec.len == 0 -} - -fun clear(var vec: Vec) { - vec.len = 0 -} - -fun reserve(var vec: Vec, min_cap: u32) { - val old_cap: u32 = vec.cap - if old_cap < min_cap { - val old_ptr: *mut int = vec.ptr - val old_len: u32 = vec.len - unsafe { - val next: *mut int = alloc::alloc_items(min_cap) - alloc::copy_nonoverlapping(next, old_ptr, old_len); - alloc::dealloc_items(old_ptr, old_cap); - vec.ptr = next; - } - vec.cap = min_cap - } -} - -fun push(var vec: Vec, value: int) { - val index: u32 = vec.len - val old_cap: u32 = vec.cap - if index == old_cap { - val old_ptr: *mut int = vec.ptr - val min_cap: u32 = if old_cap < 4 { 4 } else { old_cap * 2 } - unsafe { - val next: *mut int = alloc::alloc_items(min_cap) - alloc::copy_nonoverlapping(next, old_ptr, index); - alloc::dealloc_items(old_ptr, old_cap); - ptr_write(ptr_add(next, index), value); - vec.ptr = next; - } - vec.cap = min_cap - } else { - val ptr: *mut int = vec.ptr - unsafe { - ptr_write(ptr_add(ptr, index), value); - } - } - - vec.len = index + 1 -} - -fun get(vec: Vec, index: u32): GetResult { - if index < vec.len { - val ptr: *mut int = vec.ptr - unsafe { - GetResult.Some(ptr_read(ptr_add(ptr, index))) - } - } else { - GetResult.None - } -} - -fun set(var vec: Vec, index: u32, value: int) { - if index < vec.len { - val ptr: *mut int = vec.ptr - unsafe { - ptr_write(ptr_add(ptr, index), value); - } - } -} - -fun free(vec: Vec): () { - val ptr: *mut int = vec.ptr - val cap: u32 = vec.cap - unsafe { - alloc::dealloc_items(ptr, cap); - } -}