diff --git a/doc/fastc.md b/doc/fastc.md index 6fa8a11b4c..73ad63b3a6 100644 --- a/doc/fastc.md +++ b/doc/fastc.md @@ -235,6 +235,11 @@ v -b fastc -nocache -o v_fastc1 cmd/v Each generation retains the full command-line entry and can compile programs with either the C or FastC backend. `-nocache` forces each generation to rebuild its generated C units. +Clang production self-builds also exercise this full-compiler FastC path during PGO training +when FastC is included. Compiler sources must stay within the FastC self-hosting subset for +that training to complete. +This path preserves enum shorthand types in parallel assignments and imported call arguments, +and lowers flag enum `.has(flag)` calls on variables and imported function results. On macOS ARM64, standard full-compiler builds compile the owned native support files together so TinyCC processes shared SDK declarations once. Debug builds, custom preprocessing flags, and other native source sets retain separate compilation. FastC does not read their C headers. diff --git a/vlib/v/compiler_tests/fastc_backend_test.v b/vlib/v/compiler_tests/fastc_backend_test.v index 405454d955..7e01c7f0de 100644 --- a/vlib/v/compiler_tests/fastc_backend_test.v +++ b/vlib/v/compiler_tests/fastc_backend_test.v @@ -50,6 +50,24 @@ fn run_with_v_environment(program string, args []string, flags string, vexe stri return cmdexec.run(program, args) } +fn test_full_compiler_fastc_pgo_training_generates_c() { + $if !macos && !linux { + return + } + root := os.join_path(os.vtmp_dir(), 'v_fastc_pgo_training_${os.getpid()}') + os.mkdir_all(root) or { panic(err) } + defer { + os.rmdir_all(root) or {} + } + output := os.join_path(root, 'cmd_v_fastc_training.c') + // Match the FastC profiling workload in vself without building an instrumented compiler. + training := run_with_v_environment(@VEXE, ['-nocache', '-gc', 'none', '-prealloc', + '-compile-backend', 'fastc', '-building-v', '-b', 'fastc', '-no-memory-limit', '-o', output, + os.join_path(fastc_backend_vroot, 'cmd', 'v')], '', @VEXE) + assert training.exit_code == 0, training.output + assert os.file_size(output) > 0 +} + fn test_direct_fastc_compiler_entry_selects_selfhost_driver() { $if !macos && !linux { return diff --git a/vlib/v/gen/fastc/expr.v b/vlib/v/gen/fastc/expr.v index 15baa2f325..1c31225b97 100644 --- a/vlib/v/gen/fastc/expr.v +++ b/vlib/v/gen/fastc/expr.v @@ -3668,7 +3668,9 @@ fn (g &Parser) streaming_call_argument_type(tokens []FastcExpressionToken) u32 { } arg_index := fastc_call_argument_index(tokens, open) name := tokens[open - 1].lit - if open >= 2 && tokens[open - 2].tok == .dot { + if open >= 2 && tokens[open - 2].tok == .dot + && !(open >= 3 && tokens[open - 3].tok == .name && tokens[open - 3].lit in g.imports + && tokens[open - 3].lit !in g.locals && (open < 4 || tokens[open - 4].tok != .dot)) { // `recv.method(…)`: resolve on the receiver's type; parameter_types[0] is the receiver. receiver_start := fastc_method_receiver_start(tokens, open - 2) receiver_tokens := tokens[receiver_start..open - 2] @@ -3682,7 +3684,7 @@ fn (g &Parser) streaming_call_argument_type(tokens []FastcExpressionToken) u32 { u32(0) } } - function_key := g.unqualified_function_key(name) + function_key := g.function_key_for_call(tokens, open - 1) signature := g.functions[function_key] or { return u32(0) } return if arg_index < signature.parameter_types.ids.len { signature.parameter_types.ids[arg_index] diff --git a/vlib/v/gen/fastc/flag_method_receiver_test.v b/vlib/v/gen/fastc/flag_method_receiver_test.v new file mode 100644 index 0000000000..8bb2982bec --- /dev/null +++ b/vlib/v/gen/fastc/flag_method_receiver_test.v @@ -0,0 +1,89 @@ +module fastc + +import os +import v.cmdexec + +fn test_fastc_selfhost_enum_context_and_imported_flag_receiver() { + $if !macos && !linux { + return + } + root := os.join_path(os.vtmp_dir(), 'fastc_flag_receiver_${os.getpid()}') + os.mkdir_all(os.join_path(root, 'features')) or { panic(err) } + defer { os.rmdir_all(root) or {} } + os.write_file(os.join_path(root, 'v.mod'), 'Module { name: "flag_receiver" }') or { panic(err) } + os.write_file(os.join_path(root, 'features', 'features.v'), r'module features + +@[flag] +pub enum Flags { + read + write +} + +pub struct Counter { +pub mut: + value int +} + +pub fn select(mut calls Counter) Flags { + calls.value++ + return .read +} + +pub fn accepts(flag Flags) bool { + return flag == .read +} + +pub fn has_read(flag Flags) ?bool { + return flag.has(.read) +} + +pub fn assign(mut flag Flags) { + mut count := 0 + count, flag = 1, .read + assert count == 1 +} +') or { panic(err) } + os.write_file(os.join_path(root, 'main.v'), r'module main + +import features + +fn main() { + mut calls := features.Counter{} + required := features.Flags.read + excluded := features.Flags.write + assert features.select(mut calls).has(required) + assert features.select(mut calls).has(.read) + assert !features.select(mut calls).has(excluded) + assert calls.value == 3 + mut flag := features.Flags.write + assert flag.has(excluded) + mut count := 0 + count, flag = 7, .read + assert count == 7 + assert flag == .read + calls.value, flag = 8, .write + assert calls.value == 8 + assert flag == .write + flag, calls.value = .read, 9 + assert calls.value == 9 + assert flag == .read + flag = .write + features.assign(mut flag) + assert flag == .read + assert features.accepts(if count == 7 { .read } else { .write }) + assert !features.accepts(if count == 8 { .read } else { .write }) + assert features.has_read(required) or { false } + assert !(features.has_read(excluded) or { true }) + println("flag receivers work") +} +') or { panic(err) } + for backend in ['c', 'fastc'] { + executable := os.join_path(root, 'program_${backend}') + build := cmdexec.run(@VEXE, ['-b', backend, '-building-v', '-nocache', '-gc', 'none', '-o', + executable, root]) + assert build.exit_code == 0, '${backend}: ${build.output}' + run := cmdexec.run(executable, []) + assert run.exit_code == 0, '${backend}: ${run.output}' + assert run.output.trim_space() == 'flag receivers work', '${backend}: ${run.output}' + } +} diff --git a/vlib/v/gen/fastc/render_calls.v b/vlib/v/gen/fastc/render_calls.v index 09d1312cdc..3942614a12 100644 --- a/vlib/v/gen/fastc/render_calls.v +++ b/vlib/v/gen/fastc/render_calls.v @@ -562,7 +562,7 @@ fn (g &Parser) render_c_struct_sizeof(tokens []FastcExpressionToken) ?FastcRende } fn (g &Parser) render_flag_method_expression(tokens []FastcExpressionToken, rendered_expression string) ?FastcRenderedExpression { - if tokens.len < 7 { + if tokens.len < 6 { return none } mut rendered := rendered_expression @@ -613,7 +613,10 @@ fn (g &Parser) render_flag_method_expression(tokens []FastcExpressionToken, rend } method := tokens[i].lit raw_receiver_source := fastc_take_string(mut receiver) - receiver_source := g.render_member_receiver(receiver_tokens) or { raw_receiver_source } + receiver_source := g.render_member_receiver(receiver_tokens) or { + // Imported calls must resolve their C name and render their arguments too. + g.render_call_argument_expression(receiver_tokens, receiver_type) or { raw_receiver_source } + } raw_argument_source := fastc_take_string(mut raw_argument) c_argument_source := fastc_take_string(mut c_argument) mut needle := '${raw_receiver_source}.${method}(${c_argument_source})' diff --git a/vlib/v/gen/fastc/render_method_calls.v b/vlib/v/gen/fastc/render_method_calls.v index 809e72a59f..852e2bbb47 100644 --- a/vlib/v/gen/fastc/render_method_calls.v +++ b/vlib/v/gen/fastc/render_method_calls.v @@ -266,6 +266,10 @@ fn (g &Parser) render_method_call_expression(tokens []FastcExpressionToken, rend if !flags_processed && tokens[i].lit in ['has', 'set', 'clear'] { flags_processed = true if flags := g.render_flag_method_expression(tokens, rendered) { + call_end := fastc_matching_rpar(tokens, i + 1) or { -1 } + if receiver_start == 0 && call_end == tokens.len - 1 { + return flags + } rendered = flags.source changed = true } diff --git a/vlib/v/gen/fastc/stmt.v b/vlib/v/gen/fastc/stmt.v index 2b2944c5f4..3834f7c103 100644 --- a/vlib/v/gen/fastc/stmt.v +++ b/vlib/v/gen/fastc/stmt.v @@ -964,11 +964,20 @@ fn (mut g Parser) parse_match_statement() !bool { mut had_member_smartcast := false mut member_smartcast_active := false common_numeric_type := fastc_match_common_numeric_variant(variant_types) + mut array_variant_count := 0 + if variant_types.len > 1 { + // Keep this scan within the FastC self-hosting subset used by PGO training. + for variant_type in variant_types { + if flat.source_type_query(variant_type).has(.c_array_prefix) { + array_variant_count++ + } + } + } // Several struct variants in one arm (`FnTypeDecl, AliasTypeDecl { node.type_pos }`) // may only read fields common to all of them, which V lays out compatibly, so a // plain-local subject narrows through the first variant — mirroring the member-chain // smart-cast below. - struct_multi_variant := variant_types.len > 1 && common_numeric_type == 0 && !variant_types.any(flat.source_type_query(it).has(.c_array_prefix)) + struct_multi_variant := variant_types.len > 1 && common_numeric_type == 0 && array_variant_count == 0 smartcast_active := is_boxed && !is_else && (variant_types.len == 1 || common_numeric_type != 0) && subject_local != '' if smartcast_active { variant_cname := if common_numeric_type != 0 { @@ -1027,7 +1036,7 @@ fn (mut g Parser) parse_match_statement() !bool { // layout for array variants) is correct. member_smartcast_type := flat.source_type_id(if variant_types.len == 1 { flat.source_type_name(variant_types[0]) - } else if variant_types.len > 1 && variant_types.all(flat.source_type_query(it).has(.c_array_prefix)) { + } else if variant_types.len > 1 && array_variant_count == variant_types.len { 'array' } else if variant_types.len > 1 && common_numeric_type == 0 { flat.source_type_name(variant_types[0]) @@ -2162,10 +2171,22 @@ fn (mut g Parser) parse_parallel_assignment(initial_names []string, initial_mut if g.selfhost && names.len > 1 && g.parallel_rhs_is_option_tuple_or() { return g.parse_parallel_option_tuple(names, mutability, is_declaration) } + assignment_targets := if is_declaration { + []FastcRenderedExpression{} + } else { + g.validate_parallel_assignment_targets(names)! + } g.last_multi_return_types = flat.TypeTextList{ ids: ([]u32{}).clone() } mut values := []string{} mut value_types := []u32{} + previous_expected_type := g.expected_expression_type for { + // Each assignment value takes its context from the corresponding target. + g.expected_expression_type = if values.len < assignment_targets.len { + assignment_targets[values.len].typ + } else { + u32(0) + } item := g.read_expression([token.Token.comma, token.Token.semicolon, token.Token.rcbr])! if item == '' { return g.unsupported('empty parallel assignment') @@ -2177,6 +2198,7 @@ fn (mut g Parser) parse_parallel_assignment(initial_names []string, initial_mut } g.next() } + g.expected_expression_type = previous_expected_type value := values[0] if value == '' { return g.unsupported('empty parallel assignment') @@ -2186,11 +2208,6 @@ fn (mut g Parser) parse_parallel_assignment(initial_names []string, initial_mut if values.len != names.len { return g.unsupported('parallel assignment with ${names.len} targets and ${values.len} values') } - assignment_targets := if is_declaration { - []FastcRenderedExpression{} - } else { - g.validate_parallel_assignment_targets(names)! - } mut temporaries := []string{cap: values.len} for i, item in values { temporary := g.temporary_name('parallel') @@ -2228,11 +2245,6 @@ fn (mut g Parser) parse_parallel_assignment(initial_names []string, initial_mut if component_types.len == 0 { component_types = g.last_multi_return_types.ids.clone() } - assignment_targets := if is_declaration { - []FastcRenderedExpression{} - } else { - g.validate_parallel_assignment_targets(names)! - } temporary := g.temporary_name('multi_return') g.write_line('MultiReturn ${temporary} = (${value});') for i, name in names { @@ -2286,7 +2298,13 @@ fn (mut g Parser) finish_parallel_member_assignment(names []string, member_targe g.last_multi_return_types = flat.TypeTextList{ ids: ([]u32{}).clone() } mut values := []string{} mut value_types := []u32{} + previous_expected_type := g.expected_expression_type for { + g.expected_expression_type = if values.len < targets.len { + targets[values.len].typ + } else { + u32(0) + } value := g.read_expression([token.Token.comma, token.Token.semicolon, token.Token.rcbr])! if value == '' { return g.unsupported('empty parallel assignment') @@ -2298,6 +2316,7 @@ fn (mut g Parser) finish_parallel_member_assignment(names []string, member_targe } g.next() } + g.expected_expression_type = previous_expected_type g.consume_statement_end() if values.len > 1 { if values.len != targets.len { @@ -2574,7 +2593,13 @@ fn (mut g Parser) parse_parallel_expression_assignment(first_source string, firs g.last_multi_return_types = flat.TypeTextList{ ids: ([]u32{}).clone() } mut values := []string{} mut value_types := []u32{} + previous_expected_type := g.expected_expression_type for { + g.expected_expression_type = if values.len < targets.len { + targets[values.len].typ + } else { + u32(0) + } value := g.read_expression([token.Token.comma, token.Token.semicolon, token.Token.rcbr])! if value == '' { return g.unsupported('empty parallel assignment') @@ -2586,6 +2611,7 @@ fn (mut g Parser) parse_parallel_expression_assignment(first_source string, firs } g.next() } + g.expected_expression_type = previous_expected_type g.consume_statement_end() if values.len > 1 { if values.len != targets.len {