fastc: fix production PGO self-build training (#31269)
This commit is contained in:
1 parent
3ebaeb81d6
commit
eda8ea0130
7 files changed
+163
-16
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}'
|
||||
}
|
||||
}
|
||||
@@ -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})'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+38
-12
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user