From e7738c112c787d477501fa4a87edd0e1d72159bd Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 10 Jun 2026 05:51:32 +0300 Subject: [PATCH] v2: stream flat direct calls and appends --- vlib/v2/ast/flat.v | 19 +- vlib/v2/builder/builder.v | 41 +- vlib/v2/builder/cache_headers.v | 8 +- vlib/v2/builder/cache_headers_test.v | 124 +++ vlib/v2/builder/gen_cleanc_parallel.v | 8 +- vlib/v2/builder/transform_parallel.v | 259 +---- vlib/v2/gen/arm64/arm64.v | 30 +- vlib/v2/gen/arm64/tests/arguments.v | 8 + vlib/v2/gen/arm64/tests/array_pop_value.v | 13 + .../tests/array_struct_array_field_for_in.v | 46 + vlib/v2/gen/arm64/tests/hash_pages_shape.v | 45 + .../tests/heap_struct_array_field_zero_init.v | 24 + .../gen/arm64/tests/int_array_len_zero_init.v | 14 + .../arm64/tests/large_struct_array_field.v | 36 + ...ge_struct_array_realloc_preserves_fields.v | 42 + .../tests/large_struct_partial_init_push.v | 57 + .../arm64/tests/nested_array_index_append.v | 18 + .../tests/nested_sumtype_arrayfixed_copy.v | 95 ++ .../gen/arm64/tests/nested_sumtype_int_copy.v | 87 ++ .../arm64/tests/result_i64_return_if_cast.v | 30 + vlib/v2/gen/arm64/tests/sumtype_alias_const.v | 19 + .../tests/sumtype_object_arrayfixed_type.v | 84 ++ .../tests/type_store_copy_fixed_array_len.v | 102 ++ .../type_store_many_arrays_preserve_len.v | 77 ++ vlib/v2/gen/cleanc/array.v | 85 +- vlib/v2/gen/cleanc/assign.v | 8 +- vlib/v2/gen/cleanc/cheaders.v | 28 + vlib/v2/gen/cleanc/cleanc.v | 113 +- vlib/v2/gen/cleanc/cleanc_test.v | 3 + vlib/v2/gen/cleanc/expr.v | 34 +- vlib/v2/gen/cleanc/fn.v | 145 ++- vlib/v2/gen/cleanc/mem.v | 17 + vlib/v2/gen/cleanc/mem_darwin.c.v | 22 + vlib/v2/gen/cleanc/orm.v | 2 +- vlib/v2/gen/cleanc/parallel_cache_init_test.v | 51 + vlib/v2/gen/cleanc/stmt.v | 45 +- vlib/v2/gen/cleanc/struct.v | 2 +- vlib/v2/gen/cleanc/types.v | 142 ++- vlib/v2/gen/x64/elf_tiny.v | 16 +- vlib/v2/gen/x64/macho_tiny.v | 8 +- vlib/v2/gen/x64/x64.v | 6 +- vlib/v2/parser/parser.v | 36 - vlib/v2/parser/parser_test.v | 105 ++ vlib/v2/transformer/flat_write.v | 992 +++++++++++++++++- vlib/v2/transformer/fn.v | 12 +- vlib/v2/transformer/if.v | 34 + vlib/v2/transformer/transformer.v | 63 +- vlib/v2/types/checker.v | 23 +- 48 files changed, 2708 insertions(+), 570 deletions(-) create mode 100644 vlib/v2/builder/cache_headers_test.v create mode 100644 vlib/v2/gen/arm64/tests/arguments.v create mode 100644 vlib/v2/gen/arm64/tests/array_pop_value.v create mode 100644 vlib/v2/gen/arm64/tests/array_struct_array_field_for_in.v create mode 100644 vlib/v2/gen/arm64/tests/hash_pages_shape.v create mode 100644 vlib/v2/gen/arm64/tests/heap_struct_array_field_zero_init.v create mode 100644 vlib/v2/gen/arm64/tests/int_array_len_zero_init.v create mode 100644 vlib/v2/gen/arm64/tests/large_struct_array_field.v create mode 100644 vlib/v2/gen/arm64/tests/large_struct_array_realloc_preserves_fields.v create mode 100644 vlib/v2/gen/arm64/tests/large_struct_partial_init_push.v create mode 100644 vlib/v2/gen/arm64/tests/nested_array_index_append.v create mode 100644 vlib/v2/gen/arm64/tests/nested_sumtype_arrayfixed_copy.v create mode 100644 vlib/v2/gen/arm64/tests/nested_sumtype_int_copy.v create mode 100644 vlib/v2/gen/arm64/tests/result_i64_return_if_cast.v create mode 100644 vlib/v2/gen/arm64/tests/sumtype_alias_const.v create mode 100644 vlib/v2/gen/arm64/tests/sumtype_object_arrayfixed_type.v create mode 100644 vlib/v2/gen/arm64/tests/type_store_copy_fixed_array_len.v create mode 100644 vlib/v2/gen/arm64/tests/type_store_many_arrays_preserve_len.v create mode 100644 vlib/v2/gen/cleanc/mem.v create mode 100644 vlib/v2/gen/cleanc/mem_darwin.c.v create mode 100644 vlib/v2/gen/cleanc/parallel_cache_init_test.v create mode 100644 vlib/v2/parser/parser_test.v diff --git a/vlib/v2/ast/flat.v b/vlib/v2/ast/flat.v index ac8a16d7b..b84b3df46 100644 --- a/vlib/v2/ast/flat.v +++ b/vlib/v2/ast/flat.v @@ -372,6 +372,21 @@ pub fn new_flat_builder_with_capacity(nodes_cap int, edges_cap int, strings_cap } } +// take_flat moves the built flat graph out and releases transient builder-only +// indexes. The returned FlatAst keeps owning its arena arrays. +pub fn (mut b FlatBuilder) take_flat() FlatAst { + flat := b.flat + unsafe { + b.string_ids.free() + } + b.flat = FlatAst{} + b.string_ids = map[string]int{} + b.empty_list_id = invalid_flat_node_id + b.empty_expr_id = invalid_flat_node_id + b.empty_stmt_id = invalid_flat_node_id + return flat +} + // append_file converts one legacy File into flat nodes and registers it as a // root. Designed for streaming use: the caller can drop the legacy `file` // immediately after this returns, capping peak memory at ~one file's legacy @@ -1623,7 +1638,9 @@ fn (mut b FlatBuilder) emit_simple(kind FlatNodeKind, pos token.Pos, edges []Fla } fn (mut b FlatBuilder) push_edge(edges []FlatEdge, child FlatNodeId) []FlatEdge { - mut updated := edges.clone() + // Append-and-reassign pattern (callers always do `edges = b.push_edge(edges, ...)`), + // so skipping the defensive clone is safe and avoids one copy per edge. + mut updated := unsafe { edges } updated << FlatEdge{ child_id: child } diff --git a/vlib/v2/builder/builder.v b/vlib/v2/builder/builder.v index e46f45c4d..85ce9272f 100644 --- a/vlib/v2/builder/builder.v +++ b/vlib/v2/builder/builder.v @@ -265,34 +265,18 @@ pub fn (mut b Builder) build(files []string) { sequential_transform := b.pref.no_parallel_transform || b.pref.ownership use_native_flat_pipeline := b.should_use_native_flat_pipeline() b.native_flat_pipeline_enabled = use_native_flat_pipeline - // Both paths can now consume flat directly: sequential and parallel - // transform materialize only the per-file compatibility shape needed by - // remaining legacy transformer helpers. - // - // Flat markused routes transform through flat-output wedges. Backends that - // can consume flat codegen use the direct flat-output path and drop the - // transformed []ast.File result; legacy backends keep the compatibility - // wedge that returns both flat and files. - transform_flat_only := b.should_keep_flat_for_codegen() + // The transform is flat-only for every backend: both the sequential and + // the parallel path emit cursor-native into a FlatBuilder and never + // materialize a transformed []ast.File. The legacy backends that still + // consume []ast.File (.v/eval) rehydrate once from the transformed flat + // at the codegen boundary below instead of dragging a compatibility + // path through the transform. if sequential_transform { - if transform_flat_only { - b.flat = trans.transform_flat_to_flat_direct(&b.flat, b.files) - b.files = []ast.File{} - } else { - new_flat, files_out := trans.transform_files_to_flat_via_driver(&b.flat, b.files) - b.flat = new_flat - b.files = files_out - } + b.flat = trans.transform_flat_to_flat_direct(&b.flat, b.files) } else { - // Parallel transform fans the per-file work across worker threads via - // the driver. Flat-codegen backends use worker-local FlatBuilders merged - // by append_flat and keep b.files empty; legacy consumers request the - // compatibility []ast.File result. - new_flat, files_out := b.transform_files_parallel_to_flat_via_driver(mut trans, - !transform_flat_only) - b.flat = new_flat - b.files = files_out + b.flat = b.transform_files_parallel_flat_direct(mut trans) } + b.files = []ast.File{} transform_time := time.Duration(sw.elapsed() - transform_start) print_time('Transform', transform_time) print_rss('after transform') @@ -319,6 +303,11 @@ pub fn (mut b Builder) build(files []string) { print_rss('after markused') } if !b.should_keep_flat_for_codegen() { + // .v/eval still consume legacy []ast.File: rehydrate once from the + // transformed flat at the backend boundary, then drop the flat. + if (b.pref.backend == .v && !b.pref.skip_genv) || b.pref.backend == .eval { + b.files = b.flat.to_files() + } b.flat = ast.FlatAst{} } @@ -3203,7 +3192,7 @@ fn (mut b Builder) build_native_mir_from_files(files []ast.File, arch pref.Arch, for func in mod.funcs { if func.name == dump_fn_name { eprintln('=== POST-OPT SSA DUMP: ${func.name} ===') - eprintln(' params: ${func.params}') + eprintln(' params_len: ${func.params.len}') for pi, pid in func.params { pval := mod.values[pid] eprintln(' param[${pi}]: v${pid} kind=${pval.kind} name=`${pval.name}` typ=${pval.typ}') diff --git a/vlib/v2/builder/cache_headers.v b/vlib/v2/builder/cache_headers.v index 65ed2b014..225f17d25 100644 --- a/vlib/v2/builder/cache_headers.v +++ b/vlib/v2/builder/cache_headers.v @@ -1630,7 +1630,13 @@ fn (b &Builder) import_modules_for_cached_modules(module_names []string) []Cache } } } - imports.sort(a.import_path < b.import_path) + for i := 1; i < imports.len; i++ { + mut j := i + for j > 0 && imports[j - 1].import_path > imports[j].import_path { + imports[j - 1], imports[j] = imports[j], imports[j - 1] + j-- + } + } return imports } diff --git a/vlib/v2/builder/cache_headers_test.v b/vlib/v2/builder/cache_headers_test.v new file mode 100644 index 000000000..fbf06b74f --- /dev/null +++ b/vlib/v2/builder/cache_headers_test.v @@ -0,0 +1,124 @@ +module builder + +import os +import v2.ast + +fn test_parse_fn_signature_and_return_generic_params_with_space() { + info := parse_fn_signature_and_return('pub fn run_at[A, X](mut global_app A, params RunParams) ! {') or { + assert false + return + } + assert info.signature == 'pub fn run_at[A, X](mut global_app A, params RunParams)' + assert info.return_type == '!' +} + +fn test_sanitize_staged_c_source_addresses_channel_semaphore_waits() { + source := [ + 'sync__Semaphore__wait(ch->writesem);', + 'sync__Semaphore__wait(&ch->readsem);', + ].join('\n') + sanitized := sanitize_staged_c_source(source) + assert sanitized.contains('sync__Semaphore__wait(&ch->writesem);') + assert sanitized.contains('sync__Semaphore__wait(&ch->readsem);') + assert !sanitized.contains('sync__Semaphore__wait(ch->writesem);') + assert !sanitized.contains('sync__Semaphore__wait(&&ch->readsem);') +} + +fn test_sanitize_cached_main_c_source_addresses_channel_semaphore_waits() { + source := [ + 'sync__Semaphore__wait(ch->writesem);', + 'sync__Semaphore__wait(&ch->readsem);', + ].join('\n') + sanitized := sanitize_cached_main_c_source(source) + assert sanitized.contains('sync__Semaphore__wait(&ch->writesem);') + assert sanitized.contains('sync__Semaphore__wait(&ch->readsem);') + assert !sanitized.contains('sync__Semaphore__wait(ch->writesem);') + assert !sanitized.contains('sync__Semaphore__wait(&&ch->readsem);') +} + +fn test_sanitize_cached_main_c_source_uses_c_typedef_names_for_sync_fields() { + source := [ + 'struct atomic_uintptr_t write_adr;', + 'struct pthread_rwlockattr_t attr;', + 'struct pthread_condattr_t attr;', + ].join('\n') + sanitized := sanitize_cached_main_c_source(source) + assert sanitized.contains('atomic_uintptr_t write_adr;') + assert sanitized.contains('pthread_rwlockattr_t attr;') + assert sanitized.contains('pthread_condattr_t attr;') + assert !sanitized.contains('struct atomic_uintptr_t write_adr;') + assert !sanitized.contains('struct pthread_rwlockattr_t attr;') + assert !sanitized.contains('struct pthread_condattr_t attr;') +} + +fn test_sanitize_cached_object_c_source_uses_c_typedef_names_for_sync_fields() { + source := [ + 'struct atomic_uintptr_t write_adr;', + 'struct atomic_uintptr_t read_adr;', + ].join('\n') + sanitized := sanitize_cached_object_c_source(source) + assert sanitized.contains('atomic_uintptr_t write_adr;') + assert sanitized.contains('atomic_uintptr_t read_adr;') + assert !sanitized.contains('struct atomic_uintptr_t write_adr;') + assert !sanitized.contains('struct atomic_uintptr_t read_adr;') +} + +fn test_sanitize_cached_source_guards_stdatomic_preamble_for_tcc_compat_header() { + source := [ + '#include ', + '#include ', + '#include "/tmp/vroot/thirdparty/stdatomic/nix/atomic.h"', + ].join('\n') + sanitized := sanitize_cached_object_c_source(source) + assert sanitized.contains('#ifndef __TINYC__\n#include \n#endif') + assert sanitized.contains('#define extern static\n#endif\n#include "/tmp/vroot/thirdparty/stdatomic/nix/atomic.h"') + assert sanitized.contains('#undef extern') + assert sanitized.contains('#include "/tmp/vroot/thirdparty/stdatomic/nix/atomic.h"') +} + +fn test_sanitize_cached_source_keeps_stdatomic_preamble_without_compat_header() { + source := [ + '#include ', + '#include ', + ].join('\n') + sanitized := sanitize_cached_object_c_source(source) + assert sanitized == source +} + +fn test_join_flag_strings_deduplicates_partial_overlap() { + flags := join_flag_strings('/tmp/sqlite3.c -lm -lpthread', + '/tmp/sqlite3.c -lm -lssl -lcrypto -L/tmp/ssl -lpthread') + assert flags == '/tmp/sqlite3.c -lm -lpthread -lssl -lcrypto -L/tmp/ssl' +} + +fn test_join_flag_strings_keeps_distinct_two_token_flags() { + flags := join_flag_strings('-I /tmp/a -framework Cocoa', + '-I /tmp/a -I /tmp/b -framework Foundation') + assert flags == '-I /tmp/a -framework Cocoa -I /tmp/b -framework Foundation' +} + +fn test_user_entry_stamp_files_expands_directory_to_parsed_files() { + tmp_dir := os.join_path(os.temp_dir(), 'v2_cache_entry_${os.getpid()}') + os.mkdir_all(tmp_dir) or { panic(err) } + defer { + os.rmdir_all(tmp_dir) or {} + } + source_file := os.join_path(tmp_dir, 'main.v') + header_file := os.join_path(tmp_dir, 'main.vh') + mut b := Builder{ + pref: unsafe { nil } + user_files: [tmp_dir] + files: [ + ast.File{ + name: source_file + }, + ast.File{ + name: header_file + }, + ast.File{}, + ] + } + files := b.user_entry_stamp_files() + assert files == [os.norm_path(source_file)] + assert os.norm_path(tmp_dir) !in files +} diff --git a/vlib/v2/builder/gen_cleanc_parallel.v b/vlib/v2/builder/gen_cleanc_parallel.v index 59e0020f5..152b7b610 100644 --- a/vlib/v2/builder/gen_cleanc_parallel.v +++ b/vlib/v2/builder/gen_cleanc_parallel.v @@ -103,7 +103,13 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) { chunk_costs << 0 } mut sorted_items := work_items.clone() - sorted_items.sort(a.cost > b.cost) + for i := 1; i < sorted_items.len; i++ { + mut j := i + for j > 0 && sorted_items[j - 1].cost < sorted_items[j].cost { + sorted_items[j - 1], sorted_items[j] = sorted_items[j], sorted_items[j - 1] + j-- + } + } for item in sorted_items { mut target := 0 for ci := 1; ci < chunk_idx; ci++ { diff --git a/vlib/v2/builder/transform_parallel.v b/vlib/v2/builder/transform_parallel.v index 61b169c4e..2f20e3551 100644 --- a/vlib/v2/builder/transform_parallel.v +++ b/vlib/v2/builder/transform_parallel.v @@ -6,51 +6,17 @@ module builder import v2.ast import v2.transformer import runtime -import os -import time $if !windows { - struct TransformChunkArgs { - t voidptr // &transformer.Transformer - flat &ast.FlatAst = unsafe { nil } - flat_extra_stmts [][]ast.Stmt - flat_start int - flat_end int - result_ptr voidptr - worker_ptr voidptr - worker_idx int - } - fn C.pthread_create(thread &C.pthread_t, attr voidptr, start_routine fn (voidptr) voidptr, arg voidptr) int fn C.pthread_join(thread C.pthread_t, retval voidptr) int fn C.pthread_attr_init(attr voidptr) int fn C.pthread_attr_setstacksize(attr voidptr, stacksize usize) int fn C.pthread_attr_destroy(attr voidptr) int - fn transform_chunk_thread(arg voidptr) voidptr { - a := unsafe { &TransformChunkArgs(arg) } - t := unsafe { &transformer.Transformer(a.t) } - mut w := t.new_worker_clone(a.worker_idx) - // Streaming rehydration: rehydrate one file at a time, transform it, - // then drop the legacy form. Under GC, peak per worker is one file's - // legacy AST instead of the whole chunk. - mut result := []ast.File{cap: a.flat_end - a.flat_start} - for fi := a.flat_start; fi < a.flat_end; fi++ { - file := prepared_flat_file_for_parallel_transform(a.flat, a.flat_extra_stmts, fi) or { - continue - } - result << w.transform_file_pub(file) - } - unsafe { - *(&[]ast.File(a.result_ptr)) = result - *(&voidptr(a.worker_ptr)) = voidptr(w) - } - return unsafe { nil } - } - - // TransformChunkFlatArgs is the flat-direct counterpart of - // TransformChunkArgs: the worker emits its file range cursor-native into its - // own FlatBuilder (no legacy ast.File rehydrate) and hands back the resulting + // TransformChunkFlatArgs carries one worker's slice of the parallel flat + // transform: the worker emits its file range cursor-native into its own + // FlatBuilder (no legacy ast.File rehydrate) and hands back the resulting // FlatAst, which the main thread merges via `FlatBuilder.append_flat`. struct TransformChunkFlatArgs { t voidptr // &transformer.Transformer @@ -100,197 +66,15 @@ fn flat_extra_stmts_by_file(extra_stmts map[int][]ast.Stmt, n_files int) [][]ast return out } -fn prepared_flat_file_for_parallel_transform(flat &ast.FlatAst, flat_extra_stmts [][]ast.Stmt, fi int) ?ast.File { - if fi < 0 || fi >= flat.files.len { - return none - } - fc := flat.file_cursor(fi) - stmt_list := fc.stmts() - mut stmts := []ast.Stmt{cap: stmt_list.len()} - for i in 0 .. stmt_list.len() { - stmts << stmt_list.at(i).stmt() - } - if fi < flat_extra_stmts.len { - stmts << flat_extra_stmts[fi] - } - return ast.File{ - name: fc.name() - mod: fc.mod() - selector_names: fc.selector_names() - attributes: fc.attrs().attributes() - imports: fc.imports().import_stmts() - stmts: stmts - } -} - -// transform_files_parallel_no_post_pass is the pre-post_pass portion of the -// parallel transform: pre_pass, fan-out across workers (or single-thread -// fallback), join, merge worker state, set synth_pos_counter. The transform -// always streams from the post-parse FlatAst (workers rehydrate one legacy -// ast.File at a time); `transform_files_parallel_to_flat_via_driver` runs -// `post_pass_to_flat` on the flattened output afterwards. -fn (mut b Builder) transform_files_parallel_no_post_pass(mut trans transformer.Transformer) []ast.File { - // Pre-pass: sequential (builds elided_fns and runtime const inits) - trans.pre_pass_from_flat(&b.flat) - timing_impl := os.getenv('V2_TTIME') != '' - mut sw_impl := time.new_stopwatch() - mut flat_extra_stmts := [][]ast.Stmt{} - if trans.needs_full_files_for_transform() { - extra_stmts := trans.prepare_flat_for_transform(&b.flat) - flat_extra_stmts = flat_extra_stmts_by_file(extra_stmts, b.flat.files.len) - } - if timing_impl { - eprintln(' [ttime] prepare_files_for_transform total: ${sw_impl.elapsed().milliseconds()}ms') - sw_impl = time.new_stopwatch() - } - defer { - if timing_impl { - eprintln(' [ttime] per-file fanout: ${sw_impl.elapsed().milliseconds()}ms') - } - } - - // Workers stream the rehydration per file (one legacy ast.File in flight - // per worker at a time) directly from the post-parse FlatAst. - n_jobs := runtime.nr_jobs() - n_files := b.flat.files.len - $if windows { - mut result := []ast.File{cap: n_files} - for fi in 0 .. n_files { - file := prepared_flat_file_for_parallel_transform(&b.flat, flat_extra_stmts, fi) or { - continue - } - result << trans.transform_file_pub(file) - } - return result - } $else { - if n_files <= 1 || n_jobs <= 1 { - mut result := []ast.File{cap: n_files} - for fi in 0 .. n_files { - file := prepared_flat_file_for_parallel_transform(&b.flat, flat_extra_stmts, fi) or { - continue - } - result << trans.transform_file_pub(file) - } - return result - } - - // Split the files into contiguous [start,end) ranges, one per worker. - mut bucket_indices := [][]int{len: n_jobs} - chunk_size := (n_files + n_jobs - 1) / n_jobs - mut i := 0 - mut bw := 0 - for i < n_files { - end := if i + chunk_size < n_files { i + chunk_size } else { n_files } - for j in i .. end { - bucket_indices[bw] << j - } - i = end - bw++ - } - - mut chunk_results := [][]ast.File{len: n_jobs} - mut worker_ptrs := []voidptr{len: n_jobs, init: unsafe { nil }} - mut thread_ids := []C.pthread_t{len: n_jobs} - mut args := []TransformChunkArgs{cap: n_jobs} - - // ARM64-compiled code uses much more stack per function (one slot per SSA - // value, no reuse). Increase worker thread stack size to 64 MB so deeply - // recursive transform functions don't overflow the default 512 KB stack. - attr_buf := [64]u8{} - attr := unsafe { voidptr(&attr_buf[0]) } - C.pthread_attr_init(attr) - C.pthread_attr_setstacksize(attr, 64 * 1024 * 1024) - - mut chunk_idx := 0 - for w in 0 .. n_jobs { - idxs := bucket_indices[w] - if idxs.len == 0 { - continue - } - args << TransformChunkArgs{ - t: unsafe { voidptr(trans) } - flat: unsafe { &b.flat } - flat_extra_stmts: flat_extra_stmts - flat_start: idxs[0] - flat_end: idxs[idxs.len - 1] + 1 - result_ptr: unsafe { voidptr(&chunk_results[chunk_idx]) } - worker_ptr: unsafe { voidptr(&worker_ptrs[chunk_idx]) } - worker_idx: chunk_idx - } - C.pthread_create(unsafe { &thread_ids[chunk_idx] }, attr, transform_chunk_thread, - unsafe { voidptr(&args[chunk_idx]) }) - chunk_idx++ - } - C.pthread_attr_destroy(attr) - - // Wait for all workers - for ci := 0; ci < chunk_idx; ci++ { - C.pthread_join(thread_ids[ci], unsafe { nil }) - } - - // Scatter each worker's results back to original file order and merge - // accumulated state. bucket_indices[w] lists the original indices the - // w-th spawned worker processed, in the same order it produced results. - mut result := []ast.File{len: n_files} - mut ci := 0 - for w in 0 .. n_jobs { - idxs := bucket_indices[w] - if idxs.len == 0 { - continue - } - chunk_files := chunk_results[ci] - for k, fi in idxs { - if k < chunk_files.len { - result[fi] = chunk_files[k] - } - } - worker := unsafe { &transformer.Transformer(worker_ptrs[ci]) } - trans.merge_worker(worker) - ci++ - } - // Set synth_pos_counter past all worker ranges to avoid ID collisions in post_pass. - trans.set_synth_pos_counter(-(chunk_idx * 100_000) - 1) - return result - } -} - -// transform_files_parallel_to_flat_via_driver is the parallel counterpart to -// `Transformer.transform_files_to_flat_via_driver` (s162). Same external -// shape (returns `(ast.FlatAst, []ast.File)`) but uses the s161 driver -// instead of the `legacy post_pass + flatten_files` boundary: -// -// 1. Per-worker transform via `transform_files_parallel_no_post_pass` -// (skips legacy `post_pass`). -// 2. Compute `generated_fns_parts` against the un-post_pass'd result -// (parts helper walks `[]ast.File` for module routing). -// 3. Flatten the un-post_pass'd files into a fresh FlatBuilder. -// 4. Run `post_pass_to_flat(mut builder, generated_parts)` — the s161 -// driver appends/prepends/replaces stmts on the flat directly. -// 5. Apply the non-file post_pass tail via `apply_post_pass_tail`. -// -// Bit-equivalent to legacy in tree structure (each file's stmts list holds -// the same content) but NOT bit-equal in full `signature()`: the -// `.file.extra` slot stores `intern(mod)` as a raw intern index, and the -// two paths intern strings in different orders. Compare via per-file -// `subtree_signature` of the stmts list (file root edge 2) to assert -// structural parity. Same property as s162's sequential wedge. -// -// `keep_files` controls whether the transformed `[]ast.File` is materialized -// for the caller. Flat-codegen backends (cleanc/c/x64/arm64) drop `b.files` -// after transform, so they pass `keep_files = false`: the function then mirrors -// the flat-direct tail (`post_pass_to_flat` + `apply_post_pass_tail_from_flat`, -// no legacy file post-pass, whose edits are already on the flat) and frees the -// transient `result` here instead of threading it back only to be dropped. -// The `.v`/eval backends still consume the files, so they pass `keep_files = true`. // transform_files_parallel_flat_direct is the flat-native parallel transform -// for flat-codegen backends. It mirrors the sequential +// used by every backend. It mirrors the sequential // `transform_flat_to_flat_direct` (pre_pass -> prepare -> per-file cursor // transform -> post_pass tail) but fans the per-file loop across worker threads: // each worker emits its contiguous file range cursor-native into its OWN // FlatBuilder, then the main thread concatenates them in file order via -// `FlatBuilder.append_flat`. This replaces the legacy per-worker `ast.File` -// rehydrate (`transform_file_pub`) + main-thread `append_file` flatten, so no -// legacy AST is materialised on the default cleanc/c/x64 build path. +// `FlatBuilder.append_flat`. No legacy ast.File is materialised; backends that +// still consume []ast.File (.v/eval) rehydrate from the transformed flat at +// the codegen boundary in builder.v. // // Per-worker synth-position disjointness is handled by `new_worker_clone` // (it offsets synth_pos_counter by `-worker_idx * 100_000`); worker state is @@ -392,32 +176,3 @@ fn (mut b Builder) transform_files_parallel_flat_direct(mut trans transformer.Tr trans.apply_post_pass_tail_from_flat(&out.flat) return out.flat } - -fn (mut b Builder) transform_files_parallel_to_flat_via_driver(mut trans transformer.Transformer, keep_files bool) (ast.FlatAst, []ast.File) { - if !keep_files { - // Flat-codegen backends (cleanc/c/x64/arm64): workers transform - // cursor-native into per-worker FlatBuilders, merged via append_flat — - // no legacy ast.File ever materialises. This is the flat-native parallel - // transform that the merge primitive (FlatBuilder.append_flat) unlocked. - return b.transform_files_parallel_flat_direct(mut trans), []ast.File{} - } - // `.v`/eval backends still consume the transformed []ast.File: keep the - // legacy per-worker rehydrate-then-flatten path below. - mut result := b.transform_files_parallel_no_post_pass(mut trans) - mut builder := ast.new_flat_builder() - for file in result { - builder.append_file(file) - } - // Compute parts against the freshly-appended flat (s166): no longer - // needs `result []ast.File` for explicit_str / module routing — both - // `explicit_str_method_fn_names_from_flat` (s165) and - // `generated_fn_module_from_flat` (s164) walk `builder.flat` directly. - generated_parts := trans.generated_fns_parts_from_flat(&builder.flat) - trans.post_pass_to_flat(mut builder, generated_parts) - trans.post_pass_files_with_generated_parts(mut result, generated_parts) - // The compatibility files now receive the same file-mutating post-pass - // edits as the flat output, so run the non-file tail on those files for - // downstream legacy consumers. - trans.apply_post_pass_tail(result) - return builder.flat, result -} diff --git a/vlib/v2/gen/arm64/arm64.v b/vlib/v2/gen/arm64/arm64.v index a26e423c5..da759723e 100644 --- a/vlib/v2/gen/arm64/arm64.v +++ b/vlib/v2/gen/arm64/arm64.v @@ -977,12 +977,14 @@ pub fn (mut g Gen) gen_func(func_idx int) { continue } if vv.name.contains('cleanc__Gen__expr') { - eprintln('ARM64 FUNCREF val=${i} name=${vv.name} typ=${vv.typ}') + vv_typ := vv.typ.str() + eprintln('ARM64 FUNCREF val=${i} name=${vv.name} typ=${vv_typ}') } } for f in g.mod.funcs { if f.name.contains('cleanc__Gen__expr') { - eprintln('ARM64 FUNCDECL id=${f.id} name=${f.name} typ=${f.typ} params=${f.params}') + f_typ := f.typ.str() + eprintln('ARM64 FUNCDECL id=${f.id} name=${f.name} typ=${f_typ} params_len=${f.params.len}') } } eprintln('ARM64 FUNCREFS fn=${func_name} end') @@ -1094,13 +1096,13 @@ pub fn (mut g Gen) gen_func(func_idx int) { if val.uses.len == 0 { if opcode == .bitcast && instr.operands.len == 0 { if trace_skip_dead { - eprintln('ARM64 SKIP_DEAD fn=${func_name} val=${val_id} op=bitcast ops=${instr.operands} uses_len=${val.uses.len} uses=${val.uses}') + eprintln('ARM64 SKIP_DEAD fn=${func_name} val=${val_id} op=bitcast ops_len=${instr.operands.len} uses_len=${val.uses.len}') } continue } if opcode == .assign { if trace_skip_dead { - eprintln('ARM64 SKIP_DEAD fn=${func_name} val=${val_id} op=assign ops=${instr.operands} uses_len=${val.uses.len} uses=${val.uses}') + eprintln('ARM64 SKIP_DEAD fn=${func_name} val=${val_id} op=assign ops_len=${instr.operands.len} uses_len=${val.uses.len}') } continue } @@ -1270,7 +1272,7 @@ pub fn (mut g Gen) gen_func(func_idx int) { typ_kind = '${typ.kind}' typ_size = g.type_size(vv.typ) if typ.kind == .struct_t { - typ_desc = 'fields=${typ.field_names} ftypes=${typ.fields}' + typ_desc = 'fields_len=${typ.field_names.len} ftypes_len=${typ.fields.len}' } else if typ.kind == .ptr_t { typ_desc = 'elem=${typ.elem_type}' } @@ -1279,9 +1281,9 @@ pub fn (mut g Gen) gen_func(func_idx int) { instr := g.mod.instrs[vv.index] op = '${g.selected_opcode(instr)}' blk = instr.block - operands = '${instr.operands}' + operands = 'len=${instr.operands.len}' } - uses = '${vv.uses}' + uses = 'len=${vv.uses.len}' } eprintln('ARM64 STACKMAP ${func_name} val=${vid} off=${off} kind=${kind} blk=${blk} op=${op} ops=${operands} uses=${uses} typ=${typ_id}/${typ_kind} size=${typ_size} tdesc=`${typ_desc}` name=`${name}`') } @@ -1294,7 +1296,7 @@ pub fn (mut g Gen) gen_func(func_idx int) { vv := g.mod.values[vid] if vv.kind == .instruction { instr := g.mod.instrs[vv.index] - alloca_ops = '${instr.operands}' + alloca_ops = 'len=${instr.operands.len}' if vv.typ > 0 && vv.typ < g.mod.type_store.types.len { typ := g.mod.type_store.types[vv.typ] if typ.kind == .ptr_t && typ.elem_type > 0 @@ -1315,7 +1317,7 @@ pub fn (mut g Gen) gen_func(func_idx int) { eprintln('ARM64 BLOCKS ${func_name} begin') for bi, blk_id in func_blocks { blk := g.mod.blocks[blk_id] - eprintln('ARM64 BLOCK ${func_name} order=${bi} id=${blk_id} val=${blk.val_id} preds=${blk.preds} succs=${blk.succs} instrs=${blk.instrs}') + eprintln('ARM64 BLOCK ${func_name} order=${bi} id=${blk_id} val=${blk.val_id} preds_len=${blk.preds.len} succs_len=${blk.succs.len} instrs_len=${blk.instrs.len}') for val_id in blk.instrs { if val_id <= 0 || val_id >= g.mod.values.len { continue @@ -1326,7 +1328,7 @@ pub fn (mut g Gen) gen_func(func_idx int) { if val.kind == .instruction { instr := g.mod.instrs[val.index] op = '${g.selected_opcode(instr)}' - operands = '${instr.operands}' + operands = 'len=${instr.operands.len}' } mut callee_info := '' if val.kind == .instruction { @@ -1342,7 +1344,7 @@ pub fn (mut g Gen) gen_func(func_idx int) { } } } - eprintln('ARM64 BLOCK INSTR ${func_name} blk=${blk_id} val=${val_id} kind=${val.kind} op=${op} ops=${operands} uses=${val.uses}${callee_info}') + eprintln('ARM64 BLOCK INSTR ${func_name} blk=${blk_id} val=${val_id} kind=${val.kind} op=${op} ops=${operands} uses_len=${val.uses.len}${callee_info}') } } eprintln('ARM64 BLOCKS ${func_name} end') @@ -1581,7 +1583,7 @@ fn (mut g Gen) gen_instr(val_id int) { trace_val := g.env_trace_val.len > 0 && (g.env_trace_val == '*' || g.cur_func_name == g.env_trace_val) if trace_val { - eprintln('ARM64 VAL fn=${g.cur_func_name} val=${val_id} opi=${int(op)} off=${g.macho.text_data.len - g.curr_offset} ops=${instr_operands}') + eprintln('ARM64 VAL fn=${g.cur_func_name} val=${val_id} opi=${int(op)} off=${g.macho.text_data.len - g.curr_offset} ops_len=${instr_operands.len}') } trace_instr := g.env_trace_instr.len > 0 && (g.env_trace_instr == '*' || g.cur_func_name == g.env_trace_instr) @@ -1596,7 +1598,7 @@ fn (mut g Gen) gen_instr(val_id int) { width = typ.width is_unsigned = typ.is_unsigned } - eprintln('ARM64 INSTR fn=${g.cur_func_name} val=${val_id} op=${op} orig=${instr.op} typ=${typ_id} kind=${kind} width=${width} unsigned=${is_unsigned} ops=${instr_operands}') + eprintln('ARM64 INSTR fn=${g.cur_func_name} val=${val_id} op=${op} orig=${instr.op} typ=${typ_id} kind=${kind} width=${width} unsigned=${is_unsigned} ops_len=${instr_operands.len}') } if op == .store && g.try_emit_simple_scalar_store(instr_idx) { return @@ -9521,7 +9523,7 @@ fn (mut g Gen) allocate_registers(func_idx int) { g.used_regs.sort() if trace_ra { - eprintln('REGALLOC fn=${func_name} intervals=${iv_val_ids.len} calls=${call_indices.len} allocated=${g.reg_map.len} used_regs=${g.used_regs} total_instrs=${total_instrs}') + eprintln('REGALLOC fn=${func_name} intervals=${iv_val_ids.len} calls=${call_indices.len} allocated=${g.reg_map.len} used_regs_len=${g.used_regs.len} total_instrs=${total_instrs}') for val_id, reg in g.reg_map { if mut iv := intervals[val_id] { eprintln(' val=${val_id} -> x${reg} [${iv.start},${iv.end}] has_call=${iv.has_call}') diff --git a/vlib/v2/gen/arm64/tests/arguments.v b/vlib/v2/gen/arm64/tests/arguments.v new file mode 100644 index 000000000..a16f5bd42 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/arguments.v @@ -0,0 +1,8 @@ +import os + +fn main() { + println(os.args.len > 0) + if os.args.len > 0 { + println(os.args[0].len > 0) + } +} diff --git a/vlib/v2/gen/arm64/tests/array_pop_value.v b/vlib/v2/gen/arm64/tests/array_pop_value.v new file mode 100644 index 000000000..ca0c3e6ed --- /dev/null +++ b/vlib/v2/gen/arm64/tests/array_pop_value.v @@ -0,0 +1,13 @@ +fn main() { + mut matrix := [][]int{len: 4} + mut worklist := []int{} + worklist << 2 + idx := worklist.pop() + matrix[idx] << 10 + matrix[idx] << 5 + mut total := 0 + for value in matrix[2] { + total += value + } + println(total) +} diff --git a/vlib/v2/gen/arm64/tests/array_struct_array_field_for_in.v b/vlib/v2/gen/arm64/tests/array_struct_array_field_for_in.v new file mode 100644 index 000000000..8de2ef665 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/array_struct_array_field_for_in.v @@ -0,0 +1,46 @@ +module main + +struct Entry { + name string + initial_data []u8 + size int +} + +fn collect(entries []Entry) []u8 { + mut out := []u8{} + for entry in entries { + if entry.initial_data.len > 0 { + out << entry.initial_data + continue + } + for _ in 0 .. entry.size { + out << u8(0) + } + } + return out +} + +fn main() { + entries := [ + Entry{ + name: 'a' + initial_data: [u8(1), 2, 3] + size: 0 + }, + Entry{ + name: 'b' + initial_data: []u8{} + size: 4 + }, + Entry{ + name: 'c' + initial_data: [u8(9)] + size: 0 + }, + ] + out := collect(entries) + println(out.len) + println(out[0]) + println(out[5]) + println(out[7]) +} diff --git a/vlib/v2/gen/arm64/tests/hash_pages_shape.v b/vlib/v2/gen/arm64/tests/hash_pages_shape.v new file mode 100644 index 000000000..c54d7a37b --- /dev/null +++ b/vlib/v2/gen/arm64/tests/hash_pages_shape.v @@ -0,0 +1,45 @@ +module main + +const page_size = 64 + +@[direct_array_access] +fn fill_hash(data &u8, data_len int, out &u8) { + for i in 0 .. 32 { + unsafe { + out[i] = if i < data_len { data[i] + 1 } else { u8(i) } + } + } +} + +fn hash_pages_shape(data &u8, mut hashes []u8, page_start int, page_end int, code_limit int) { + mut hash_buf := [32]u8{} + for page := page_start; page < page_end; page++ { + start := page * page_size + mut end := start + page_size + if end > code_limit { + end = code_limit + } + unsafe { + fill_hash(data + start, end - start, &hash_buf[0]) + } + hash_offset := page * 32 + for i in 0 .. 32 { + hashes[hash_offset + i] = hash_buf[i] + } + } +} + +fn main() { + mut data := []u8{len: 150} + for i in 0 .. data.len { + data[i] = u8(i % 251) + } + mut hashes := []u8{len: 96} + unsafe { + hash_pages_shape(data.data, mut hashes, 0, 3, data.len) + } + println(hashes[0]) + println(hashes[32]) + println(hashes[64]) + println(hashes[95]) +} diff --git a/vlib/v2/gen/arm64/tests/heap_struct_array_field_zero_init.v b/vlib/v2/gen/arm64/tests/heap_struct_array_field_zero_init.v new file mode 100644 index 000000000..7b6a619f0 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/heap_struct_array_field_zero_init.v @@ -0,0 +1,24 @@ +module main + +struct GenShape { +mut: + cache []int + stack []bool +} + +fn new_gen_shape(n int) &GenShape { + return &GenShape{ + cache: []int{len: n} + stack: []bool{len: n} + } +} + +fn main() { + g := new_gen_shape(256) + mut sum := 0 + for i := 0; i < g.cache.len; i++ { + sum += g.cache[i] + } + println(sum) + println(g.cache.len) +} diff --git a/vlib/v2/gen/arm64/tests/int_array_len_zero_init.v b/vlib/v2/gen/arm64/tests/int_array_len_zero_init.v new file mode 100644 index 000000000..e5aa1a5fc --- /dev/null +++ b/vlib/v2/gen/arm64/tests/int_array_len_zero_init.v @@ -0,0 +1,14 @@ +module main + +fn sum_cache(n int) int { + cache := []int{len: n} + mut sum := 0 + for i := 0; i < cache.len; i++ { + sum += cache[i] + } + return sum +} + +fn main() { + println(sum_cache(256)) +} diff --git a/vlib/v2/gen/arm64/tests/large_struct_array_field.v b/vlib/v2/gen/arm64/tests/large_struct_array_field.v new file mode 100644 index 000000000..2b24a7bef --- /dev/null +++ b/vlib/v2/gen/arm64/tests/large_struct_array_field.v @@ -0,0 +1,36 @@ +module main + +struct TypeShape { + kind int + width int + elem_type int + len int + fields []int + field_names []string + params []int + ret_type int + is_c_struct bool + is_union bool + is_unsigned bool +} + +fn fixed_array_size(types []TypeShape, typ_id int) int { + typ := types[typ_id] + if typ.kind == 4 { + return typ.len * 4 + } + return 0 +} + +fn main() { + types := [ + TypeShape{ + kind: 4 + elem_type: 1 + len: 128 + fields: []int{} + params: []int{} + }, + ] + println(fixed_array_size(types, 0)) +} diff --git a/vlib/v2/gen/arm64/tests/large_struct_array_realloc_preserves_fields.v b/vlib/v2/gen/arm64/tests/large_struct_array_realloc_preserves_fields.v new file mode 100644 index 000000000..65bafbe8b --- /dev/null +++ b/vlib/v2/gen/arm64/tests/large_struct_array_realloc_preserves_fields.v @@ -0,0 +1,42 @@ +module main + +enum TypeKindShape { + void_t + int_t + float_t + ptr_t + array_t + struct_t + func_t + label_t + metadata_t +} + +struct TypeShape { + kind TypeKindShape + width int + elem_type int + len int + fields []int + field_names []string + params []int + ret_type int + is_c_struct bool + is_union bool + is_unsigned bool +} + +fn main() { + mut types := []TypeShape{} + for i := 0; i < 400; i++ { + types << TypeShape{ + kind: .array_t + elem_type: 1 + len: 64 + i + } + } + println(types[0].len) + println(types[63].len) + println(types[128].len) + println(types[399].len) +} diff --git a/vlib/v2/gen/arm64/tests/large_struct_partial_init_push.v b/vlib/v2/gen/arm64/tests/large_struct_partial_init_push.v new file mode 100644 index 000000000..260bcd438 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/large_struct_partial_init_push.v @@ -0,0 +1,57 @@ +module main + +enum TypeKindShape { + void_t + int_t + float_t + ptr_t + array_t + struct_t + func_t + label_t + metadata_t +} + +struct TypeShape { + kind TypeKindShape + width int + elem_type int + len int + fields []int + field_names []string + params []int + ret_type int + is_c_struct bool + is_union bool + is_unsigned bool +} + +struct TypeStoreShape { +mut: + types []TypeShape + cache map[string]int +} + +fn (mut ts TypeStoreShape) register(t TypeShape) int { + id := ts.types.len + ts.types << t + return id +} + +fn (mut ts TypeStoreShape) get_array(elem int, length int) int { + return ts.register(TypeShape{ + kind: .array_t + elem_type: elem + len: length + }) +} + +fn main() { + mut ts := TypeStoreShape{} + ts.cache = map[string]int{} + id := ts.get_array(1, 128) + typ := ts.types[id] + println(typ.len) + println(typ.elem_type) + println(int(typ.kind)) +} diff --git a/vlib/v2/gen/arm64/tests/nested_array_index_append.v b/vlib/v2/gen/arm64/tests/nested_array_index_append.v new file mode 100644 index 000000000..98363aadc --- /dev/null +++ b/vlib/v2/gen/arm64/tests/nested_array_index_append.v @@ -0,0 +1,18 @@ +module main + +fn main() { + mut lists := [][]int{len: 4} + for i := 0; i < lists.len; i++ { + lists[i] << (i + 10) + lists[i] << (i + 20) + } + mut total := 0 + for i := 0; i < lists.len; i++ { + total += lists[i].len + total += lists[i][0] + total += lists[i][1] + } + println(total) + println(lists[3][0]) + println(lists[3][1]) +} diff --git a/vlib/v2/gen/arm64/tests/nested_sumtype_arrayfixed_copy.v b/vlib/v2/gen/arm64/tests/nested_sumtype_arrayfixed_copy.v new file mode 100644 index 000000000..c95fbe028 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/nested_sumtype_arrayfixed_copy.v @@ -0,0 +1,95 @@ +module main + +struct EmptyExpr { +} + +struct BasicLiteral { + value string +} + +struct Ident { + name string +} + +type Type = ArrayFixedType | ArrayType + +type Expr = ArrayInitExpr | BasicLiteral | EmptyExpr | Ident | Type + +struct ArrayType { + elem_type Expr +} + +struct ArrayFixedType { + len Expr + elem_type Expr +} + +struct ArrayInitExpr { + typ Expr + exprs []Expr +} + +struct FieldDecl { + name string + value Expr +} + +fn parse_digits(s string) int { + mut out := 0 + for i := 0; i < s.len; i++ { + out = out * 10 + int(s[i] - `0`) + } + return out +} + +fn fixed_len_from_type(typ Type) int { + return match typ { + ArrayFixedType { + if typ.len is BasicLiteral { + parse_digits(typ.len.value) + } else { + -2 + } + } + ArrayType { + -3 + } + } +} + +fn type_to_len(expr Expr) int { + return match expr { + Type { + fixed_len_from_type(expr) + } + else { + -1 + } + } +} + +fn field_len(field FieldDecl) int { + if field.value is ArrayInitExpr { + arr := field.value + return type_to_len(arr.typ) + } + return -4 +} + +fn main() { + field := FieldDecl{ + name: 'buf' + value: Expr(ArrayInitExpr{ + typ: Expr(Type(ArrayFixedType{ + len: Expr(BasicLiteral{ + value: '128' + }) + elem_type: Expr(Ident{ + name: 'u8' + }) + })) + exprs: []Expr{} + }) + } + println(field_len(field)) +} diff --git a/vlib/v2/gen/arm64/tests/nested_sumtype_int_copy.v b/vlib/v2/gen/arm64/tests/nested_sumtype_int_copy.v new file mode 100644 index 000000000..4d1e54cdb --- /dev/null +++ b/vlib/v2/gen/arm64/tests/nested_sumtype_int_copy.v @@ -0,0 +1,87 @@ +module main + +struct EmptyExpr { +} + +struct BasicLiteral { + value int +} + +struct Ident { + name string +} + +type Type = ArrayFixedType | ArrayType + +type Expr = ArrayInitExpr | BasicLiteral | EmptyExpr | Ident | Type + +struct ArrayType { + elem_type Expr +} + +struct ArrayFixedType { + len Expr + elem_type Expr +} + +struct ArrayInitExpr { + typ Expr + exprs []Expr +} + +struct FieldDecl { + name string + value Expr +} + +fn fixed_len_from_type(typ Type) int { + return match typ { + ArrayFixedType { + if typ.len is BasicLiteral { + typ.len.value + } else { + -2 + } + } + ArrayType { + -3 + } + } +} + +fn type_to_len(expr Expr) int { + return match expr { + Type { + fixed_len_from_type(expr) + } + else { + -1 + } + } +} + +fn field_len(field FieldDecl) int { + if field.value is ArrayInitExpr { + arr := field.value + return type_to_len(arr.typ) + } + return -4 +} + +fn main() { + field := FieldDecl{ + name: 'buf' + value: Expr(ArrayInitExpr{ + typ: Expr(Type(ArrayFixedType{ + len: Expr(BasicLiteral{ + value: 128 + }) + elem_type: Expr(Ident{ + name: 'u8' + }) + })) + exprs: []Expr{} + }) + } + println(field_len(field)) +} diff --git a/vlib/v2/gen/arm64/tests/result_i64_return_if_cast.v b/vlib/v2/gen/arm64/tests/result_i64_return_if_cast.v new file mode 100644 index 000000000..1e55a6269 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/result_i64_return_if_cast.v @@ -0,0 +1,30 @@ +module main + +fn to_i64_direct(x u64) i64 { + return i64(x) +} + +fn to_i64_if_expr(x u64, neg bool) i64 { + return if neg { -i64(x) } else { i64(x) } +} + +fn to_i64_result_direct(x u64) !i64 { + return i64(x) +} + +fn to_i64_result_if_expr(x u64, neg bool) !i64 { + return if neg { -i64(x) } else { i64(x) } +} + +fn to_i64_result_if_var(x u64, neg bool) !i64 { + y := if neg { -i64(x) } else { i64(x) } + return y +} + +fn main() { + println(to_i64_direct(6)) + println(to_i64_if_expr(6, false)) + println(to_i64_result_direct(6) or { 99 }) + println(to_i64_result_if_expr(6, false) or { 99 }) + println(to_i64_result_if_var(6, false) or { 99 }) +} diff --git a/vlib/v2/gen/arm64/tests/sumtype_alias_const.v b/vlib/v2/gen/arm64/tests/sumtype_alias_const.v new file mode 100644 index 000000000..05ec779a3 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/sumtype_alias_const.v @@ -0,0 +1,19 @@ +module main + +type EmptyExpr = u8 + +struct Node { + value int +} + +type Expr = EmptyExpr | Node + +const empty_expr = Expr(EmptyExpr(0)) + +fn main() { + if empty_expr is EmptyExpr { + println('ok') + return + } + println('bad') +} diff --git a/vlib/v2/gen/arm64/tests/sumtype_object_arrayfixed_type.v b/vlib/v2/gen/arm64/tests/sumtype_object_arrayfixed_type.v new file mode 100644 index 000000000..8473c02b4 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/sumtype_object_arrayfixed_type.v @@ -0,0 +1,84 @@ +module main + +type Type = ArrayFixed | Primitive + +struct Primitive { + size int +} + +struct ArrayFixed { + len int + elem_type Type +} + +struct ObjectCommon { + name string + typ Type +} + +struct Const { + ObjectCommon +} + +struct Global { + ObjectCommon +} + +type Object = Const | Global | Type + +struct Scope { +mut: + objects map[string]Object +} + +fn (s &Scope) lookup(name string) ?Object { + if obj := s.objects[name] { + return obj + } + return none +} + +fn (obj &Object) typ() Type { + match obj { + Const { + return obj.typ + } + Global { + return obj.typ + } + Type { + return obj + } + } +} + +fn array_len(t Type) int { + return match t { + ArrayFixed { + t.len + } + Primitive { + -1 + } + } +} + +fn main() { + mut s := Scope{ + objects: map[string]Object{} + } + s.objects['buf'] = Object(Global{ + name: 'buf' + typ: Type(ArrayFixed{ + len: 128 + elem_type: Type(Primitive{ + size: 8 + }) + }) + }) + if obj := s.lookup('buf') { + println(array_len(obj.typ())) + return + } + println(-2) +} diff --git a/vlib/v2/gen/arm64/tests/type_store_copy_fixed_array_len.v b/vlib/v2/gen/arm64/tests/type_store_copy_fixed_array_len.v new file mode 100644 index 000000000..a2be03124 --- /dev/null +++ b/vlib/v2/gen/arm64/tests/type_store_copy_fixed_array_len.v @@ -0,0 +1,102 @@ +module main + +enum TypeKindShape { + void_t + int_t + float_t + ptr_t + array_t + struct_t + func_t + label_t + metadata_t +} + +struct TypeShape { + kind TypeKindShape + width int + elem_type int + len int + fields []int + field_names []string + params []int + ret_type int + is_c_struct bool + is_union bool + is_unsigned bool +} + +struct TypeStoreShape { +mut: + types []TypeShape + cache map[string]int +} + +struct TargetShape { + ptr_size int + endian_little bool +} + +struct ModuleShape { +mut: + name string + target TargetShape + ssa_mod &int = unsafe { nil } + env &int = unsafe { nil } + type_store TypeStoreShape + values []int + instrs []int + blocks []int + funcs []int + globals []int +} + +fn lower(src &ModuleShape) ModuleShape { + return ModuleShape{ + name: src.name + target: src.target + ssa_mod: src.ssa_mod + env: src.env + type_store: src.type_store + values: src.values.clone() + instrs: src.instrs.clone() + blocks: src.blocks.clone() + funcs: src.funcs.clone() + globals: src.globals.clone() + } +} + +fn fixed_array_size(m &ModuleShape, typ_id int) int { + typ := m.type_store.types[typ_id] + if typ.kind == .array_t { + return typ.len * 4 + } + return 0 +} + +fn main() { + mut src := ModuleShape{} + src.name = 'main' + src.target = TargetShape{ + ptr_size: 8 + endian_little: true + } + src.type_store.cache = map[string]int{} + src.type_store.types = [ + TypeShape{ + kind: .array_t + elem_type: 1 + len: 128 + fields: []int{} + params: []int{} + }, + ] + src.values = [1, 2, 3] + src.instrs = [4, 5] + src.blocks = [6] + src.funcs = [7] + src.globals = [8] + src.type_store.cache['x'] = 0 + dst := lower(&src) + println(fixed_array_size(&dst, 0)) +} diff --git a/vlib/v2/gen/arm64/tests/type_store_many_arrays_preserve_len.v b/vlib/v2/gen/arm64/tests/type_store_many_arrays_preserve_len.v new file mode 100644 index 000000000..c7f8160df --- /dev/null +++ b/vlib/v2/gen/arm64/tests/type_store_many_arrays_preserve_len.v @@ -0,0 +1,77 @@ +module main + +enum TypeKindShape { + void_t + int_t + float_t + ptr_t + array_t + struct_t + func_t + label_t + metadata_t +} + +struct TypeShape { + kind TypeKindShape + width int + elem_type int + len int + fields []int + field_names []string + params []int + ret_type int + is_c_struct bool + is_union bool + is_unsigned bool +} + +struct TypeStoreShape { +mut: + types []TypeShape + cache map[string]int +} + +struct ModuleShape { +mut: + type_store TypeStoreShape +} + +fn (mut ts TypeStoreShape) register(t TypeShape) int { + id := ts.types.len + ts.types << t + return id +} + +fn (mut ts TypeStoreShape) get_array(elem int, length int) int { + key := 'a${elem}_${length}' + if id := ts.cache[key] { + return id + } + id := ts.register(TypeShape{ + kind: .array_t + elem_type: elem + len: length + }) + ts.cache[key] = id + return id +} + +fn main() { + mut ts := TypeStoreShape{ + cache: map[string]int{} + } + for i := 0; i < 400; i++ { + ts.get_array(1, 64 + i) + } + src := ModuleShape{ + type_store: ts + } + dst := ModuleShape{ + type_store: src.type_store + } + println(dst.type_store.types[0].len) + println(dst.type_store.types[63].len) + println(dst.type_store.types[128].len) + println(dst.type_store.types[399].len) +} diff --git a/vlib/v2/gen/cleanc/array.v b/vlib/v2/gen/cleanc/array.v index d85b21f9c..0de5c7ddf 100644 --- a/vlib/v2/gen/cleanc/array.v +++ b/vlib/v2/gen/cleanc/array.v @@ -204,10 +204,25 @@ fn (mut g Gen) emit_array_interface_repeat_helpers() { } } -fn (mut g Gen) emit_missing_array_contains_fallbacks() { - mut fn_names := g.fn_param_types.keys() +struct ArrayContainsFallbackSpec { + fn_name string + arr_type string + elem_type string +} + +fn (g &Gen) missing_array_contains_fallback_specs() []ArrayContainsFallbackSpec { + mut fn_name_set := map[string]bool{} + for fn_name in g.fn_param_types.keys() { + fn_name_set[fn_name] = true + } + for fn_name in g.called_fn_names.keys() { + if fn_name.starts_with('Array_') && fn_name.ends_with('_contains') { + fn_name_set[fn_name] = true + } + } + mut fn_names := fn_name_set.keys() fn_names.sort() - mut emitted_any := false + mut specs := []ArrayContainsFallbackSpec{} for fn_name in fn_names { if !fn_name.starts_with('Array_') || !fn_name.ends_with('_contains') { continue @@ -219,46 +234,82 @@ fn (mut g Gen) emit_missing_array_contains_fallbacks() { if fn_key in g.emitted_types { continue } - if g.fn_return_types[fn_name] or { '' } != 'bool' { - continue + has_signature := fn_name in g.fn_param_types + if has_signature { + if g.fn_return_types[fn_name] or { '' } != 'bool' { + continue + } } param_types := g.fn_param_types[fn_name] or { []string{} } - if param_types.len != 2 { + mut arr_type := '' + mut elem_type := '' + if param_types.len == 2 { + arr_type = param_types[0] + elem_type = param_types[1] + } else if fn_name.starts_with('Array_') && fn_name.ends_with('_contains') { + elem_type = fn_name['Array_'.len..fn_name.len - '_contains'.len] + arr_type = 'Array_${elem_type}' + } else { continue } - arr_type := param_types[0] - elem_type := param_types[1] if arr_type.len == 0 || elem_type.len == 0 { continue } + specs << ArrayContainsFallbackSpec{ + fn_name: fn_name + arr_type: arr_type + elem_type: elem_type + } + } + return specs +} + +fn (mut g Gen) emit_missing_array_contains_fallback_decls() { + specs := g.missing_array_contains_fallback_specs() + if specs.len == 0 { + return + } + for spec in specs { + g.sb.writeln('bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v);') + } + g.sb.writeln('') +} + +fn (mut g Gen) emit_missing_array_contains_fallbacks() { + mut emitted_any := false + for spec in g.missing_array_contains_fallback_specs() { + fn_key := 'fn_${spec.fn_name}' + if fn_key in g.emitted_types { + continue + } g.emitted_types[fn_key] = true g.sb.writeln('') if g.is_freestanding_target() && g.pref != unsafe { nil } && g.pref.skip_builtin { - g.sb.writeln('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: ${fn_name}");') + g.sb.writeln('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: ${spec.fn_name}");') emitted_any = true continue } // Fixed arrays are C arrays (e.g., voidptr[20]), not structs — use direct indexing. - is_fixed := arr_type.starts_with('Array_fixed_') + is_fixed := spec.arr_type.starts_with('Array_fixed_') if is_fixed { - _, fixed_len := g.parse_fixed_array_type(arr_type) - g.sb.writeln('__attribute__((weak)) bool ${fn_name}(${arr_type} a, ${elem_type} v) {') + _, fixed_len := g.parse_fixed_array_type(spec.arr_type) + g.sb.writeln('__attribute__((weak)) bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v) {') g.sb.writeln('\tfor (int i = 0; i < ${fixed_len}; i++) {') - if elem_type == 'string' { + if spec.elem_type == 'string' { g.sb.writeln('\t\tif (string__eq(a[i], v)) {') } else { - g.sb.writeln('\t\tif (memcmp(&a[i], &v, sizeof(${elem_type})) == 0) {') + g.sb.writeln('\t\tif (memcmp(&a[i], &v, sizeof(${spec.elem_type})) == 0) {') } } else { - g.sb.writeln('__attribute__((weak)) bool ${fn_name}(${arr_type} a, ${elem_type} v) {') + g.sb.writeln('__attribute__((weak)) bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v) {') g.sb.writeln('\tfor (int i = 0; (i < a.len); i += 1) {') - if elem_type == 'string' { + if spec.elem_type == 'string' { g.sb.writeln('\t\tif (string__eq(*((string*)(array__get(a, i))), v)) {') } else { left_cmp := '_cmp_l_${g.tmp_counter}' right_cmp := '_cmp_r_${g.tmp_counter + 1}' g.tmp_counter += 2 - g.sb.writeln('\t\tif (({ ${elem_type} ${left_cmp} = *((${elem_type}*)(array__get(a, i))); ${elem_type} ${right_cmp} = v; memcmp(&${left_cmp}, &${right_cmp}, sizeof(${elem_type})) == 0; })) {') + g.sb.writeln('\t\tif (({ ${spec.elem_type} ${left_cmp} = *((${spec.elem_type}*)(array__get(a, i))); ${spec.elem_type} ${right_cmp} = v; memcmp(&${left_cmp}, &${right_cmp}, sizeof(${spec.elem_type})) == 0; })) {') } } g.sb.writeln('\t\t\treturn true;') diff --git a/vlib/v2/gen/cleanc/assign.v b/vlib/v2/gen/cleanc/assign.v index 482fc880f..214180f7e 100644 --- a/vlib/v2/gen/cleanc/assign.v +++ b/vlib/v2/gen/cleanc/assign.v @@ -1778,6 +1778,10 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { g.sb.writeln(';') return } + if node.op == .assign && g.gen_enum_shorthand_for_type(rhs, assign_lhs_type) { + g.sb.writeln(';') + return + } // When RHS is an array method (first/last/pop/pop_left), the call emission // in fn.v already wraps with (*(elem_type*)call(...)). Skip the outer // assign-level cast to avoid double dereference. @@ -1892,7 +1896,9 @@ fn (mut g Gen) gen_plain_selector_assign(lhs ast.SelectorExpr, rhs ast.Expr, op g.write_indent() g.gen_selector_lvalue(lhs, local_type, lhs_struct) g.sb.write_string(' = ') - g.expr(rhs) + if !g.gen_enum_shorthand_for_type(rhs, field_type) { + g.expr(rhs) + } g.sb.writeln(';') return true } diff --git a/vlib/v2/gen/cleanc/cheaders.v b/vlib/v2/gen/cleanc/cheaders.v index 24f686951..dda3078fe 100644 --- a/vlib/v2/gen/cleanc/cheaders.v +++ b/vlib/v2/gen/cleanc/cheaders.v @@ -1443,6 +1443,33 @@ fn (mut g Gen) emit_tinyc_arm_cpu_relax_fallback() { g.sb.writeln('#endif') } +fn (mut g Gen) emit_stdatomic_compat_include() { + if g.pref == unsafe { nil } || g.pref.vroot.len == 0 { + return + } + dir_name := if g.target_os_name() == 'windows' { 'win' } else { 'nix' } + header_path := os.join_path(g.pref.vroot, 'thirdparty', 'stdatomic', dir_name, 'atomic.h') + if !os.exists(header_path) { + return + } + include_path := header_path.replace('\\', '/') + // The `extern -> static` hack only matters for tcc on Apple arm64; keep + // the __APPLE__ guard out of non-macos target preambles (they are + // asserted apple-free by target_codegen_test). + apple_target := g.target_os_name() == 'macos' + if apple_target { + g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') + g.sb.writeln('#define extern static') + g.sb.writeln('#endif') + } + g.sb.writeln('#include "${include_path}"') + if apple_target { + g.sb.writeln('#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)') + g.sb.writeln('#undef extern') + g.sb.writeln('#endif') + } +} + fn (mut g Gen) emit_v_architecture_macros() { g.sb.writeln('#if defined(__x86_64__) || defined(_M_AMD64)') g.sb.writeln('#define __V_amd64 1') @@ -1547,6 +1574,7 @@ fn (mut g Gen) emit_target_rwmutex() { fn (mut g Gen) write_preamble() { minimal_preamble := g.use_minimal_preamble() g.sb.write_string(g.preamble_includes(minimal_preamble)) + g.emit_stdatomic_compat_include() g.emit_v_architecture_macros() g.emit_v_commit_hash_fallback() g.emit_collected_c_directives() diff --git a/vlib/v2/gen/cleanc/cleanc.v b/vlib/v2/gen/cleanc/cleanc.v index 77b6d0d09..d227f5060 100644 --- a/vlib/v2/gen/cleanc/cleanc.v +++ b/vlib/v2/gen/cleanc/cleanc.v @@ -130,6 +130,10 @@ mut: struct_type_lookup_miss map[string]bool struct_decl_info_cache map[string]StructDeclInfo struct_decl_info_miss map[string]bool + flat_struct_decl_exact map[string]FlatStructDeclInfo + flat_struct_decl_short_by_mod map[string]FlatStructDeclInfo + flat_struct_decl_short map[string]FlatStructDeclInfo + flat_struct_decl_indexed bool alias_base_lookup_cache map[string]string alias_base_lookup_miss map[string]bool declared_type_names_all map[string]bool @@ -149,6 +153,8 @@ mut: force_emit_fn_names map[string]bool // function C names that must be emitted regardless of mark_used export_fn_names map[string]string // V-qualified name → export name (from @[export:] attribute) called_fn_names map[string]bool + called_specialized_names map[string]string // base generic C name -> called specialized C name + called_specialized_names_indexed bool declared_fn_names map[string]bool // C function names that have a prototype/body head emitted should_emit_fn_decl_cache map[string]bool generic_body_scan_cache map[string]bool @@ -161,6 +167,7 @@ mut: specialized_receiver_methods map[string]string // receiver|method -> single matching specialized method specialized_receiver_method_ambiguous map[string]bool // receiver|method keys with multiple matches specialized_receiver_method_miss map[string]bool // receiver|method keys with no matching specialized method + specialized_receiver_method_indexed bool // true after existing signature maps have been indexed late_generic_specs map[string][]map[string]types.Type // additional comptime-discovered specs anon_fn_defs []string // lifted anonymous function definitions late_struct_defs []string // struct definitions discovered during pass 5 codegen @@ -253,6 +260,13 @@ struct StructDeclInfo { file_name string } +struct FlatStructDeclInfo { + file_idx int + stmt_idx int + mod string + file_name string +} + struct InterfaceDeclInfo { decl ast.InterfaceDecl mod string @@ -456,6 +470,10 @@ fn new_gen_with_env_and_pref_impl(env &types.Environment, p &pref.Preferences) & struct_type_lookup_miss: map[string]bool{} struct_decl_info_cache: map[string]StructDeclInfo{} struct_decl_info_miss: map[string]bool{} + flat_struct_decl_exact: map[string]FlatStructDeclInfo{} + flat_struct_decl_short_by_mod: map[string]FlatStructDeclInfo{} + flat_struct_decl_short: map[string]FlatStructDeclInfo{} + flat_struct_decl_indexed: false alias_base_lookup_cache: map[string]string{} alias_base_lookup_miss: map[string]bool{} declared_type_names_all: map[string]bool{} @@ -503,6 +521,8 @@ fn new_gen_with_env_and_pref_impl(env &types.Environment, p &pref.Preferences) & used_fn_keys: map[string]bool{} force_emit_fn_names: map[string]bool{} called_fn_names: map[string]bool{} + called_specialized_names: map[string]string{} + called_specialized_names_indexed: false declared_fn_names: map[string]bool{} should_emit_fn_decl_cache: map[string]bool{} generic_body_scan_cache: map[string]bool{} @@ -512,6 +532,7 @@ fn new_gen_with_env_and_pref_impl(env &types.Environment, p &pref.Preferences) & specialized_receiver_methods: map[string]string{} specialized_receiver_method_ambiguous: map[string]bool{} specialized_receiver_method_miss: map[string]bool{} + specialized_receiver_method_indexed: false c_struct_types: map[string]bool{} typedef_c_types: map[string]bool{} blocked_fn_keys: map[string]bool{} @@ -1120,10 +1141,12 @@ fn (g &Gen) print_cgen_step_time(stats_enabled bool, scope string, step string, fn (g &Gen) mark_cgen_step(stats_enabled bool, scope string, mut sw time.StopWatch, stage_start time.Duration, step string) time.Duration { if !stats_enabled { + g.print_cgen_mem(step) return stage_start } now := sw.elapsed() g.print_cgen_step_time(true, scope, step, time.Duration(now - stage_start)) + g.print_cgen_mem(step) return now } @@ -1150,7 +1173,6 @@ pub fn (mut g Gen) gen_passes_1_to_4() { g.collect_typedef_c_types() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup.typedef_c_types') - g.build_generic_fn_decl_index() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup.generic_fn_decl_index') if g.has_generic_setup_snapshot { @@ -1176,8 +1198,6 @@ pub fn (mut g Gen) gen_passes_1_to_4() { g.generic_struct_bindings['stdatomic__AtomicVal'] = { 'T': types.Type(types.int_) } - g.discover_direct_generic_call_specs() - g.discover_nested_generic_specs() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'setup.discover_generic_specs') g.collect_force_emit_sort_fns() @@ -1403,6 +1423,7 @@ pub fn (mut g Gen) gen_passes_1_to_4() { g.emit_interface_method_wrapper_decls() g.emit_interface_clone_decls() g.emit_array_interface_repeat_decls() + g.emit_missing_array_contains_fallback_decls() stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'pass 4 helper declarations') @@ -2159,6 +2180,8 @@ fn (mut g Gen) gen_pass5() { g.selector_field_type_miss = map[string]bool{} g.struct_field_lookup_cache = map[string]string{} g.struct_field_lookup_miss = map[string]bool{} + g.ensure_flat_struct_decl_index() + g.ensure_called_specialized_name_index() g.collect_force_emit_str_fns() g.emit_pass5_extern_consts_for_non_emit_files() g.emit_pass5_extern_globals() @@ -2192,6 +2215,8 @@ pub fn (mut g Gen) gen_pass5_pre() []int { g.selector_field_type_miss = map[string]bool{} g.struct_field_lookup_cache = map[string]string{} g.struct_field_lookup_miss = map[string]bool{} + g.ensure_flat_struct_decl_index() + g.ensure_called_specialized_name_index() g.collect_force_emit_str_fns() g.emit_pass5_extern_consts_for_non_emit_files() g.emit_pass5_extern_globals() @@ -2755,7 +2780,13 @@ pub fn (g &Gen) print_pass5_file_times(limit int) { return } mut times := g.pass5_file_times.clone() - times.sort(a.ms > b.ms) + for i := 1; i < times.len; i++ { + mut j := i + for j > 0 && times[j - 1].ms < times[j].ms { + times[j - 1], times[j] = times[j], times[j - 1] + j-- + } + } stats_scope := g.cgen_stats_scope_label() n := if times.len < limit { times.len } else { limit } for item in times[..n] { @@ -3198,6 +3229,8 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen { force_emit_fn_names: g.force_emit_fn_names.clone() export_fn_names: g.export_fn_names.clone() called_fn_names: g.called_fn_names.clone() + called_specialized_names: g.called_specialized_names.clone() + called_specialized_names_indexed: g.called_specialized_names_indexed declared_fn_names: g.declared_fn_names.clone() should_emit_fn_decl_cache: g.should_emit_fn_decl_cache.clone() generic_body_scan_cache: g.generic_body_scan_cache.clone() @@ -3208,6 +3241,7 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen { specialized_receiver_methods: g.specialized_receiver_methods.clone() specialized_receiver_method_ambiguous: g.specialized_receiver_method_ambiguous.clone() specialized_receiver_method_miss: g.specialized_receiver_method_miss.clone() + specialized_receiver_method_indexed: g.specialized_receiver_method_indexed late_generic_specs: g.late_generic_specs.clone() generic_scan_called_names: map[string]bool{} generic_struct_bindings: g.generic_struct_bindings.clone() @@ -3217,38 +3251,42 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen { // Per-worker mutable state (starts fresh). // Each worker gets a unique tmp_counter offset to avoid name collisions // for generated trampolines (_bound_method_N, _bound_recv_N, etc.). - tmp_counter: (worker_id + 1) * 100_000 - pass5_worker_id: worker_id - emitted_types: worker_emitted - blocked_fn_keys: blocked_fn_keys - runtime_local_types: map[string]string{} - runtime_decl_types: map[string]string{} - runtime_fn_pointer_types: map[string]types.Type{} - cur_fn_returned_idents: map[string]bool{} - active_generic_types: map[string]types.Type{} - cur_fn_generic_params: map[string]string{} - cur_fn_scope_miss_key: '' - cur_import_modules: map[string]string{} - is_module_ident_cache: map[string]bool{} - not_local_var_cache: map[string]bool{} - resolved_module_names: map[string]string{} - cur_fn_mut_params: map[string]bool{} - cached_env_scopes: map[string]voidptr{} - selector_field_type_cache: map[string]string{} - selector_field_type_miss: map[string]bool{} - struct_field_lookup_cache: map[string]string{} - struct_field_lookup_miss: map[string]bool{} - struct_type_lookup_cache: map[string]types.Struct{} - struct_type_lookup_miss: map[string]bool{} - struct_decl_info_cache: map[string]StructDeclInfo{} - struct_decl_info_miss: map[string]bool{} - alias_base_lookup_cache: map[string]string{} - alias_base_lookup_miss: map[string]bool{} - needed_interface_wrappers: map[string]bool{} - needed_ierror_wrapper_bases: map[string]bool{} - spawned_fns: map[string]bool{} - exported_const_seen: map[string]bool{} - exported_const_symbols: []ExportedConstSymbol{} + tmp_counter: (worker_id + 1) * 100_000 + pass5_worker_id: worker_id + emitted_types: worker_emitted + blocked_fn_keys: blocked_fn_keys + runtime_local_types: map[string]string{} + runtime_decl_types: map[string]string{} + runtime_fn_pointer_types: map[string]types.Type{} + cur_fn_returned_idents: map[string]bool{} + active_generic_types: map[string]types.Type{} + cur_fn_generic_params: map[string]string{} + cur_fn_scope_miss_key: '' + cur_import_modules: map[string]string{} + is_module_ident_cache: map[string]bool{} + not_local_var_cache: map[string]bool{} + resolved_module_names: map[string]string{} + cur_fn_mut_params: map[string]bool{} + cached_env_scopes: map[string]voidptr{} + selector_field_type_cache: map[string]string{} + selector_field_type_miss: map[string]bool{} + struct_field_lookup_cache: map[string]string{} + struct_field_lookup_miss: map[string]bool{} + struct_type_lookup_cache: map[string]types.Struct{} + struct_type_lookup_miss: map[string]bool{} + struct_decl_info_cache: map[string]StructDeclInfo{} + struct_decl_info_miss: map[string]bool{} + flat_struct_decl_exact: g.flat_struct_decl_exact + flat_struct_decl_short_by_mod: g.flat_struct_decl_short_by_mod + flat_struct_decl_short: g.flat_struct_decl_short + flat_struct_decl_indexed: g.flat_struct_decl_indexed + alias_base_lookup_cache: map[string]string{} + alias_base_lookup_miss: map[string]bool{} + needed_interface_wrappers: map[string]bool{} + needed_ierror_wrapper_bases: map[string]bool{} + spawned_fns: map[string]bool{} + exported_const_seen: map[string]bool{} + exported_const_symbols: []ExportedConstSymbol{} } } @@ -3279,6 +3317,9 @@ pub fn (mut g Gen) merge_pass5_worker(w &Gen) { } for k, v in w.called_fn_names { g.called_fn_names[k] = v + if v { + g.remember_called_specialized_name(k) + } } // Merge emitted_types so post-pass dedup (e.g. array_contains fallbacks) works for k, v in w.emitted_types { diff --git a/vlib/v2/gen/cleanc/cleanc_test.v b/vlib/v2/gen/cleanc/cleanc_test.v index 809b313e7..d0a1fc240 100644 --- a/vlib/v2/gen/cleanc/cleanc_test.v +++ b/vlib/v2/gen/cleanc/cleanc_test.v @@ -920,6 +920,9 @@ fn test_fn_head_emits_forward_typedef_for_concrete_generic_receiver() { } fn_name := 'printer__CounterWriter_T_string__count' g.fn_param_types[fn_name] = ['printer__CounterWriter_T_string'] + // Forward typedefs for concrete generic signature types are emitted by the + // centralized signature pass (not inline by each fn head). + g.emit_forward_typedefs_for_signature_types() g.gen_fn_head_with_name(count_method, fn_name) g.sb.writeln(';') csrc := g.sb.str() diff --git a/vlib/v2/gen/cleanc/expr.v b/vlib/v2/gen/cleanc/expr.v index 166565ec6..67088e075 100644 --- a/vlib/v2/gen/cleanc/expr.v +++ b/vlib/v2/gen/cleanc/expr.v @@ -76,6 +76,9 @@ fn is_nil_like_expr(expr ast.Expr) bool { // CallOrCastExpr can be a type cast wrapping 0 return is_nil_like_expr(expr.expr) } + ast.Type { + return expr is ast.NilType + } ast.UnsafeExpr { if expr.stmts.len == 0 { return false @@ -1197,11 +1200,11 @@ fn (mut g Gen) gen_channel_receive_expr(node ast.PrefixExpr) bool { elem_type = 'void*' } g.force_emit_fn_names['sync__Channel__try_pop_priv'] = true - g.called_fn_names['sync__Channel__try_pop_priv'] = true + g.mark_called_fn_name('sync__Channel__try_pop_priv') g.tmp_counter++ if expr_type.starts_with('_option_') { g.force_emit_fn_names['error'] = true - g.called_fn_names['error'] = true + g.mark_called_fn_name('error') opt_tmp := '_chopt_${g.tmp_counter}' val_tmp := '_chval_${g.tmp_counter}' g.sb.write_string('({ ${expr_type} ${opt_tmp} = (${expr_type}){ .state = 2, .err = error((string){.str = "channel closed", .len = sizeof("channel closed") - 1, .is_lit = 1}) }; ${elem_type} ${val_tmp} = ${zero_value_for_type(elem_type)}; if (sync__Channel__try_pop_priv((sync__Channel*)') @@ -1263,6 +1266,21 @@ fn (g &Gen) should_use_known_enum_field(enum_name string, field_name string) boo && g.enum_has_field(enum_name, field_name) } +fn (mut g Gen) gen_enum_shorthand_for_type(expr ast.Expr, expected_type string) bool { + enum_name := expected_type.trim_space().trim_right('*') + if enum_name == '' || enum_name == 'int' || is_generic_placeholder_c_type_name(enum_name) + || !g.is_enum_type(enum_name) { + return false + } + if expr is ast.SelectorExpr { + if expr.lhs is ast.EmptyExpr && g.enum_has_field(enum_name, expr.rhs.name) { + g.sb.write_string(g.enum_member_c_name(enum_name, expr.rhs.name)) + return true + } + } + return false +} + fn (mut g Gen) enum_type_from_expr(expr ast.Expr, fallback string) string { mut enum_type := fallback.trim_space().trim_right('*') if enum_type != '' && enum_type != 'int' && !is_generic_placeholder_c_type_name(enum_type) @@ -1368,7 +1386,7 @@ fn (mut g Gen) gen_infix_expr(node &ast.InfixExpr) { elem_type = 'bool' } g.force_emit_fn_names['sync__Channel__try_push_priv'] = true - g.called_fn_names['sync__Channel__try_push_priv'] = true + g.mark_called_fn_name('sync__Channel__try_push_priv') g.sb.write_string('sync__Channel__try_push_priv((sync__Channel*)') g.expr(node.lhs) g.sb.write_string(', &(${elem_type}[]){') @@ -2519,6 +2537,10 @@ fn (mut g Gen) expr(node ast.Expr) { // &T(x) in unsafe contexts is used as a pointer cast in V stdlib code. // Emit it as (T*)(x) so `*unsafe { &T(p) }` becomes `*((T*)p)`. if node.op == .amp { + if is_nil_like_expr(node.expr) { + g.sb.write_string('NULL') + return + } if node.expr is ast.CastExpr { cast_expr := node.expr as ast.CastExpr expr_type := g.get_expr_type(cast_expr.expr) @@ -3536,7 +3558,11 @@ fn (mut g Gen) expr(node ast.Expr) { panic('bug in v2 compiler: LockExpr should have been lowered in v2.transformer') } ast.Type { - g.sb.write_string('/* [TODO] Type */ 0') + if node is ast.NilType { + g.sb.write_string('NULL') + } else { + g.sb.write_string('/* [TODO] Type */ 0') + } } ast.AssocExpr { panic('bug in v2 compiler: AssocExpr should have been lowered in v2.transformer') diff --git a/vlib/v2/gen/cleanc/fn.v b/vlib/v2/gen/cleanc/fn.v index e4b5c221b..6206a694c 100644 --- a/vlib/v2/gen/cleanc/fn.v +++ b/vlib/v2/gen/cleanc/fn.v @@ -69,6 +69,7 @@ pub fn (mut g Gen) add_called_fn_names(names []string) { fn (mut g Gen) mark_called_fn_name(name string) { if name.len > 0 { g.called_fn_names[name] = true + g.remember_called_specialized_name(name) } } @@ -888,6 +889,19 @@ fn specialized_receiver_method_key(receiver_type string, method_name string) str return '${receiver_type}|${method_name}' } +fn (mut g Gen) ensure_specialized_receiver_method_index() { + if g.specialized_receiver_method_indexed { + return + } + for fn_name, _ in g.fn_return_types { + g.remember_specialized_fn_base(fn_name) + } + for fn_name, _ in g.fn_param_is_ptr { + g.remember_specialized_fn_base(fn_name) + } + g.specialized_receiver_method_indexed = true +} + fn (mut g Gen) needs_implicit_veb_ctx_param(node &ast.FnDecl, fn_name string) bool { if node.receiver.name == 'ctx' { return false @@ -1018,8 +1032,7 @@ fn (g &Gen) generic_fn_param_names(node ast.FnDecl) []string { } fn (g &Gen) should_skip_backend_generic_fn(node ast.FnDecl) bool { - _ = node - return false + return g.generic_fn_param_names(node).len > 0 || receiver_generic_param_names(node).len > 0 } // receiver_generic_param_names collects generic placeholder names from a @@ -6427,10 +6440,38 @@ fn (mut g Gen) gen_fn_decl_with_name(node ast.FnDecl, fn_name string) { g.gen_fn_decl_with_name_ptr(&node, fn_name) } +fn (mut g Gen) remember_called_specialized_name(name string) { + if name == '' || !name.contains('_T_') { + return + } + base := name.all_before('_T_') + if base == '' || base in g.called_specialized_names { + return + } + g.called_specialized_names[base] = name +} + +fn (mut g Gen) ensure_called_specialized_name_index() { + if g.called_specialized_names_indexed { + return + } + g.called_specialized_names = map[string]string{} + for name, _ in g.called_fn_names { + g.remember_called_specialized_name(name) + } + g.called_specialized_names_indexed = true +} + fn (g &Gen) called_specialized_name_for_base(base string) ?string { if base.contains('_T_') { return none } + if g.called_specialized_names_indexed { + if name := g.called_specialized_names[base] { + return name + } + return none + } prefix := '${base}_T_' for name, _ in g.called_fn_names { if name.starts_with(prefix) { @@ -7061,9 +7102,6 @@ fn (mut g Gen) gen_fn_head_with_name_ptr(node &ast.FnDecl, fn_name string) { c_ret = 'int' } sig_param_types := g.fn_param_types[fn_name] or { []string{} } - for sig_type in g.fn_head_signature_type_names(node, fn_name, ret, sig_param_types) { - g.emit_forward_typedef_for_signature_type(sig_type) - } // main takes argc/argv if is_program_main { @@ -7156,70 +7194,6 @@ fn (mut g Gen) gen_fn_head_with_name_ptr(node &ast.FnDecl, fn_name string) { g.sb.write_string(')') } -fn (mut g Gen) fn_head_signature_type_names(node &ast.FnDecl, fn_name string, ret string, sig_param_types []string) []string { - mut sig_types := []string{} - sig_types << ret - mut sig_idx := 0 - if node.is_method && node.receiver.name != '' { - receiver_type := if sig_idx < sig_param_types.len { - normalize_signature_type_name(sig_param_types[sig_idx], 'void*') - } else if node.receiver.is_mut { - rt := normalize_signature_type_name(g.decl_expr_type_to_c(node.receiver.typ), 'void*') - if rt.ends_with('*') { - rt - } else { - rt + '*' - } - } else { - normalize_signature_type_name(g.decl_expr_type_to_c(node.receiver.typ), 'void*') - } - sig_types << receiver_type - sig_idx++ - } - if g.needs_implicit_veb_ctx_param(node, fn_name) { - sig_types << g.implicit_veb_ctx_c_type() - sig_idx++ - } - for param in node.typ.params { - if param.typ is ast.Type { - if param.typ is ast.FnType { - fn_type := param.typ as ast.FnType - if fn_type.return_type !is ast.EmptyExpr { - sig_types << normalize_signature_type_name(g.expr_type_to_c(fn_type.return_type), - 'void') - } - for fn_param in fn_type.params { - mut param_type := normalize_signature_type_name(g.expr_type_to_c(fn_param.typ), - 'int') - if fn_param.is_mut && !param_type.ends_with('*') { - param_type += '*' - } - sig_types << param_type - } - sig_idx++ - continue - } - } - t := if sig_idx < sig_param_types.len { - normalize_signature_type_name(sig_param_types[sig_idx], 'int') - } else if param.is_mut { - pt := normalize_signature_type_name(g.expr_type_to_c(param.typ), 'int') - if pt.ends_with('*') { - pt - } else { - pt + '*' - } - } else { - normalize_signature_type_name(g.expr_type_to_c(param.typ), 'int') - } - if !t.contains('(*)') { - sig_types << t - } - sig_idx++ - } - return sig_types -} - fn c_fn_pointer_type_with_name(fn_type string, name string) string { if !fn_type.contains('(*)') { return '' @@ -7853,7 +7827,7 @@ fn (g &Gen) is_simple_addressable(expr ast.Expr) bool { fn (mut g Gen) gen_addr_of_expr(arg ast.Expr, typ string) { base_arg := if arg is ast.ModifierExpr { arg.expr } else { arg } // nil is a null pointer — emit NULL directly, never wrap in compound literal - if base_arg is ast.Ident && base_arg.name == 'nil' { + if is_nil_like_expr(base_arg) { g.sb.write_string('NULL') return } @@ -8992,18 +8966,20 @@ fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) { return } } - // nil is always a valid pointer value (NULL) — never wrap it - if (base_arg is ast.Ident && base_arg.name == 'nil') || base_arg is ast.Type { + // nil is always a valid pointer value (NULL) — never wrap it. A literal + // `0` (possibly cast-wrapped) is nil-like only for pointer parameters; + // for value parameters it must stay a plain `0`. + if is_nil_like_expr(base_arg) { + if (base_arg is ast.Ident && base_arg.name == 'nil') || base_arg is ast.Type { + g.sb.write_string('NULL') + return + } if ptr_params := g.fn_param_is_ptr[fn_name] { if idx < ptr_params.len && ptr_params[idx] { g.sb.write_string('NULL') return } } - if base_arg is ast.Ident && base_arg.name == 'nil' { - g.sb.write_string('NULL') - return - } } if fn_name == 'new_map_init_noscan_value' && idx in [7, 8] { // new_map_init_noscan_value expects raw key/value element pointers. @@ -10263,13 +10239,13 @@ fn (mut g Gen) resolve_method_on_concrete_type(receiver_type string, method_name return module_candidate } } - if g.specialized_fn_bases.len == 0 || clean_type in g.specialized_fn_bases { + g.ensure_specialized_receiver_method_index() + if clean_type in g.specialized_fn_bases { if specialized := g.resolve_specialized_receiver_method(clean_type, sanitized) { return specialized } } - if short_name != '' && short_name != clean_type - && (g.specialized_fn_bases.len == 0 || short_name in g.specialized_fn_bases) { + if short_name != '' && short_name != clean_type && short_name in g.specialized_fn_bases { if specialized := g.resolve_specialized_receiver_method(short_name, sanitized) { return specialized } @@ -10306,17 +10282,18 @@ fn (mut g Gen) resolve_specialized_receiver_method(receiver_type string, method_ // simple method (no `__`) is exactly (all_before('_T_') == base) && (all_after_last('__') == // method) — i.e. the same keys remember_specialized_receiver_method records. if !receiver_type.contains('_T_') && !method_name.contains('__') { + g.ensure_specialized_receiver_method_index() key := specialized_receiver_method_key(receiver_type, method_name) + if key in g.specialized_receiver_method_miss { + return none + } if key in g.specialized_receiver_method_ambiguous { return none } if found := g.specialized_receiver_methods[key] { return found } - if g.specialized_receiver_methods.len == 0 - && g.specialized_receiver_method_ambiguous.len == 0 { - return g.scan_specialized_receiver_method(receiver_type, method_name) - } + g.specialized_receiver_method_miss[key] = true return none } // Rare fallback: receiver_type/method contains the `_T_`/`__` separator the index keys on. @@ -10822,7 +10799,7 @@ fn (mut g Gen) gen_json_decode_call(lhs ast.Expr, args []ast.Expr) bool { raw_tmp := '_json_decode_raw_${tmp_id}' res_tmp := '_json_decode_res_${tmp_id}' ptr_tmp := '_json_decode_ptr_${tmp_id}' - g.called_fn_names['json__decode'] = true + g.mark_called_fn_name('json__decode') g.sb.write_string('({ _result_voidptr ${raw_tmp} = json__decode(NULL, ') g.gen_call_arg('json__decode', 1, args[1]) g.sb.write_string('); ${result_type} ${res_tmp} = (${result_type}){ .is_error = ${raw_tmp}.is_error, .err = ${raw_tmp}.err }; if (!${raw_tmp}.is_error) { void* ${ptr_tmp} = (*(void**)(((u8*)(&${raw_tmp}.err)) + sizeof(IError))); if (${ptr_tmp} != NULL) { _result_ok(${ptr_tmp}, (_result*)&${res_tmp}, sizeof(${target_type})); } } ${res_tmp}; })') diff --git a/vlib/v2/gen/cleanc/mem.v b/vlib/v2/gen/cleanc/mem.v new file mode 100644 index 000000000..082eca5f7 --- /dev/null +++ b/vlib/v2/gen/cleanc/mem.v @@ -0,0 +1,17 @@ +// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module cleanc + +import os + +fn (g &Gen) print_cgen_mem(stage string) { + if os.getenv('V2_MEM') == '' { + return + } + $if macos { + eprintln(' [mem] cleanc/${stage}: live ${darwin_cleanc_live_mb()} MB') + } $else { + eprintln(' [mem] cleanc/${stage}') + } +} diff --git a/vlib/v2/gen/cleanc/mem_darwin.c.v b/vlib/v2/gen/cleanc/mem_darwin.c.v new file mode 100644 index 000000000..dfd87ff2a --- /dev/null +++ b/vlib/v2/gen/cleanc/mem_darwin.c.v @@ -0,0 +1,22 @@ +// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module cleanc + +#include + +struct C.malloc_statistics_t { + blocks_in_use u32 + size_in_use usize + max_size_in_use usize + size_allocated usize +} + +fn C.malloc_default_zone() voidptr +fn C.malloc_zone_statistics(zone voidptr, stats &C.malloc_statistics_t) + +fn darwin_cleanc_live_mb() u64 { + mut st := C.malloc_statistics_t{} + C.malloc_zone_statistics(C.malloc_default_zone(), &st) + return u64(st.size_in_use) / (1024 * 1024) +} diff --git a/vlib/v2/gen/cleanc/orm.v b/vlib/v2/gen/cleanc/orm.v index 4e4be743a..67aff7768 100644 --- a/vlib/v2/gen/cleanc/orm.v +++ b/vlib/v2/gen/cleanc/orm.v @@ -21,7 +21,7 @@ fn (mut g Gen) try_gen_orm_create_call(c_name string, call_args []ast.Expr) bool g.write_orm_table_fields_array_c_expr(call_args[2]) g.sb.write_string(')') if c_name != '' { - g.called_fn_names[c_name] = true + g.mark_called_fn_name(c_name) } return true } diff --git a/vlib/v2/gen/cleanc/parallel_cache_init_test.v b/vlib/v2/gen/cleanc/parallel_cache_init_test.v new file mode 100644 index 000000000..2cecb5a4c --- /dev/null +++ b/vlib/v2/gen/cleanc/parallel_cache_init_test.v @@ -0,0 +1,51 @@ +// vtest build: !linux && !windows +module cleanc + +import os +import v2.markused +import v2.parser +import v2.pref as vpref +import v2.token +import v2.transformer +import v2.types + +fn generate_parallel_worker_c_with_cached_init_for_test(code string, cached_init_calls []string) string { + tmp_file := os.join_path(os.temp_dir(), 'v2_parallel_cache_init_test_${os.getpid()}.v') + os.write_file(tmp_file, code) or { panic('failed to write temp file') } + defer { + os.rm(tmp_file) or {} + } + prefs := &vpref.Preferences{ + backend: .cleanc + no_parallel: false + } + mut file_set := token.FileSet.new() + mut par := parser.Parser.new(prefs) + files := par.parse_files([tmp_file], mut file_set) + env := types.Environment.new() + mut checker := types.Checker.new(prefs, file_set, env) + checker.check_files(files) + mut trans := transformer.Transformer.new_with_pref(env, prefs) + gen_files := trans.transform_files(files) + used := markused.mark_used(gen_files, env) + mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs) + gen.set_used_fn_keys(used) + gen.set_cached_init_calls(cached_init_calls) + gen.gen_passes_1_to_4() + emit_indices := gen.gen_pass5_pre() + mut worker := gen.new_pass5_worker(emit_indices, 0) + worker.gen_pass5_files(emit_indices) + gen.merge_pass5_worker(worker) + gen.gen_pass5_post() + return gen.gen_finalize() +} + +fn test_parallel_worker_preserves_cached_init_calls_in_main() { + csrc := generate_parallel_worker_c_with_cached_init_for_test(' +fn main() { +} +', [ + '__v2_cached_init_imports', + ]) + assert csrc.contains('__v2_cached_init_imports();') +} diff --git a/vlib/v2/gen/cleanc/stmt.v b/vlib/v2/gen/cleanc/stmt.v index 87cf59cb8..4d391fc37 100644 --- a/vlib/v2/gen/cleanc/stmt.v +++ b/vlib/v2/gen/cleanc/stmt.v @@ -64,40 +64,47 @@ fn (mut g Gen) restore_file_module_context(file_name string, module_name string, fn (mut g Gen) gen_stmts(stmts []ast.Stmt) { saved_file_name := g.cur_file_name saved_module := g.cur_module - saved_import_modules := g.cur_import_modules.clone() + mut saved_import_modules := g.cur_import_modules.clone() for i in 0 .. stmts.len { - g.cur_file_name = saved_file_name - g.cur_module = saved_module - g.cur_import_modules = saved_import_modules.clone() - g.is_module_ident_cache.clear() - g.resolved_module_names.clear() g.gen_stmt(stmts[i]) + // A ModuleStmt (or a spliced generated-fn body) can switch the module + // context mid-list; restore the source context so the following + // statements resolve against the original module. The cheap dirty + // check keeps the hot path free of per-statement map clones. + if g.cur_module != saved_module || g.cur_file_name != saved_file_name + || g.cur_import_modules.len != saved_import_modules.len { + g.cur_file_name = saved_file_name + g.cur_module = saved_module + g.cur_import_modules = saved_import_modules.clone() + g.is_module_ident_cache.clear() + g.resolved_module_names.clear() + } } g.cur_file_name = saved_file_name g.cur_module = saved_module - g.cur_import_modules = saved_import_modules.clone() + g.cur_import_modules = saved_import_modules.move() g.is_module_ident_cache.clear() g.resolved_module_names.clear() } fn (mut g Gen) gen_scoped_stmts(stmts []ast.Stmt) { - saved_runtime_local_types := g.runtime_local_types.clone() - saved_runtime_decl_types := g.runtime_decl_types.clone() - saved_not_local_var_cache := g.not_local_var_cache.clone() + mut saved_runtime_local_types := g.runtime_local_types.clone() + mut saved_runtime_decl_types := g.runtime_decl_types.clone() + mut saved_not_local_var_cache := g.not_local_var_cache.clone() g.gen_stmts(stmts) - g.runtime_local_types = saved_runtime_local_types.clone() - g.runtime_decl_types = saved_runtime_decl_types.clone() - g.not_local_var_cache = saved_not_local_var_cache.clone() + g.runtime_local_types = saved_runtime_local_types.move() + g.runtime_decl_types = saved_runtime_decl_types.move() + g.not_local_var_cache = saved_not_local_var_cache.move() } fn (mut g Gen) gen_scoped_expr_stmts(expr ast.Expr) { - saved_runtime_local_types := g.runtime_local_types.clone() - saved_runtime_decl_types := g.runtime_decl_types.clone() - saved_not_local_var_cache := g.not_local_var_cache.clone() + mut saved_runtime_local_types := g.runtime_local_types.clone() + mut saved_runtime_decl_types := g.runtime_decl_types.clone() + mut saved_not_local_var_cache := g.not_local_var_cache.clone() g.gen_stmts_from_expr(expr) - g.runtime_local_types = saved_runtime_local_types.clone() - g.runtime_decl_types = saved_runtime_decl_types.clone() - g.not_local_var_cache = saved_not_local_var_cache.clone() + g.runtime_local_types = saved_runtime_local_types.move() + g.runtime_decl_types = saved_runtime_decl_types.move() + g.not_local_var_cache = saved_not_local_var_cache.move() } fn tuple_field_types_need_stmt_expr(field_types []string) bool { diff --git a/vlib/v2/gen/cleanc/struct.v b/vlib/v2/gen/cleanc/struct.v index 78410e5e0..b8505d28f 100644 --- a/vlib/v2/gen/cleanc/struct.v +++ b/vlib/v2/gen/cleanc/struct.v @@ -7686,7 +7686,7 @@ fn (mut g Gen) gen_channel_init_expr(node ast.InitExpr) bool { elem_c_type = 'void*' } g.force_emit_fn_names['sync__new_channel_st'] = true - g.called_fn_names['sync__new_channel_st'] = true + g.mark_called_fn_name('sync__new_channel_st') g.sb.write_string('sync__new_channel_st(') mut wrote_cap := false for field in node.fields { diff --git a/vlib/v2/gen/cleanc/types.v b/vlib/v2/gen/cleanc/types.v index 7f6269576..6c6dd8be4 100644 --- a/vlib/v2/gen/cleanc/types.v +++ b/vlib/v2/gen/cleanc/types.v @@ -2551,6 +2551,20 @@ fn embedded_owner_field_name(type_name string) string { return base } +fn flat_struct_decl_c_name(module_name string, decl_name string) string { + if decl_name.contains('__') { + return decl_name + } + if module_name != '' && module_name != 'main' && module_name != 'builtin' { + return '${module_name}__${decl_name}' + } + return decl_name +} + +fn flat_struct_short_key(module_name string, short_name string) string { + return '${module_name}\x01${short_name}' +} + fn (mut g Gen) lookup_embedded_field_info_in_struct(st types.Struct, field_name string) ?EmbeddedFieldLookupInfo { for embedded in st.embedded { embedded_c_type := g.types_type_to_c(embedded) @@ -2579,37 +2593,86 @@ fn (mut g Gen) lookup_embedded_field_info_in_struct(st types.Struct, field_name return none } -fn (mut g Gen) find_struct_decl_info_in_flat(c_name string, saved_module string, current_module_only bool, exact bool) ?StructDeclInfo { - for i in 0 .. g.flat.files.len { - fc := g.flat.file_cursor(i) - g.set_file_cursor_module(fc) - if current_module_only && g.cur_module != saved_module { - continue - } +fn (mut g Gen) ensure_flat_struct_decl_index() { + if g.flat_struct_decl_indexed || !g.has_flat() { + return + } + g.flat_struct_decl_exact = map[string]FlatStructDeclInfo{} + g.flat_struct_decl_short_by_mod = map[string]FlatStructDeclInfo{} + g.flat_struct_decl_short = map[string]FlatStructDeclInfo{} + for file_idx in 0 .. g.flat.files.len { + fc := g.flat.file_cursor(file_idx) + module_name := flat_file_module_name(fc) + file_name := fc.name() stmts := fc.stmts() - for j in 0 .. stmts.len() { - stmt := stmts.at(j) + for stmt_idx in 0 .. stmts.len() { + stmt := stmts.at(stmt_idx) if stmt.kind() != .stmt_struct_decl { continue } - decl := stmt.struct_decl() - if decl.language != .v { + language := unsafe { ast.Language(int(stmt.aux())) } + if language != .v { continue } - struct_name := g.get_struct_name(decl) - matches := if exact { - struct_name == c_name - } else { - short_type_name(struct_name) == c_name + decl_name := stmt.name() + c_name := flat_struct_decl_c_name(module_name, decl_name) + short_name := short_type_name(c_name) + info := FlatStructDeclInfo{ + file_idx: file_idx + stmt_idx: stmt_idx + mod: module_name + file_name: file_name } - if matches { - return StructDeclInfo{ - decl: decl - mod: g.cur_module - file_name: fc.name() - } + if c_name !in g.flat_struct_decl_exact { + g.flat_struct_decl_exact[c_name] = info + } + mod_key := flat_struct_short_key(module_name, short_name) + if mod_key !in g.flat_struct_decl_short_by_mod { + g.flat_struct_decl_short_by_mod[mod_key] = info } + if short_name !in g.flat_struct_decl_short { + g.flat_struct_decl_short[short_name] = info + } + } + } + g.flat_struct_decl_indexed = true +} + +fn (g &Gen) struct_decl_info_from_flat_info(info FlatStructDeclInfo) ?StructDeclInfo { + if !g.has_flat() || info.file_idx < 0 || info.file_idx >= g.flat.files.len { + return none + } + stmts := g.flat.file_cursor(info.file_idx).stmts() + if info.stmt_idx < 0 || info.stmt_idx >= stmts.len() { + return none + } + stmt := stmts.at(info.stmt_idx) + if stmt.kind() != .stmt_struct_decl { + return none + } + return StructDeclInfo{ + decl: stmt.struct_decl() + mod: info.mod + file_name: info.file_name + } +} + +fn (mut g Gen) find_struct_decl_info_in_flat(c_name string, saved_module string, current_module_only bool, exact bool) ?StructDeclInfo { + g.ensure_flat_struct_decl_index() + if exact { + if info := g.flat_struct_decl_exact[c_name] { + return g.struct_decl_info_from_flat_info(info) } + return none + } + if current_module_only { + if info := g.flat_struct_decl_short_by_mod[flat_struct_short_key(saved_module, c_name)] { + return g.struct_decl_info_from_flat_info(info) + } + return none + } + if info := g.flat_struct_decl_short[c_name] { + return g.struct_decl_info_from_flat_info(info) } return none } @@ -5606,20 +5669,8 @@ fn (mut g Gen) find_struct_node_by_c_name(c_name string) ?ast.StructDecl { g.cur_file_name = prev_file_name } if g.has_flat() { - for i in 0 .. g.flat.files.len { - fc := g.flat.file_cursor(i) - g.set_file_cursor_module(fc) - stmts := fc.stmts() - for j in 0 .. stmts.len() { - stmt := stmts.at(j) - if stmt.kind() != .stmt_struct_decl { - continue - } - decl := stmt.struct_decl() - if g.get_struct_name(decl) == c_name { - return decl - } - } + if info := g.find_struct_decl_info_by_c_name(c_name) { + return info.decl } return none } @@ -5637,22 +5688,9 @@ fn (mut g Gen) find_struct_node_by_c_name(c_name string) ?ast.StructDecl { // find_generic_struct_node finds the AST StructDecl for a given C struct name. fn (mut g Gen) find_generic_struct_node(c_name string) ?ast.StructDecl { if g.has_flat() { - for i in 0 .. g.flat.files.len { - fc := g.flat.file_cursor(i) - g.set_file_cursor_module(fc) - stmts := fc.stmts() - for j in 0 .. stmts.len() { - stmt := stmts.at(j) - if stmt.kind() != .stmt_struct_decl { - continue - } - decl := stmt.struct_decl() - if decl.generic_params.len > 0 { - name := g.get_struct_name(decl) - if name == c_name { - return decl - } - } + if info := g.find_struct_decl_info_by_c_name(c_name) { + if info.decl.generic_params.len > 0 { + return info.decl } } return none diff --git a/vlib/v2/gen/x64/elf_tiny.v b/vlib/v2/gen/x64/elf_tiny.v index a639f711e..73e0adec5 100644 --- a/vlib/v2/gen/x64/elf_tiny.v +++ b/vlib/v2/gen/x64/elf_tiny.v @@ -293,7 +293,13 @@ fn (l ElfTinyLinker) ultra_hello_world_main_shape_matches(data_range ElfDataRang relocs << reloc } } - relocs.sort(a.offset < b.offset) + for i := 1; i < relocs.len; i++ { + mut j := i + for j > 0 && relocs[j - 1].offset > relocs[j].offset { + relocs[j - 1], relocs[j] = relocs[j], relocs[j - 1] + j-- + } + } if relocs.len != 2 { return false } @@ -1056,7 +1062,13 @@ fn (l ElfTinyLinker) text_symbol_ranges() []ElfTextRange { syms << sym } } - syms.sort(a.value < b.value) + for i := 1; i < syms.len; i++ { + mut j := i + for j > 0 && syms[j - 1].value > syms[j].value { + syms[j - 1], syms[j] = syms[j], syms[j - 1] + j-- + } + } mut ranges := []ElfTextRange{} for i, sym in syms { mut end := u64(l.elf.text_data.len) diff --git a/vlib/v2/gen/x64/macho_tiny.v b/vlib/v2/gen/x64/macho_tiny.v index eded9f0a4..2a6548f7f 100644 --- a/vlib/v2/gen/x64/macho_tiny.v +++ b/vlib/v2/gen/x64/macho_tiny.v @@ -202,7 +202,13 @@ fn (w MachOTinyObjectWriter) text_symbol_ranges() []MachOTextRange { syms << sym } } - syms.sort(a.value < b.value) + for i := 1; i < syms.len; i++ { + mut j := i + for j > 0 && syms[j - 1].value > syms[j].value { + syms[j - 1], syms[j] = syms[j], syms[j - 1] + j-- + } + } mut ranges := []MachOTextRange{} for i, sym in syms { mut end := u64(w.macho.text_data.len) diff --git a/vlib/v2/gen/x64/x64.v b/vlib/v2/gen/x64/x64.v index 08ecb1cec..2bf275eaa 100644 --- a/vlib/v2/gen/x64/x64.v +++ b/vlib/v2/gen/x64/x64.v @@ -2986,8 +2986,12 @@ fn (mut g Gen) allocate_registers(func mir.Function) { mut sorted := []&Interval{} for _, i in intervals { sorted << i + mut j := sorted.len - 1 + for j > 0 && sorted[j - 1].start > sorted[j].start { + sorted[j - 1], sorted[j] = sorted[j], sorted[j - 1] + j-- + } } - sorted.sort(a.start < b.start) mut active := []&Interval{} // Use callee-saved registers: RBX(3), R12(12), R13(13), R14(14), R15(15) diff --git a/vlib/v2/parser/parser.v b/vlib/v2/parser/parser.v index ed47ca027..06a4eda89 100644 --- a/vlib/v2/parser/parser.v +++ b/vlib/v2/parser/parser.v @@ -796,42 +796,6 @@ fn (mut p Parser) complete_simple_stmt(expr ast.Expr, expecting_semi bool) ast.S fn (mut p Parser) expr(min_bp token.BindingPower) ast.Expr { // p.log('EXPR: ${p.tok} - ${p.line}') - // Self-hosted ARM64 builds can conflate `&` with backtick char tokens, - // so disambiguate the two from the source byte before token matching. - start_offset := p.pos.offset - p.file.base - start_char := if start_offset >= 0 && start_offset < p.scanner.src.len { - p.scanner.src[start_offset] - } else { - `\0` - } - if start_char == `\`` { - char_pos := p.pos - char_value := p.lit() - return p.finish_expr(ast.Expr(ast.BasicLiteral{ - kind: .char - value: char_value - pos: char_pos - }), min_bp) - } - if start_char == `&` && p.tok.str() == '&' { - prefix_pos := p.pos - p.next() - mut prefix_rhs := ast.empty_expr - if p.tok == .name { - prefix_rhs = p.ident_or_named_type() - if p.tok == .lcbr && p.can_parse_init_expr(prefix_rhs) { - prefix_rhs = p.assoc_or_init_expr(prefix_rhs) - } - prefix_rhs = p.finish_expr(prefix_rhs, .highest) - } else { - prefix_rhs = p.expr(.highest) - } - return p.finish_expr(ast.Expr(ast.PrefixExpr{ - pos: prefix_pos - op: .amp - expr: prefix_rhs - }), min_bp) - } if p.tok == .xor { lifetime_pos := p.pos p.next() diff --git a/vlib/v2/parser/parser_test.v b/vlib/v2/parser/parser_test.v new file mode 100644 index 000000000..62f4226af --- /dev/null +++ b/vlib/v2/parser/parser_test.v @@ -0,0 +1,105 @@ +// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +// vtest build: !windows +module parser + +import os +import v2.pref +import v2.token + +fn parse_code_for_test(code string) { + tmp_file := '/tmp/v2_parser_test_${os.getpid()}.v' + os.write_file(tmp_file, code) or { panic('failed to write temp file') } + defer { + os.rm(tmp_file) or {} + } + prefs := &pref.Preferences{} + mut file_set := token.FileSet.new() + mut par := Parser.new(prefs) + files := par.parse_files([tmp_file], mut file_set) + assert files.len == 1 +} + +fn test_if_condition_parses_selector_struct_init_before_infix() { + parse_code_for_test(' +module main + +fn main() { + mut pos := token.Pos{} + if pos == token.Pos{} && true { + x := 1 + } +} +') +} + +fn test_if_condition_keeps_uppercase_selector_followed_by_block() { + parse_code_for_test(' +module main + +fn main() int { + ch := 0 + if ch == C.EOF { + return -1 + } + return ch +} +') +} + +fn test_match_branch_keeps_empty_uppercase_branch_block() { + parse_code_for_test(' +module main + +type Obj = EmptyScopeObject | AsmRegister + +struct AsmRegister {} +struct EmptyScopeObject {} + +fn main() { + node := Obj(EmptyScopeObject{}) + match node { + AsmRegister, EmptyScopeObject {} + } +} +') +} + +fn test_global_unsafe_block_parses_leading_array_literal_stmt() { + parse_code_for_test(' +module main + +struct File {} + +__global files = unsafe { []&File{} } + +fn main() {} +') +} + +fn test_array_literal_parses_backslash_char_literal() { + parse_code_for_test(' +module main + +fn main() { + x := [u8(`\\\\`)] + _ = x +} +') +} + +fn test_call_argument_parses_untyped_backslash_char_array_literal() { + parse_code_for_test(' +module main + +fn b(bytes []u8) []u8 { + return bytes +} + +fn main() { + x := b([`\\\\`]) + _ = x +} +') +} diff --git a/vlib/v2/transformer/flat_write.v b/vlib/v2/transformer/flat_write.v index 99036194b..3f4810f61 100644 --- a/vlib/v2/transformer/flat_write.v +++ b/vlib/v2/transformer/flat_write.v @@ -1952,6 +1952,103 @@ fn (mut t Transformer) transform_stmt_list_item_to_flat(stmt ast.Stmt, mut ids [ // First converted set: true-passthrough top-level kinds that carry no // `try_expand_*` guard and that `transform_stmt_to_flat` emits verbatim. Those // copy their flat subtrees directly, so they do not route through `c.stmt()`. + +// classify_call_fallback_cursor builds a diagnostic key describing WHY a call +// fell back to the legacy decode path. Instrumentation for V2_FLAT_FB_STATS. +fn (t &Transformer) classify_call_fallback_cursor(c ast.Cursor, prefix string) string { + lhs := c.edge(0) + if lhs.kind() != .expr_selector { + return '${prefix}/lhs=${lhs.kind()}' + } + receiver := lhs.edge(0) + method_name := selector_rhs_name_cursor(lhs) + if t.has_active_smartcast() { + return '${prefix}/sel/smartcast' + } + if method_name_needs_legacy_selector_pipeline(method_name) { + return '${prefix}/sel/legacy-list:${method_name}' + } + recv_type := t.get_expr_type_cursor(receiver) or { return '${prefix}/sel/no-recv-type' } + base := t.unwrap_alias_and_pointer_type(recv_type) + if base is types.Struct { + if !t.type_has_cached_method(base, method_name) { + return '${prefix}/sel/no-cached-method' + } + } else { + // Mirror the non-struct receiver gate: only its rejects classify as + // receiver failures; accepted receivers fall through to late gates. + if method_name in ['filter', 'map', 'any', 'count', 'wait'] { + return '${prefix}/sel/expansion-method:${method_name}' + } + if base is types.Interface || base is types.SumType { + return '${prefix}/sel/recv-dispatch=${base.name()}' + } + if t.resolve_alias_receiver_method_name(recv_type, method_name) == none + && !(t.type_is_string(recv_type) + && t.lookup_method_cached('string', method_name) != none) { + return '${prefix}/sel/recv=${typeof(base).name}:${base.name()}:m=${method_name}' + } + } + if t.resolve_static_type_method_call_cursor(receiver, method_name) != none { + return '${prefix}/sel/static-shadow' + } + call_name := t.resolve_method_call_name_cursor(receiver, method_name) or { + return '${prefix}/sel/no-call-name' + } + if call_name in t.elided_fns { + return '${prefix}/sel/elided' + } + info := t.generic_aware_call_fn_info_cursor(c.edge(0), call_name) or { + return '${prefix}/sel/no-fn-info' + } + arg_count := if c.kind() == .expr_call_or_cast { + arg0 := c.edge(1) + if arg0.is_valid() && arg0.kind() != .expr_empty { + 1 + } else { + 0 + } + } else { + c.edge_count() - 1 + } + if info.param_types.len != arg_count { + return '${prefix}/sel/arity:${call_name}:${info.param_types.len}vs${arg_count}' + } + if info.is_variadic { + return '${prefix}/sel/variadic' + } + if info.generic_params.len > 0 { + return '${prefix}/sel/generic' + } + for i in 0 .. arg_count { + arg := c.edge(i + 1) + if arg.kind() == .aux_field_init { + return '${prefix}/sel/field-init-arg' + } + if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) { + param_base := t.unwrap_alias_type(info.param_types[i]) + param_kind := match param_base { + types.Struct { 'struct' } + types.Enum { 'enum' } + types.Array { 'array' } + types.ArrayFixed { 'array_fixed' } + types.Map { 'map' } + types.SumType { 'sumtype' } + types.Interface { 'interface' } + types.FnType { 'fn' } + types.OptionType { 'option' } + types.ResultType { 'result' } + types.Pointer { 'pointer' } + types.String { 'string' } + else { 'other' } + } + + return '${prefix}/sel/arg-not-identity/param=${param_kind}' + } + } + return '${prefix}/sel/struct-late-gate-unknown' +} + fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) { match c.kind() { .stmt_import, .stmt_module, .stmt_directive, .stmt_empty, .stmt_enum_decl, @@ -1980,6 +2077,7 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } .stmt_struct_decl { + t.count_flat_fallback('stmt_struct_decl') id := out.emit_stmt(t.transform_struct_decl(c.struct_decl())) t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } @@ -1987,10 +2085,15 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut t.expand_assert_stmt_cursor_to_flat(c, mut ids, mut out) } .stmt_assign { + if id := t.transform_map_index_assign_cursor_to_flat(c, mut out) { + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + return + } if t.try_expand_if_expr_assign_cursor_to_flat(c, mut ids, mut out) { return } if t.assign_stmt_cursor_needs_legacy_expand(c) { + t.count_flat_fallback('stmt_assign') t.transform_stmt_list_item_to_flat(assign_stmt_from_cursor(c), mut ids, mut out) } else { id := t.transform_assign_stmt_cursor_to_flat(c, mut out) @@ -2002,11 +2105,19 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) } .stmt_expr { + if t.try_expand_comptime_if_stmt_cursor_to_flat(c, mut ids, mut out) { + return + } if c.edge(0).kind() == .expr_lock { t.expand_lock_expr_cursor_to_flat(c.edge(0), mut ids, mut out) return } + if id := t.transform_flag_enum_set_clear_cursor_to_flat(c, mut out) { + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + return + } if t.expr_stmt_cursor_needs_legacy_expand(c) { + t.count_flat_fallback('stmt_expr') t.transform_stmt_list_item_to_flat(expr_stmt_from_cursor(c), mut ids, mut out) } else { id := t.transform_expr_stmt_cursor_to_flat(c, mut out) @@ -2018,6 +2129,7 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut return } if t.return_stmt_cursor_needs_legacy_expand(c) { + t.count_flat_fallback('stmt_return') t.transform_stmt_list_item_to_flat(return_stmt_from_cursor(c), mut ids, mut out) } else { id := t.transform_return_stmt_cursor_to_flat(c, mut out) @@ -2025,6 +2137,7 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut } } .stmt_for_in { + t.count_flat_fallback('stmt_for_in') t.transform_stmt_list_item_to_flat(for_in_stmt_from_cursor(c), mut ids, mut out) } .stmt_fn_decl { @@ -2032,6 +2145,7 @@ fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut // lowering needs the whole body, so those take the legacy // whole-decl decode path). if flat_body_has_defer(c.list_at(3)) { + t.count_flat_fallback('stmt_fn_decl_defer') decl := fn_decl_signature_with_body_cursor(c.fn_decl_signature(), c) t.transform_stmt_list_item_to_flat(decl, mut ids, mut out) } else { @@ -2101,6 +2215,15 @@ pub fn (mut t Transformer) append_transformed_stmt_to_flat(mut ids []ast.FlatNod // cursor-native stmt arms (transform_stmt_list_item_cursor_to_flat) so both // drain pending side effects identically. fn (mut t Transformer) append_transformed_stmt_id_to_flat(mut ids []ast.FlatNodeId, id ast.FlatNodeId, mut out ast.FlatBuilder) { + // Flat-side hoists drain first: arms that push pending_flat_stmt_ids + // flush the legacy pending_stmts queue into it beforehand, so emitting + // the flat queue first preserves the legacy chronological order. + if t.pending_flat_stmt_ids.len > 0 { + for fid in t.pending_flat_stmt_ids { + ids << fid + } + t.pending_flat_stmt_ids.clear() + } if t.pending_stmts.len > 0 { pending := t.pending_stmts.clone() t.pending_stmts.clear() @@ -2400,6 +2523,9 @@ fn (t &Transformer) is_nil_expr_cursor(c ast.Cursor) bool { .expr_keyword { unsafe { token.Token(int(c.aux())) } == .key_nil } + .typ_nil { + true + } .expr_basic_literal { unsafe { token.Token(int(c.aux())) } == .number && c.name() == '0' } @@ -2814,6 +2940,317 @@ fn (t &Transformer) infix_and_cursor_can_transform_direct(c ast.Cursor) bool { return true } +// get_struct_field_type_cursor mirrors get_struct_field_type (struct.v) for a +// cursor selector, minus the smartcast branch — callers gate on +// !has_active_smartcast(), which makes that branch a no-op in the legacy +// pipeline too. +fn (t &Transformer) get_struct_field_type_cursor(sel ast.Cursor) ?types.Type { + field_name := selector_rhs_name_cursor(sel) + if field_name == '' { + return none + } + lhs := sel.edge(0) + mut struct_type_name := '' + if lhs.kind() == .expr_ident { + lhs_name := lhs.name() + if lhs_name == '' { + return none + } + if lhs_type := t.lookup_var_type(lhs_name) { + if field_typ := t.field_type_from_receiver_type(lhs_type, field_name) { + return field_typ + } + base_type := lhs_type.base_type() + if base_type is types.Struct { + if field_typ := t.lookup_struct_field_type(base_type.name, field_name) { + return field_typ + } + } + struct_type_name = t.type_to_name(base_type) + } + } + if struct_type_name != '' { + looked_up_type := t.lookup_type(struct_type_name) or { return none } + looked_base := if looked_up_type is types.Pointer { + looked_up_type.base_type + } else { + looked_up_type + } + match looked_base { + types.Struct { + if field_typ := t.lookup_struct_field_type(looked_base.name, field_name) { + return field_typ + } + } + else {} + } + } + struct_type := t.get_expr_type_cursor(lhs) or { return none } + base_type := if struct_type is types.Pointer { + struct_type.base_type + } else { + struct_type + } + match base_type { + types.Struct { + if field_typ := t.lookup_struct_field_type(base_type.name, field_name) { + return field_typ + } + } + else {} + } + + return none +} + +// try_transform_array_append_cursor_to_flat streams the common `arr << value` +// single-element append cursor-native, mirroring the legacy lowering in +// transform_infix_expr (expr.v): +// builtin__array_push_noscan((array*)&arr, (elem_type[]){ value }) +// The gates are deliberately strict so that every accepted shape provably +// takes the same single-push path in the legacy pipeline: +// - lhs is a plain ident whose scope type resolves to a dynamic array +// (mirrors get_array_elem_type_str's first, deterministic branch); +// - the elem type is not a (pointer-to-)sum type (no +// wrap_array_push_elem_value effect) and not a nested array (no +// push_many ambiguity); +// - the rhs has no calls (the legacy path hoists call-bearing values into +// a pending `_ap_tN` temp), is not enum shorthand, not a bitwise infix +// (reassociation), not an `_or_tN.data` extract, and its checker type is +// definitively NOT an array (so push_many cannot apply). +// Everything else returns none and keeps the legacy decode fallback. +fn (mut t Transformer) try_transform_array_append_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId { + if t.is_eval_backend() { + return none + } + lhs := c.edge(0) + rhs := c.edge(1) + mut elem_type_name := '' + mut lhs_is_ptr := false + if lhs.kind() == .expr_ident { + lhs_typ := t.lookup_var_type(lhs.name()) or { return none } + lhs_base := t.unwrap_alias_and_pointer_type(lhs_typ) + if lhs_base !is types.Array { + return none + } + arr_type := lhs_base as types.Array + // Mirrors the ident branch of get_array_elem_type_str (which + // normalizes literal elem type names). + elem_type_name = + t.normalize_literal_type(t.array_elem_type_name_for_helpers(arr_type.elem_type)) + lhs_is_ptr = (lhs.name() == t.cur_fn_recv_param && t.cur_fn_recv_is_ptr) + || t.is_pointer_type(lhs_typ) + } else if lhs.kind() == .expr_selector { + // Mirrors the SelectorExpr branch of get_array_elem_type_str: struct + // field type first, then the env type of the whole selector. Neither + // normalizes the elem name. Smartcast scopes stay legacy (the legacy + // chain consults smartcast_type_for_expr first). + if t.has_active_smartcast() { + return none + } + if field_typ := t.get_struct_field_type_cursor(lhs) { + field_base := t.unwrap_alias_and_pointer_type(field_typ) + if field_base is types.Array { + elem_type_name = t.array_elem_type_name_for_helpers(field_base.elem_type) + } + } + if elem_type_name == '' { + typ := t.get_expr_type_cursor(lhs) or { return none } + base := t.unwrap_alias_and_pointer_type(typ) + if base !is types.Array { + return none + } + arr_type := base as types.Array + elem_type_name = t.array_elem_type_name_for_helpers(arr_type.elem_type) + } + // is_pointer_type_expr has no selector branch: always address-of. + lhs_is_ptr = false + } else { + return none + } + if elem_type_name == '' || elem_type_name == 'void' { + return none + } + if elem_type_name.starts_with('Array_') || elem_type_name == 'array' { + return none + } + if t.sumtype_pointer_base(elem_type_name) != '' { + return none + } + elem_is_sumtype := t.is_sum_type(elem_type_name) + if rhs.kind() == .expr_selector { + rhs_lhs := rhs.edge(0) + if !rhs_lhs.is_valid() || rhs_lhs.kind() == .expr_empty { + return none + } + if selector_rhs_name_cursor(rhs) == 'data' && rhs_lhs.kind() == .expr_ident + && rhs_lhs.name().starts_with('_or_t') { + return none + } + } + if rhs.kind() == .expr_infix { + rhs_op := unsafe { token.Token(int(rhs.aux())) } + if rhs_op in [.amp, .pipe, .xor, .left_shift, .right_shift] { + return none + } + } + rhs_typ := t.get_expr_type_cursor(rhs) or { return none } + rhs_base := t.unwrap_alias_and_pointer_type(rhs_typ) + mut push_many := false + if rhs_base is types.Array { + // push_many candidate: resolve the rhs elem type the same way the + // legacy append_rhs_is_array_value_compatible chain does (ident: + // scope type, normalized; selector: struct field type, then the env + // type of the whole selector, unnormalized), then require + // push-compatibility with the lhs elem. Other rhs shapes keep the + // legacy path. + mut rhs_elem := '' + if rhs.kind() == .expr_ident { + rhs_scope_typ := t.lookup_var_type(rhs.name()) or { return none } + rhs_scope_base := t.unwrap_alias_and_pointer_type(rhs_scope_typ) + if rhs_scope_base !is types.Array { + return none + } + rhs_arr := rhs_scope_base as types.Array + rhs_elem = + t.normalize_literal_type(t.array_elem_type_name_for_helpers(rhs_arr.elem_type)) + } else if rhs.kind() == .expr_selector { + if t.has_active_smartcast() { + return none + } + if field_typ := t.get_struct_field_type_cursor(rhs) { + field_base := t.unwrap_alias_and_pointer_type(field_typ) + if field_base is types.Array { + rhs_elem = t.array_elem_type_name_for_helpers(field_base.elem_type) + } + } + if rhs_elem == '' { + rhs_arr := rhs_base as types.Array + rhs_elem = t.array_elem_type_name_for_helpers(rhs_arr.elem_type) + } + } else { + return none + } + if !t.array_elem_types_compatible(elem_type_name, rhs_elem) { + return none + } + push_many = true + } else if rhs_base is types.ArrayFixed { + return none + } else if rhs.kind() == .expr_ident { + // The legacy rhs analysis consults the scope before the checker env; + // require both to agree that the value is not an array. + if rhs_scope_typ := t.lookup_var_type(rhs.name()) { + rhs_scope_base := t.unwrap_alias_and_pointer_type(rhs_scope_typ) + if rhs_scope_base is types.Array || rhs_scope_base is types.ArrayFixed { + return none + } + } + } + if elem_is_sumtype { + // Only the no-wrap case streams: when the pushed value's checker type + // IS the sum type, wrap_array_push_elem_value provably returns it + // unchanged (for hoisted call values the legacy path wraps the + // `_ap_tN` temp ident, whose registered type is the sum type — same + // no-op). Variant-typed values that need the sumtype init wrap keep + // the legacy path, as do smartcast scopes. + if t.has_active_smartcast() { + return none + } + if !t.is_same_sumtype_name(t.type_to_c_name(rhs_typ), elem_type_name) { + return none + } + if rhs.kind() == .expr_ident { + rhs_scope_typ := t.lookup_var_type(rhs.name()) or { return none } + if !t.is_same_sumtype_name(t.type_to_c_name(rhs_scope_typ), elem_type_name) { + return none + } + } else if rhs.kind() != .expr_selector && !t.contains_call_expr_cursor(rhs) { + return none + } + } + // Emission mirrors the legacy tree shape and evaluation order exactly + // (lhs transform, then rhs transform; synthetic wrapper nodes carry a + // zero pos like their legacy counterparts). + lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out) + cast_inner_id := if lhs_is_ptr { + lhs_id + } else { + out.emit_prefix_expr_by_id(.amp, lhs_id, c.pos()) + } + array_ptr_typ_id := out.emit_ident_by_name('array*', token.Pos{}) + arr_ptr_id := out.emit_cast_expr_by_ids(array_ptr_typ_id, cast_inner_id, token.Pos{}) + mut rhs_id := t.transform_expr_cursor_to_flat(rhs, mut out) + if push_many { + // array__push_many(arr_ptr, rhs.data, rhs.len). Call-bearing values + // are hoisted into a `_pm_tN` temp first (the legacy path does the + // same so .data/.len don't evaluate the call twice); ident/selector + // rhs is never a PrefixExpr, so no paren wrap applies. data/len are + // typed synth selectors exactly like the legacy synth_selector calls + // (same synth-pos consumption order). + if t.contains_call_expr_cursor(rhs) { + if t.pending_stmts.len > 0 { + pending := t.pending_stmts.clone() + t.pending_stmts.clear() + for ps in pending { + t.pending_flat_stmt_ids << out.emit_stmt(ps) + } + } + t.temp_counter++ + tmp_name := '_pm_t${t.temp_counter}' + if rhs_type := t.get_expr_type_cursor(rhs) { + t.register_temp_var(tmp_name, rhs_type) + } + tmp_lhs_id := out.emit_ident_by_name(tmp_name, token.Pos{}) + t.pending_flat_stmt_ids << out.emit_assign_stmt_by_ids(.decl_assign, [ + tmp_lhs_id, + ], [rhs_id], token.Pos{}) + rhs_id = out.emit_ident_by_name(tmp_name, token.Pos{}) + } + data_id := + t.synth_selector_cursor_to_flat(rhs_id, 'data', types.Type(types.voidptr_), mut out) + len_id := t.synth_selector_cursor_to_flat(rhs_id, 'len', types.Type(types.int_), mut out) + push_many_lhs_id := out.emit_ident_by_name('array__push_many', token.Pos{}) + return out.emit_call_expr_by_ids(push_many_lhs_id, [arr_ptr_id, data_id, len_id], c.pos()) + } + if t.contains_call_expr_cursor(rhs) { + // The legacy path hoists call-bearing values into a `_ap_tN` temp via + // pending_stmts so the call is evaluated before the push. Flush the + // chronologically-earlier legacy pendings into the flat queue first, + // then queue the temp assign as an already-flat stmt. + if t.pending_stmts.len > 0 { + pending := t.pending_stmts.clone() + t.pending_stmts.clear() + for ps in pending { + t.pending_flat_stmt_ids << out.emit_stmt(ps) + } + } + t.temp_counter++ + tmp_name := '_ap_t${t.temp_counter}' + if rhs_type := t.get_expr_type_cursor(rhs) { + t.register_temp_var(tmp_name, rhs_type) + } + tmp_lhs_id := out.emit_ident_by_name(tmp_name, token.Pos{}) + t.pending_flat_stmt_ids << out.emit_assign_stmt_by_ids(.decl_assign, [ + tmp_lhs_id, + ], [rhs_id], token.Pos{}) + rhs_id = out.emit_ident_by_name(tmp_name, token.Pos{}) + } + value_id := if elem_type_name == 'string' { + clone_lhs_id := out.emit_ident_by_name('string__clone', token.Pos{}) + out.emit_call_expr_by_ids(clone_lhs_id, [rhs_id], token.Pos{}) + } else { + rhs_id + } + elem_ident_id := out.emit_ident_by_name(elem_type_name, token.Pos{}) + arr_typ_id := out.emit_array_type_by_elem_id(elem_ident_id) + arr_lit_id := out.emit_array_init_expr_by_ids(arr_typ_id, out.emit_expr(ast.empty_expr), + out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr), + out.emit_expr(ast.empty_expr), [value_id], token.Pos{}) + push_lhs_id := out.emit_ident_by_name('builtin__array_push_noscan', token.Pos{}) + return out.emit_call_expr_by_ids(push_lhs_id, [arr_ptr_id, arr_lit_id], c.pos()) +} + fn (t &Transformer) infix_left_shift_cursor_can_transform_direct(c ast.Cursor) bool { lhs_type := t.get_expr_type_cursor(c.edge(0)) or { return false } lhs_base := t.unwrap_alias_and_pointer_type(lhs_type) @@ -2925,6 +3362,60 @@ fn (t &Transformer) identity_call_arg_can_transform_direct(arg ast.Cursor, param types.Primitive, types.Char, types.ISize, types.Nil, types.Rune, types.String, types.USize { return true } + types.Struct { + // An arg whose checker type is EXACTLY the (non-generic) param + // struct needs no call-boundary coercion (no sumtype wrap, no + // interface boxing, no auto-deref). Smartcast scopes stay on the + // legacy path: an active cast can change an ident's effective + // type between the declared and narrowed form. + if base.generic_params.len > 0 || t.has_active_smartcast() { + return false + } + arg_typ := t.get_expr_type_cursor(arg) or { return false } + arg_base := t.unwrap_alias_type(arg_typ) + if arg_base is types.Struct { + return arg_base.name == base.name && arg_base.generic_params.len == 0 + } + return false + } + types.SumType { + // Exact sumtype-to-sumtype: the arg is already the param's sum + // type, so no variant wrapping applies. + if t.has_active_smartcast() { + return false + } + arg_typ := t.get_expr_type_cursor(arg) or { return false } + arg_base := t.unwrap_alias_type(arg_typ) + if arg_base is types.SumType { + return arg_base.name == base.name + } + return false + } + types.Enum { + // Typed enum values pass through unchanged. Bare `.value` + // shorthand args need the param's enum context to resolve, so + // they keep the legacy pipeline (as does anything that is not a + // plain ident/qualified selector). + if t.has_active_smartcast() { + return false + } + if arg.kind() == .expr_ident { + // fine: a typed enum variable resolves without param context + } else if arg.kind() == .expr_selector { + lhs := arg.edge(0) + if !lhs.is_valid() || lhs.kind() == .expr_empty { + return false + } + } else { + return false + } + arg_typ := t.get_expr_type_cursor(arg) or { return false } + arg_base := t.unwrap_alias_type(arg_typ) + if arg_base is types.Enum { + return arg_base.name == base.name + } + return false + } else { return false } @@ -3099,10 +3590,89 @@ fn (t &Transformer) receiver_method_cursor_can_transform_direct(receiver ast.Cur } recv_type := t.get_expr_type_cursor(receiver) or { return false } base := t.unwrap_alias_and_pointer_type(recv_type) - if base !is types.Struct { + if base is types.Struct { + return t.type_has_cached_method(base, method_name) + } + // Non-struct receivers. Methods with transformer-level expansions + // (hoisted loops) and dynamic-dispatch receivers stay on the legacy + // pipeline; the rest resolve to a plain method fn by name. + if method_name in ['filter', 'map', 'any', 'count', 'wait'] { + return false + } + if base is types.Interface || base is types.SumType { return false } - return t.type_has_cached_method(base, method_name) + // Alias receiver with its own declared method (e.g. strings.Builder.writeln). + if t.resolve_alias_receiver_method_name(recv_type, method_name) != none { + return true + } + // Plain string methods (string__starts_with etc.). + if t.type_is_string(recv_type) { + return t.lookup_method_cached('string', method_name) != none + } + return false +} + +// enum_shorthand_arg_can_transform_direct reports whether `arg` is a bare +// `.member` enum shorthand for an enum-typed parameter — the one non-identity +// arg shape the direct call arms resolve themselves (mirroring +// resolve_expr_with_expected_type -> resolve_enum_shorthand). +fn (t &Transformer) enum_shorthand_arg_can_transform_direct(arg ast.Cursor, param_typ types.Type) bool { + if t.has_active_smartcast() { + return false + } + param_base := t.unwrap_alias_type(param_typ) + if param_base !is types.Enum { + return false + } + if arg.kind() != .expr_selector { + return false + } + lhs := arg.edge(0) + return !lhs.is_valid() || lhs.kind() == .expr_empty +} + +// transform_call_arg_cursor_to_flat emits one direct-call argument with the +// parameter type as context: bare enum shorthand resolves to the member ident +// exactly like the legacy resolve_enum_shorthand (same synth-type +// registration at the selector pos); everything else transforms normally. +fn (mut t Transformer) transform_call_arg_cursor_to_flat(arg ast.Cursor, param_typ types.Type, mut out ast.FlatBuilder) ast.FlatNodeId { + if t.enum_shorthand_arg_can_transform_direct(arg, param_typ) { + param_base := t.unwrap_alias_type(param_typ) + enum_name := t.type_to_c_name(param_base) + member := selector_rhs_name_cursor(arg) + if typ := t.lookup_type(enum_name) { + t.register_synth_type(arg.pos(), typ) + if typ is types.Enum { + return out.emit_ident_by_name(t.enum_member_ident_for_lookup(enum_name, typ, member), + arg.pos()) + } + } + return out.emit_ident_by_name(enum_member_ident(enum_name, member), arg.pos()) + } + return t.transform_expr_cursor_to_flat(arg, mut out) +} + +// direct_method_call_fn_info_cursor returns the resolved method's declared +// receiver-less signature for the direct-call gates. The shared +// generic_aware_call_fn_info_cursor path heuristically strips a "receiver" +// param from checker fn types, which eats the real first param whenever its +// type equals the receiver type (e.g. s.starts_with(prefix) — both string). +// The cached method declaration never includes the receiver in its params, +// so it is the ground truth for arity and per-arg identity checks. +fn (t &Transformer) direct_method_call_fn_info_cursor(lhs ast.Cursor, method_name string, call_name string) ?CallFnInfo { + recv_key := call_name.all_before_last('__') + mut lookup_names := []string{cap: 2} + lookup_names << recv_key + if recv_key.contains('__') { + lookup_names << recv_key.all_after_last('__') + } + for name in lookup_names { + if fn_type := t.lookup_method_cached(name, method_name) { + return call_fn_info_from_fn_type(fn_type) + } + } + return t.generic_aware_call_fn_info_cursor(lhs, call_name) } fn (t &Transformer) call_selector_method_name_can_transform_direct(c ast.Cursor) ?string { @@ -3126,7 +3696,7 @@ fn (t &Transformer) call_selector_method_name_can_transform_direct(c ast.Cursor) if call_name in t.elided_fns { return none } - info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none } + info := t.direct_method_call_fn_info_cursor(lhs, method_name, call_name) or { return none } arg_count := c.edge_count() - 1 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 { return none @@ -3136,7 +3706,8 @@ fn (t &Transformer) call_selector_method_name_can_transform_direct(c ast.Cursor) if arg.kind() == .aux_field_init { return none } - if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) { + if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) + && !t.enum_shorthand_arg_can_transform_direct(arg, info.param_types[i]) { return none } } @@ -3244,13 +3815,14 @@ fn (t &Transformer) call_or_cast_selector_method_name_can_transform_direct(c ast } arg := c.edge(1) arg_count := if arg.is_valid() && arg.kind() != .expr_empty { 1 } else { 0 } - info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none } + info := t.direct_method_call_fn_info_cursor(lhs, method_name, call_name) or { return none } if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 { return none } if arg_count == 1 { if arg.kind() == .aux_field_init - || !t.identity_call_arg_can_transform_direct(arg, info.param_types[0]) { + || (!t.identity_call_arg_can_transform_direct(arg, info.param_types[0]) + && !t.enum_shorthand_arg_can_transform_direct(arg, info.param_types[0])) { return none } } @@ -4010,10 +4582,12 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F return t.emit_lowered_expr_result_to_flat(result, mut out) } .expr_assoc { + t.count_flat_fallback('expr_assoc') result := t.lower_assoc_expr(assoc_expr_from_cursor(c), false) return t.emit_lowered_expr_result_to_flat(result, mut out) } .expr_or { + t.count_flat_fallback('expr_or') return t.transform_expr_to_flat(ast.Expr(or_expr_from_cursor(c)), mut out) } .expr_infix { @@ -4039,11 +4613,17 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F if result_id := t.transform_map_membership_cursor_to_flat(c, op, mut out) { return result_id } + if op == .left_shift { + if result_id := t.try_transform_array_append_cursor_to_flat(c, mut out) { + return result_id + } + } if t.infix_cursor_can_transform_direct(c, op) { lhs_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out) rhs_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out) return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, t.infix_cursor_result_pos(c)) } + t.count_flat_fallback('expr_infix/op=${op}') result := t.transform_infix_expr(infix_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4080,14 +4660,22 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F } if call_name := t.call_selector_method_name_can_transform_direct(c) { lhs := c.edge(0) + info := t.direct_method_call_fn_info_cursor(lhs, selector_rhs_name_cursor(lhs), + call_name) or { CallFnInfo{} } lhs_id := out.emit_ident_by_name(call_name, lhs.pos()) mut arg_ids := []ast.FlatNodeId{cap: c.edge_count()} arg_ids << t.transform_expr_cursor_to_flat(lhs.edge(0), mut out) for i in 1 .. c.edge_count() { - arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out) + if i - 1 < info.param_types.len { + arg_ids << t.transform_call_arg_cursor_to_flat(c.edge(i), + info.param_types[i - 1], mut out) + } else { + arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out) + } } return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos()) } + t.count_flat_fallback(t.classify_call_fallback_cursor(c, 'expr_call')) result := t.transform_call_expr(call_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4120,11 +4708,17 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos()) } if call_name := t.call_or_cast_selector_method_name_can_transform_direct(c) { + info := t.direct_method_call_fn_info_cursor(lhs, selector_rhs_name_cursor(lhs), + call_name) or { CallFnInfo{} } lhs_id := out.emit_ident_by_name(call_name, lhs.pos()) mut arg_ids := []ast.FlatNodeId{cap: 2} arg_ids << t.transform_expr_cursor_to_flat(lhs.edge(0), mut out) if arg.is_valid() && arg.kind() != .expr_empty { - arg_ids << t.transform_expr_cursor_to_flat(arg, mut out) + if info.param_types.len > 0 { + arg_ids << t.transform_call_arg_cursor_to_flat(arg, info.param_types[0], mut out) + } else { + arg_ids << t.transform_expr_cursor_to_flat(arg, mut out) + } } return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos()) } @@ -4157,6 +4751,7 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F return out.emit_cast_expr_by_ids(typ_id, expr_id, c.pos()) } } + t.count_flat_fallback(t.classify_call_fallback_cursor(c, 'expr_coc')) result := t.transform_call_or_cast_expr(call_or_cast_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4164,6 +4759,7 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F if result_id := t.transform_map_init_cursor_to_flat(c, mut out) { return result_id } + t.count_flat_fallback('expr_map_init') result := t.transform_map_init_expr(map_init_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4177,6 +4773,7 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F if result_id := t.transform_explicit_dynamic_array_init_cursor_to_flat(c, mut out) { return result_id } + t.count_flat_fallback('expr_array_init') result := t.transform_array_init_expr(array_init_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4184,16 +4781,19 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F if result_id := t.transform_empty_map_init_cursor_to_flat(c, mut out) { return result_id } + t.count_flat_fallback('expr_init') return t.transform_init_expr_to_flat(init_expr_from_cursor(c), mut out) } .expr_if { if t.if_expr_cursor_can_transform_plain(c) { return t.transform_plain_if_expr_cursor_to_flat(c, mut out) } + t.count_flat_fallback('expr_if') result := t.transform_if_expr(if_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } .expr_match { + t.count_flat_fallback('expr_match') match_expr, branches := t.transform_match_expr_parts(match_expr_from_cursor(c)) return t.lower_match_expr_to_if_flat(match_expr, branches, mut out) } @@ -4307,6 +4907,7 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F rhs_id := out.copy_subtree_from(rhs.flat, rhs.id) return out.emit_selector_expr_by_ids(lhs_id, rhs_id, c.pos()) } + t.count_flat_fallback('expr_selector') result := t.transform_selector_expr(selector_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4317,6 +4918,7 @@ fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.F return out.emit_index_expr_by_ids(lhs_id, index_id, c.flag(ast.flag_is_gated), c.pos()) } + t.count_flat_fallback('expr_index') result := t.transform_index_expr(index_expr_from_cursor(c)) return t.emit_lowered_expr_result_to_flat(result, mut out) } @@ -4760,10 +5362,16 @@ fn (t &Transformer) selector_cursor_can_transform_direct(c ast.Cursor) bool { if rhs_name == '' { return false } - if rhs_name in ['name', 'idx', '_tag', '_data'] || rhs_name.starts_with('_') { + if rhs_name in ['_tag', '_data'] || rhs_name.starts_with('_') { return false } lhs := c.edge(0) + if rhs_name in ['name', 'idx'] + && lhs.kind() in [.expr_keyword_operator, .expr_call, .expr_call_or_cast] { + // typeof(x).name / typeof[T]().idx lower to literals in the legacy + // pipeline; plain field accesses named `name`/`idx` are ordinary. + return false + } if lhs.kind() == .expr_ident { lhs_name := lhs.name() if lhs_name == 'os' && rhs_name == 'args' { @@ -5623,6 +6231,102 @@ fn (mut t Transformer) transform_comptime_stmt_cursor_to_flat(c ast.Cursor, mut return t.transform_stmt_cursor_to_flat(inner, mut out) } +fn (mut t Transformer) try_expand_comptime_if_stmt_cursor_to_flat(c ast.Cursor, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool { + if if_expr := t.comptime_if_cursor_from_expr_stmt(c) { + if !t.can_eval_selected_comptime_if_cursor(if_expr) { + return false + } + selected_ids := t.selected_comptime_if_cursor_stmts_to_flat(if_expr, mut out) + for id in selected_ids { + t.append_transformed_stmt_id_to_flat(mut ids, id, mut out) + } + return true + } + return false +} + +fn (mut t Transformer) transform_comptime_if_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId { + if if_expr := t.comptime_if_cursor_from_expr_stmt(c) { + if !t.can_eval_selected_comptime_if_cursor(if_expr) { + return none + } + selected_ids := t.selected_comptime_if_cursor_stmts_to_flat(if_expr, mut out) + if selected_ids.len == 1 { + return selected_ids[0] + } + return out.emit_block_stmt_by_ids(selected_ids) + } + return none +} + +fn (t &Transformer) comptime_if_cursor_from_expr_stmt(c ast.Cursor) ?ast.Cursor { + if !c.is_valid() || c.kind() != .stmt_expr || c.edge_count() == 0 { + return none + } + expr := c.edge(0) + if !expr.is_valid() || expr.kind() != .expr_comptime { + return none + } + inner := expr.edge(0) + if !inner.is_valid() || inner.kind() != .expr_if { + return none + } + return inner +} + +fn (t &Transformer) can_eval_selected_comptime_if_cursor(c ast.Cursor) bool { + if !c.is_valid() || c.kind() != .expr_if { + return false + } + cond := c.edge(0) + if !t.can_eval_comptime_cond_cursor(cond) { + return false + } + if t.eval_comptime_cond_cursor(cond) { + return true + } + else_expr := c.edge(1) + if !else_expr.is_valid() || else_expr.kind() == .expr_empty { + return true + } + if else_expr.kind() != .expr_if { + return true + } + if expr_cursor_is_empty(else_expr.edge(0)) { + return true + } + return t.can_eval_selected_comptime_if_cursor(else_expr) +} + +fn (mut t Transformer) selected_comptime_if_cursor_stmts_to_flat(c ast.Cursor, mut out ast.FlatBuilder) []ast.FlatNodeId { + if !c.is_valid() || c.kind() != .expr_if { + return []ast.FlatNodeId{} + } + if t.eval_comptime_cond_cursor(c.edge(0)) { + return t.transform_cursor_stmts_to_flat_direct(ast.CursorList{ + flat: c.flat + parent_id: c.id + offset: 2 + }, [], mut out) + } + else_expr := c.edge(1) + if !else_expr.is_valid() || else_expr.kind() == .expr_empty { + return []ast.FlatNodeId{} + } + if else_expr.kind() == .expr_if { + if expr_cursor_is_empty(else_expr.edge(0)) { + return t.transform_cursor_stmts_to_flat_direct(ast.CursorList{ + flat: else_expr.flat + parent_id: else_expr.id + offset: 2 + }, [], mut out) + } + return t.selected_comptime_if_cursor_stmts_to_flat(else_expr, mut out) + } + expr_id := t.transform_expr_cursor_to_flat(else_expr, mut out) + return [out.emit_expr_stmt_by_id(expr_id)] +} + fn (mut t Transformer) transform_label_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { inner_id := t.transform_stmt_cursor_to_flat(c.edge(0), mut out) return out.emit_label_stmt_by_id(c.name(), inner_id) @@ -5759,10 +6463,11 @@ fn make_exit_one_stmt_to_flat(pos token.Pos, mut out ast.FlatBuilder) ast.FlatNo } fn (mut t Transformer) transform_assign_stmt_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + t.remember_decl_assign_cursor_type(c) lhs_len := c.extra_int() mut lhs_ids := []ast.FlatNodeId{cap: lhs_len} for i in 0 .. lhs_len { - lhs_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out) + lhs_ids << t.transform_assign_lhs_cursor_to_flat(c.edge(i), mut out) } mut rhs_ids := []ast.FlatNodeId{cap: c.edge_count() - lhs_len} for i in lhs_len .. c.edge_count() { @@ -5772,6 +6477,162 @@ fn (mut t Transformer) transform_assign_stmt_cursor_to_flat(c ast.Cursor, mut ou return out.emit_assign_stmt_by_ids(op, lhs_ids, rhs_ids, c.pos()) } +fn (mut t Transformer) transform_assign_lhs_cursor_to_flat(lhs ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId { + if lhs.kind() == .expr_ident { + return out.emit_ident_by_name(lhs.name(), lhs.pos()) + } + return t.transform_expr_cursor_to_flat(lhs, mut out) +} + +fn (mut t Transformer) remember_decl_assign_cursor_type(c ast.Cursor) { + if c.kind() != .stmt_assign || unsafe { token.Token(int(c.aux())) } != .decl_assign + || c.extra_int() != 1 || c.edge_count() != 2 { + return + } + lhs := c.edge(0) + rhs := c.edge(1) + lhs_name := t.get_var_name_cursor(lhs) + if lhs_name == '' || lhs_name == '_' { + return + } + if decl_type := t.decl_assign_storage_type_cursor(lhs, rhs) { + t.remember_decl_assign_lhs_type_cursor(lhs, decl_type) + } + if rhs_type := t.fn_pointer_call_return_type_cursor(rhs) { + t.register_temp_var(lhs_name, rhs_type) + } else if rhs_type := t.smartcast_type_for_expr_cursor(rhs) { + t.register_local_var_type(lhs_name, rhs_type) + } else if rhs_type := t.rune_arithmetic_expr_type_cursor(rhs) { + t.register_local_var_type(lhs_name, rhs_type) + } else if rhs.kind() == .expr_array_init { + if rhs_type := t.get_array_init_expr_type_cursor(rhs) { + t.register_local_var_type(lhs_name, rhs_type) + } + } else if rhs.kind() == .expr_map_init { + typ_cursor := rhs.edge(0) + if typ_cursor.is_valid() && typ_cursor.kind() != .expr_empty { + if rhs_type := t.type_from_param_type_expr(typ_cursor.type_expr(), []) { + t.register_local_var_type(lhs_name, rhs_type) + } + } + } else if rhs.kind() in [.expr_call, .expr_call_or_cast, .expr_init, .expr_ident, .expr_selector] { + if rhs_type := t.get_expr_type_cursor(rhs) { + t.register_local_var_type(lhs_name, rhs_type) + } + } + if bindings := t.generic_bindings_from_generic_call_expr_cursor(rhs) { + t.local_receiver_generic_bindings[lhs_name] = bindings.clone() + } +} + +fn (mut t Transformer) remember_decl_assign_lhs_type_cursor(lhs ast.Cursor, typ types.Type) { + match lhs.kind() { + .expr_ident { + if lhs.name() == '_' { + return + } + t.remember_local_decl_type(lhs.name(), typ) + t.register_local_var_type(lhs.name(), typ) + if lhs.pos().id != 0 { + t.register_synth_type(lhs.pos(), typ) + } + } + .expr_modifier { + t.remember_decl_assign_lhs_type_cursor(lhs.edge(0), typ) + } + else {} + } +} + +fn (mut t Transformer) transform_map_index_assign_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId { + if t.is_eval_backend() || c.kind() != .stmt_assign { + return none + } + lhs_len := c.extra_int() + rhs_len := c.edge_count() - lhs_len + op := unsafe { token.Token(int(c.aux())) } + if op !in [.assign, .decl_assign] || lhs_len != 1 || rhs_len != 1 { + return none + } + lhs := c.edge(0) + if lhs.kind() != .expr_index { + return none + } + rhs := c.edge(lhs_len) + if t.cursor_subtree_has_or_expr(rhs) || rhs.kind() in [.expr_if, .expr_if_guard] { + return none + } + if rhs.kind() == .expr_comptime && rhs.edge(0).kind() == .expr_if { + return none + } + map_expr := lhs.edge(0) + key_expr := lhs.edge(1) + map_expr_typ := t.map_index_lhs_type_cursor(map_expr) or { return none } + map_type := t.unwrap_map_type(map_expr_typ) or { return none } + map_ptr_id := t.map_expr_to_runtime_ptr_cursor(map_expr, map_expr_typ, mut out) or { + return none + } + + key_ident := t.typed_temp_ident(t.gen_temp_name(), map_type.key_type) + key_lhs_id := out.emit_ident_by_name(key_ident.name, key_ident.pos) + key_value_id := t.transform_map_key_value_cursor_to_flat(key_expr, map_type.key_type, mut out) + key_assign_id := out.emit_assign_stmt_by_ids(.decl_assign, [key_lhs_id], [ + key_value_id, + ], key_ident.pos) + key_ref_id := out.emit_ident_by_name(key_ident.name, key_ident.pos) + key_ptr_id := out.emit_prefix_expr_by_id(.amp, key_ref_id, token.Pos{}) + + val_ident := t.typed_temp_ident(t.gen_temp_name(), map_type.value_type) + val_lhs_id := out.emit_ident_by_name(val_ident.name, val_ident.pos) + val_value_id := t.transform_map_assign_value_cursor_to_flat(rhs, map_type.value_type, mut out) + val_assign_id := out.emit_assign_stmt_by_ids(.decl_assign, [val_lhs_id], [ + val_value_id, + ], val_ident.pos) + val_ref_id := out.emit_ident_by_name(val_ident.name, val_ident.pos) + val_ptr_id := out.emit_prefix_expr_by_id(.amp, val_ref_id, token.Pos{}) + + call_lhs_id := out.emit_ident_by_name('map__set', token.Pos{}) + call_id := out.emit_call_expr_by_ids(call_lhs_id, [ + map_ptr_id, + emit_voidptr_cast_id(key_ptr_id, mut out), + emit_voidptr_cast_id(val_ptr_id, mut out), + ], c.pos()) + call_stmt_id := out.emit_expr_stmt_by_id(call_id) + return out.emit_block_stmt_by_ids([key_assign_id, val_assign_id, call_stmt_id]) +} + +fn emit_voidptr_cast_id(expr_id ast.FlatNodeId, mut out ast.FlatBuilder) ast.FlatNodeId { + typ_id := out.emit_ident_by_name('voidptr', token.Pos{}) + return out.emit_cast_expr_by_ids(typ_id, expr_id, token.Pos{}) +} + +fn (mut t Transformer) transform_map_key_value_cursor_to_flat(c ast.Cursor, key_type types.Type, mut out ast.FlatBuilder) ast.FlatNodeId { + enum_type := t.type_to_c_name(t.unwrap_alias_and_pointer_type(key_type)) + if enum_type != '' { + if enum_id := t.enum_shorthand_cursor_to_flat(c, enum_type, mut out) { + return enum_id + } + } + return t.transform_expr_cursor_to_flat(c, mut out) +} + +fn (mut t Transformer) transform_map_assign_value_cursor_to_flat(c ast.Cursor, value_type types.Type, mut out ast.FlatBuilder) ast.FlatNodeId { + base := t.unwrap_alias_and_pointer_type(value_type) + if base is types.Enum { + enum_type := t.type_to_c_name(base) + if enum_id := t.enum_shorthand_cursor_to_flat(c, enum_type, mut out) { + return enum_id + } + } + if base is types.SumType { + sumtype_name := t.type_to_c_name(base) + if wrapped_id := t.wrap_sumtype_value_cursor_to_flat(c, sumtype_name, mut out) { + return wrapped_id + } + } + return t.transform_expr_cursor_to_flat(c, mut out) +} + fn (t &Transformer) assign_stmt_cursor_needs_legacy_expand(c ast.Cursor) bool { if t.is_native_be { return true @@ -5781,6 +6642,10 @@ fn (t &Transformer) assign_stmt_cursor_needs_legacy_expand(c ast.Cursor) bool { if lhs_len != 1 || rhs_len != 1 { return true } + lhs := c.edge(0) + if t.assign_lhs_cursor_needs_legacy_rewrite(lhs) { + return true + } rhs := c.edge(lhs_len) if t.cursor_subtree_has_or_expr(rhs) { return true @@ -6514,7 +7379,11 @@ fn (mut t Transformer) transform_stmt_cursor_to_flat(c ast.Cursor, mut out ast.F return out.emit_assert_stmt_by_id(expr_id) } .stmt_assign { + if id := t.transform_map_index_assign_cursor_to_flat(c, mut out) { + return id + } if t.assign_stmt_cursor_needs_legacy_expand(c) { + t.count_flat_fallback('nested_assign') return t.transform_stmt_to_flat(assign_stmt_from_cursor(c), mut out) } return t.transform_assign_stmt_cursor_to_flat(c, mut out) @@ -6523,7 +7392,14 @@ fn (mut t Transformer) transform_stmt_cursor_to_flat(c ast.Cursor, mut out ast.F return t.transform_comptime_stmt_cursor_to_flat(c, mut out) } .stmt_expr { + if id := t.transform_comptime_if_stmt_cursor_to_flat(c, mut out) { + return id + } + if id := t.transform_flag_enum_set_clear_cursor_to_flat(c, mut out) { + return id + } if t.expr_stmt_cursor_needs_legacy_expand(c) { + t.count_flat_fallback('nested_expr') return t.transform_stmt_to_flat(expr_stmt_from_cursor(c), mut out) } return t.transform_expr_stmt_cursor_to_flat(c, mut out) @@ -6532,10 +7408,12 @@ fn (mut t Transformer) transform_stmt_cursor_to_flat(c ast.Cursor, mut out ast.F return t.transform_return_stmt_cursor_to_flat(c, mut out) } .stmt_for_in { + t.count_flat_fallback('nested_for_in') return t.transform_stmt_to_flat(for_in_stmt_from_cursor(c), mut out) } .stmt_fn_decl { if flat_body_has_defer(c.list_at(3)) { + t.count_flat_fallback('nested_fn_decl_defer') decl := fn_decl_signature_with_body_cursor(c.fn_decl_signature(), c) return t.transform_stmt_to_flat(decl, mut out) } @@ -6544,6 +7422,7 @@ fn (mut t Transformer) transform_stmt_cursor_to_flat(c ast.Cursor, mut out ast.F .stmt_for { init_c := c.edge(0) if init_c.is_valid() && init_c.kind() == .stmt_for_in { + t.count_flat_fallback('nested_for') return t.transform_stmt_to_flat(for_stmt_from_cursor(c), mut out) } return t.transform_for_stmt_streaming_to_flat(c, mut out) @@ -8470,6 +9349,99 @@ pub fn (mut t Transformer) try_emit_flag_enum_set_clear_to_flat(stmt ast.ExprStm return true } +fn (mut t Transformer) transform_flag_enum_set_clear_cursor_to_flat(stmt ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId { + if !stmt.is_valid() || stmt.kind() != .stmt_expr || stmt.edge_count() == 0 { + return none + } + expr := stmt.edge(0) + mut receiver := ast.Cursor{} + mut arg := ast.Cursor{} + mut method_name := '' + match expr.kind() { + .expr_call { + if expr.edge_count() == 2 { + lhs := expr.edge(0) + if lhs.kind() != .expr_selector { + return none + } + method_name = selector_rhs_name_cursor(lhs) + receiver = lhs.edge(0) + arg = expr.edge(1) + } else if expr.edge_count() == 3 { + lhs := expr.edge(0) + if lhs.kind() != .expr_ident { + return none + } + name := lhs.name() + if name.ends_with('__set') { + method_name = 'set' + } else if name.ends_with('__clear') { + method_name = 'clear' + } else { + return none + } + receiver = expr.edge(1) + arg = expr.edge(2) + } else { + return none + } + } + .expr_call_or_cast { + if expr.edge_count() < 2 { + return none + } + lhs := expr.edge(0) + if lhs.kind() != .expr_selector || t.call_or_cast_lhs_is_type_cursor(lhs) { + return none + } + method_name = selector_rhs_name_cursor(lhs) + receiver = lhs.edge(0) + arg = expr.edge(1) + } + else { + return none + } + } + + if method_name !in ['set', 'clear'] || !receiver.is_valid() || !arg.is_valid() { + return none + } + enum_type := t.get_enum_type_name_cursor(receiver) + if enum_type == '' || !t.is_flag_enum(enum_type) { + return none + } + lhs_id := t.transform_expr_cursor_to_flat(receiver, mut out) + arg_id := t.transform_flag_enum_arg_cursor_to_flat(arg, enum_type, mut out) + rhs_id := if method_name == 'clear' { + out.emit_prefix_expr_by_id(.bit_not, arg_id, token.Pos{}) + } else { + arg_id + } + op := if method_name == 'set' { token.Token.or_assign } else { token.Token.and_assign } + return out.emit_assign_stmt_by_ids(op, [lhs_id], [rhs_id], expr.pos()) +} + +fn (mut t Transformer) transform_flag_enum_arg_cursor_to_flat(arg ast.Cursor, enum_type string, mut out ast.FlatBuilder) ast.FlatNodeId { + if id := t.enum_shorthand_cursor_to_flat(arg, enum_type, mut out) { + return id + } + match arg.kind() { + .expr_infix { + op := unsafe { token.Token(int(arg.aux())) } + lhs_id := t.transform_flag_enum_arg_cursor_to_flat(arg.edge(0), enum_type, mut out) + rhs_id := t.transform_flag_enum_arg_cursor_to_flat(arg.edge(1), enum_type, mut out) + return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, arg.pos()) + } + .expr_paren { + inner_id := t.transform_flag_enum_arg_cursor_to_flat(arg.edge(0), enum_type, mut out) + return out.emit_paren_expr_by_id(inner_id, arg.pos()) + } + else { + return t.transform_expr_cursor_to_flat(arg, mut out) + } + } +} + // try_emit_map_index_push_to_flat is the flat-direct mirror of the // map-index-push single-emit arm in `transform_stmts_to_flat_direct` // (flat_write.v:1856). Thin-dispatcher port (s134 wrap pattern). Delegates diff --git a/vlib/v2/transformer/fn.v b/vlib/v2/transformer/fn.v index d8c9932b6..1349f9fb4 100644 --- a/vlib/v2/transformer/fn.v +++ b/vlib/v2/transformer/fn.v @@ -74,6 +74,16 @@ fn (t &Transformer) type_from_param_type_expr(expr ast.Expr, generic_params []st return none } +fn fixed_array_len_from_type_expr(expr ast.Expr) int { + if expr is ast.BasicLiteral { + return expr.value.int() + } + if expr is ast.Ident { + return expr.name.int() + } + return 0 +} + fn (t &Transformer) generic_aware_type_from_param_type_expr(expr ast.Expr, generic_params []string) ?types.Type { if expr is ast.Ident { if expr.name in generic_params { @@ -105,7 +115,7 @@ fn (t &Transformer) generic_aware_type_from_param_type_expr(expr ast.Expr, gener return none } return types.Type(types.ArrayFixed{ - len: expr.len.str().int() + len: fixed_array_len_from_type_expr(expr.len) elem_type: elem }) } diff --git a/vlib/v2/transformer/if.v b/vlib/v2/transformer/if.v index 9eec6eb1b..1b090d402 100644 --- a/vlib/v2/transformer/if.v +++ b/vlib/v2/transformer/if.v @@ -1307,6 +1307,40 @@ fn (t &Transformer) can_eval_comptime_cond(cond ast.Expr) bool { } } +fn (t &Transformer) can_eval_comptime_cond_cursor(cond ast.Cursor) bool { + if !cond.is_valid() { + return false + } + match cond.kind() { + .expr_ident { + return true + } + .expr_comptime, .expr_paren { + return t.can_eval_comptime_cond_cursor(cond.edge(0)) + } + .expr_call, .expr_call_or_cast { + return transformer_pkgconfig_call_name_cursor(cond) != none + } + .expr_prefix { + return t.can_eval_comptime_cond_cursor(cond.edge(0)) + } + .expr_infix { + op := unsafe { token.Token(int(cond.aux())) } + if op == .key_is || op == .not_is { + return false + } + return t.can_eval_comptime_cond_cursor(cond.edge(0)) + && t.can_eval_comptime_cond_cursor(cond.edge(1)) + } + .expr_postfix { + return true + } + else { + return false + } + } +} + // transform_comptime_if_bodies recursively transforms the body stmts of each // branch in a comptime $if, without evaluating the condition. This is used when // the condition can't be evaluated at transform time (e.g., generic type checks). diff --git a/vlib/v2/transformer/transformer.v b/vlib/v2/transformer/transformer.v index 21cf35a20..3ad289ba0 100644 --- a/vlib/v2/transformer/transformer.v +++ b/vlib/v2/transformer/transformer.v @@ -27,6 +27,16 @@ pub struct Transformer { mut: pref &pref.Preferences = unsafe { nil } env &types.Environment + // flat_fb_counts tracks how often each cursor-transform arm falls back to + // a legacy decode, keyed by arm name. Dumped via V2_FLAT_FB_STATS to + // prioritize the remaining flat-native migration work. + flat_fb_counts map[string]int + // pending_flat_stmt_ids queues already-emitted flat stmt ids that + // cursor-native arms hoist ahead of the statement being transformed — + // the flat-side twin of `pending_stmts`. Hoisting arms must first flush + // `pending_stmts` into this queue so the combined drain keeps the legacy + // chronological order. + pending_flat_stmt_ids []ast.FlatNodeId // Current scope for type lookups (walks up scope chain) scope &types.Scope = unsafe { nil } // Function root scope for registering transformer-created temp variables @@ -3136,8 +3146,14 @@ fn (mut t Transformer) transform_files_from_flat_no_post_pass(flat &ast.FlatAst, // transform_files_to_flat is the legacy-compatible flat-output entry point. // It wraps transform_files_from_flat + ast.flatten_files and returns both the -// FlatAst and transformed files for callers that still need []ast.File. Flat -// codegen paths should use transform_flat_to_flat_direct instead. +// FlatAst and transformed files for callers that still need []ast.File. +// +// NO PRODUCTION CALLERS remain: the builder uses transform_flat_to_flat_direct +// (sequential) / transform_files_parallel_flat_direct (parallel) for every +// backend, and .v/eval rehydrate via flat.to_files() at the codegen boundary. +// Kept only as the legacy parity reference for the flat-diff test suite; delete +// together with the remaining decode-fallback arms once the cursor-native +// transform is complete. pub fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) { result := t.transform_files_from_flat(flat, files) return ast.flatten_files(result), result @@ -3171,8 +3187,11 @@ pub fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []as // the stmts list (file root edge 2) to assert structural parity. // // Memory: this avoids the legacy post_pass + flatten_files boundary but still -// returns []ast.File for compatibility consumers. Flat-codegen backends bypass -// this with transform_flat_to_flat_direct or the parallel flat-direct path. +// returns []ast.File for compatibility consumers. +// +// NO PRODUCTION CALLERS remain (every backend transforms flat-direct; .v/eval +// rehydrate at the codegen boundary). Kept only as the legacy parity reference +// for tests; delete once the cursor-native transform is complete. pub fn (mut t Transformer) transform_files_to_flat_via_driver(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) { timing := os.getenv('V2_TTIME') != '' mut sw := time.new_stopwatch() @@ -3307,9 +3326,37 @@ pub fn (mut t Transformer) transform_flat_to_flat_direct(flat &ast.FlatAst, file if timing { eprintln(' [ttime] flat input direct post_pass: ${sw.elapsed().milliseconds()}ms') } + t.dump_flat_fallback_stats() return builder.flat } +// count_flat_fallback records one legacy-decode fallback hit for a cursor +// transform arm. Used to prioritize the remaining flat-native migration. +@[inline] +fn (mut t Transformer) count_flat_fallback(key string) { + t.flat_fb_counts[key]++ +} + +// dump_flat_fallback_stats prints the per-arm legacy-decode fallback counts +// recorded during the transform, gated on V2_FLAT_FB_STATS. +fn (t &Transformer) dump_flat_fallback_stats() { + if os.getenv('V2_FLAT_FB_STATS') == '' { + return + } + mut keys := t.flat_fb_counts.keys() + keys.sort_with_compare(fn [t] (a &string, b &string) int { + return t.flat_fb_counts[*b] - t.flat_fb_counts[*a] + }) + mut total := 0 + for k in keys { + total += t.flat_fb_counts[k] + } + eprintln('[flat-fb] total legacy-decode fallbacks: ${total}') + for k in keys { + eprintln('[flat-fb] ${t.flat_fb_counts[k]:8} ${k}') + } +} + fn new_transform_output_flat_builder(files []ast.File) ast.FlatBuilder { mut total_bytes := i64(0) for file in files { @@ -12115,7 +12162,13 @@ fn (mut t Transformer) append_active_defers(mut out []ast.Stmt, active_defers [] mut all_defers := []LoweredDefer{cap: active_defers.len + function_defers.len} all_defers << active_defers all_defers << function_defers - all_defers.sort(a.seq > b.seq) + for i := 1; i < all_defers.len; i++ { + mut j := i + for j > 0 && all_defers[j - 1].seq < all_defers[j].seq { + all_defers[j - 1], all_defers[j] = all_defers[j], all_defers[j - 1] + j-- + } + } for defer_entry in all_defers { t.append_defer_body(mut out, defer_entry) } diff --git a/vlib/v2/types/checker.v b/vlib/v2/types/checker.v index ea100efde..cd90be601 100644 --- a/vlib/v2/types/checker.v +++ b/vlib/v2/types/checker.v @@ -57,12 +57,25 @@ pub mut: } pub fn Environment.new() &Environment { - return &Environment{ + return Environment.new_with_capacity(0, 0) +} + +// Environment.new_with_capacity returns a new checker environment with the hot +// expression metadata maps pre-sized for large flat-AST builds. +pub fn Environment.new_with_capacity(expr_types_cap int, selector_names_cap int) &Environment { + mut env := &Environment{ expr_types: map[int]Type{} selector_names: map[int]string{} c_scope: new_scope(unsafe { nil }) c_scope_mu: sync.new_mutex() } + if expr_types_cap > 0 { + env.expr_types.reserve(u32(expr_types_cap)) + } + if selector_names_cap > 0 { + env.selector_names.reserve(u32(selector_names_cap)) + } + return env } // set_expr_type stores the computed type for an expression by its unique ID. @@ -5289,7 +5302,9 @@ fn (mut c Checker) check_pending_fn_body(pending PendingFnBody) bool { // Skip checking generic functions that are never instantiated. return false } - c.env.cur_generic_types << generic_types + for generic_type_map in generic_types { + c.env.cur_generic_types << generic_type_map + } } if !has_decl_generic_params || c.env.cur_generic_types.len > 0 { mut decl := signature_decl @@ -5363,7 +5378,7 @@ fn (mut c Checker) check_pending_fn_body(pending PendingFnBody) bool { c.scope = prev_scope c.cur_file_module = prev_module } - c.env.cur_generic_types = [] + c.env.cur_generic_types = []map[string]Type{} return true } @@ -7606,7 +7621,7 @@ fn (mut c Checker) fn_type_with_insert_params(fn_type ast.FnType, attributes FnT // scope: c.scope } if generic_params.len > 0 { - c.generic_params = [] + c.generic_params = []string{} } // c.close_scope() return typ -- 2.39.5