Skip to content

Commit 258ca9d

Browse files
committed
v3: preserve fastc language semantics
1 parent 06ab85d commit 258ca9d

5 files changed

Lines changed: 138 additions & 6 deletions

File tree

vlib/v3/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ parser or type-checker diagnostics rather than a speculative C diagnostic. The c
108108
the same language and ownership/autofree coverage as the C backend. A successfully compiled `run`
109109
program keeps its exit status and is never retried.
110110

111+
Integer-range bounds in the direct lane are evaluated once, from left to right. Float printing is
112+
promoted to the complete lane so it uses V's `strconv` formatting rather than C `printf` rules.
113+
111114
The direct path is limited to host-target, non-production, non-test, non-shared single-file builds.
112115
Compiler/self-host and other non-direct modes enter the complete lane before source scanning.
113116
`-o file.c` emits the standalone fast C translation unit when the direct lane supports the input;

vlib/v3/gen/fastc/fastc.v

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,18 @@ static void v_fastc_print_bool(bool value) { fputs(value ? "true" : "false", std
3737
static void v_fastc_print_char(char value) { fputc(value, stdout); }
3838
static void v_fastc_print_signed(long long value) { printf("%lld", value); }
3939
static void v_fastc_print_unsigned(unsigned long long value) { printf("%llu", value); }
40-
static void v_fastc_print_float(double value) { printf("%g", value); }
4140
static void v_fastc_println_string(const char *value) { puts(value); }
4241
static void v_fastc_println_bool(bool value) { puts(value ? "true" : "false"); }
4342
static void v_fastc_println_char(char value) { fputc(value, stdout); fputc(10, stdout); }
4443
static void v_fastc_println_signed(long long value) { printf("%lld\n", value); }
4544
static void v_fastc_println_unsigned(unsigned long long value) { printf("%llu\n", value); }
46-
static void v_fastc_println_float(double value) { printf("%g\n", value); }
4745
48-
#define V_FASTC_PRINT_SELECT(value, string_fn, bool_fn, char_fn, signed_fn, unsigned_fn, float_fn) _Generic((value), char *: string_fn, const char *: string_fn, bool: bool_fn, char: char_fn, signed char: signed_fn, short: signed_fn, int: signed_fn, long: signed_fn, long long: signed_fn, unsigned char: unsigned_fn, unsigned short: unsigned_fn, unsigned int: unsigned_fn, unsigned long: unsigned_fn, unsigned long long: unsigned_fn, float: float_fn, double: float_fn)(value)
49-
#define print(value) V_FASTC_PRINT_SELECT(value, v_fastc_print_string, v_fastc_print_bool, v_fastc_print_char, v_fastc_print_signed, v_fastc_print_unsigned, v_fastc_print_float)
50-
#define println(value) V_FASTC_PRINT_SELECT(value, v_fastc_println_string, v_fastc_println_bool, v_fastc_println_char, v_fastc_println_signed, v_fastc_println_unsigned, v_fastc_println_float)
46+
/* Float formatting belongs to the V strconv routines. Leaving float and double
47+
* unmatched makes TinyCC reject this speculative candidate so the driver uses
48+
* the checked FastC lane instead of silently applying printf %g semantics. */
49+
#define V_FASTC_PRINT_SELECT(value, string_fn, bool_fn, char_fn, signed_fn, unsigned_fn) _Generic((value), char *: string_fn, const char *: string_fn, bool: bool_fn, char: char_fn, signed char: signed_fn, short: signed_fn, int: signed_fn, long: signed_fn, long long: signed_fn, unsigned char: unsigned_fn, unsigned short: unsigned_fn, unsigned int: unsigned_fn, unsigned long: unsigned_fn, unsigned long long: unsigned_fn)(value)
50+
#define print(value) V_FASTC_PRINT_SELECT(value, v_fastc_print_string, v_fastc_print_bool, v_fastc_print_char, v_fastc_print_signed, v_fastc_print_unsigned)
51+
#define println(value) V_FASTC_PRINT_SELECT(value, v_fastc_println_string, v_fastc_println_bool, v_fastc_println_char, v_fastc_println_signed, v_fastc_println_unsigned)
5152
#define assert(value) do { if (!(value)) { fprintf(stderr, "assertion failed: %s\n", #value); abort(); } } while (0)
5253
5354
'
@@ -62,6 +63,7 @@ mut:
6263
protos strings.Builder
6364
indent int
6465
in_main bool
66+
temp_id int
6567
}
6668

6769
// generate scans V source and emits C as each declaration and statement is consumed. It does
@@ -114,6 +116,12 @@ fn (mut g DirectGen) next() {
114116
g.lit = g.s.lit
115117
}
116118

119+
fn (mut g DirectGen) temporary_name(kind string) string {
120+
name := '__v_fastc_${kind}_${g.temp_id}'
121+
g.temp_id++
122+
return name
123+
}
124+
117125
fn (mut g DirectGen) skip_semicolons() {
118126
for g.tok == .semicolon {
119127
g.next()
@@ -355,7 +363,12 @@ fn (mut g DirectGen) parse_for() ! {
355363
g.expect(.dotdot)!
356364
end := g.read_expression([token.Token.lcbr])!
357365
g.expect(.lcbr)!
358-
g.write_line('for (__typeof__((${start})) ${name} = (${start}); ${name} < (${end}); ${name}++) {')
366+
start_name := g.temporary_name('range_start')
367+
end_name := g.temporary_name('range_end')
368+
// V evaluates both range bounds exactly once, from left to right.
369+
g.write_line('__typeof__((${start})) ${start_name} = (${start});')
370+
g.write_line('__typeof__((${end})) ${end_name} = (${end});')
371+
g.write_line('for (__typeof__((${start_name})) ${name} = (${start_name}); ${name} < (${end_name}); ${name}++) {')
359372
g.indent++
360373
g.parse_block_body()!
361374
g.indent--

vlib/v3/gen/fastc/fastc_test.v

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,28 @@ fn main() {
7878
assert c_source.contains('void stop(void) {\n\treturn;\n}')
7979
assert c_source.contains('if (true) {\n\t\treturn 0;\n\t}')
8080
}
81+
82+
fn test_integer_range_caches_bounds() {
83+
prefs := pref.new_preferences()
84+
c_source := generate('module main
85+
86+
fn start() int {
87+
return 0
88+
}
89+
90+
fn limit() int {
91+
return 3
92+
}
93+
94+
fn main() {
95+
for i in start() .. limit() {
96+
println(i)
97+
}
98+
}
99+
',
100+
'range_bounds.v', prefs) or { panic(err) }
101+
assert c_source.contains('__v_fastc_range_start_0 = (start());')
102+
assert c_source.contains('__v_fastc_range_end_1 = (limit());')
103+
assert c_source.contains('i < (__v_fastc_range_end_1)')
104+
assert !c_source.contains('i < (limit())')
105+
}

vlib/v3/gen/fastc/fn.v

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2983,6 +2983,11 @@ fn (mut g FlatGen) gen_mut_sum_lvalue_arg(arg_id flat.NodeId, expected types.Typ
29832983
mut lvalue_id := arg_id
29842984
if int(arg_id) >= 0 && int(arg_id) < g.a.nodes.len {
29852985
arg_node := g.a.nodes[int(arg_id)]
2986+
// Interface values use the sum-value ABI, and some call paths reach this
2987+
// helper before the general argument handling below.
2988+
if g.gen_lowered_mut_value_storage_arg(arg_id, arg_node, expected) {
2989+
return true
2990+
}
29862991
if arg_node.kind == .prefix && arg_node.op == .amp && arg_node.children_count > 0 {
29872992
lvalue_id = g.a.child(&arg_node, 0)
29882993
}
@@ -7068,6 +7073,11 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
70687073
g.expected_enum = ''
70697074
continue
70707075
}
7076+
if !is_c_call && arg_idx < typed_param_count
7077+
&& g.gen_lowered_mut_value_storage_arg(arg_id, arg_node, param_types[arg_idx]) {
7078+
g.expected_enum = ''
7079+
continue
7080+
}
70717081
if !is_c_call && arg_idx < typed_param_count
70727082
&& g.gen_mut_pointer_slot_arg(arg_id, arg_node, param_types[arg_idx]) {
70737083
g.expected_enum = ''
@@ -12918,6 +12928,10 @@ fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
1291812928
if arg_idx < typed_param_count && g.gen_mut_sum_lvalue_arg(arg_id, param_types[arg_idx]) {
1291912929
continue
1292012930
}
12931+
if arg_idx < typed_param_count
12932+
&& g.gen_lowered_mut_value_storage_arg(arg_id, arg_node, param_types[arg_idx]) {
12933+
continue
12934+
}
1292112935
if arg_idx < typed_param_count
1292212936
&& g.gen_mut_pointer_slot_arg(arg_id, arg_node, param_types[arg_idx]) {
1292312937
continue
@@ -14031,6 +14045,25 @@ fn (mut g FlatGen) gen_mut_pointer_slot_arg(arg_id flat.NodeId, arg_node flat.No
1403114045
return true
1403214046
}
1403314047

14048+
fn (mut g FlatGen) gen_lowered_mut_value_storage_arg(arg_id flat.NodeId, arg_node flat.Node, expected types.Type) bool {
14049+
if arg_node.kind != .prefix || arg_node.op != .mul || arg_node.children_count != 1 {
14050+
return false
14051+
}
14052+
child_id := g.a.child(&arg_node, 0)
14053+
child := g.a.nodes[int(child_id)]
14054+
// Mutable array iteration stores the value as `T*` and lowers an ordinary
14055+
// expression to `*value`. In a source `mut value` argument the callee expects
14056+
// that storage pointer itself, including when T is an interface value.
14057+
if child.kind != .ident || !child.is_mut || !g.local_storage_is_pointer(child.value) {
14058+
return false
14059+
}
14060+
if g.tc.c_type(g.usable_expr_type(arg_id)) != g.tc.c_type(expected) {
14061+
return false
14062+
}
14063+
g.gen_expr(child_id)
14064+
return true
14065+
}
14066+
1403414067
fn (g &FlatGen) c_typedef_nil_call(id flat.NodeId) bool {
1403514068
if int(id) < 0 || int(id) >= g.a.nodes.len {
1403614069
return false

vlib/v3/tests/fastc_backend_test.v

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,64 @@ fn main() {
111111
early_return_run := cmdexec.run(early_return_binary, [])
112112
assert early_return_run.exit_code == 0, early_return_run.output
113113

114+
range_source := os.join_path(root, 'range.v')
115+
os.write_file(range_source, 'module main
116+
117+
fn start() int {
118+
println("start")
119+
return 0
120+
}
121+
122+
fn limit() int {
123+
println("limit")
124+
return 3
125+
}
126+
127+
fn main() {
128+
for i in start() .. limit() {
129+
println(i)
130+
}
131+
}
132+
') or {
133+
panic(err)
134+
}
135+
range_binary := os.join_path(root, 'range')
136+
range_compile := cmdexec.run(v3_bin,
137+
['-silent', '-b', 'fastc', '-o', range_binary, range_source])
138+
assert range_compile.exit_code == 0, range_compile.output
139+
range_c := os.read_file(range_binary + '.c') or { panic(err) }
140+
assert range_c.contains('__v_fastc_range_start_0 = (start());')
141+
assert range_c.contains('__v_fastc_range_end_1 = (limit());')
142+
range_run := cmdexec.run(range_binary, [])
143+
assert range_run.exit_code == 0, range_run.output
144+
assert range_run.output.trim_space() == 'start\nlimit\n0\n1\n2'
145+
146+
float_source := os.join_path(root, 'float.v')
147+
os.write_file(float_source, 'module main
148+
149+
fn main() {
150+
println(2.0)
151+
println(12.3456789)
152+
}
153+
') or {
154+
panic(err)
155+
}
156+
float_binary := os.join_path(root, 'float')
157+
float_compile := cmdexec.run(v3_bin,
158+
['-silent', '-b', 'fastc', '-o', float_binary, float_source])
159+
assert float_compile.exit_code == 0, float_compile.output
160+
float_run := cmdexec.run(float_binary, [])
161+
assert float_run.exit_code == 0, float_run.output
162+
assert float_run.output.trim_space() == '2.0\n12.3456789'
163+
164+
mutable_interface_source := os.join_path(os.dir(@FILE), 'mutable_interface_array_value_test.v')
165+
mutable_interface_binary := os.join_path(root, 'mutable_interface_array_value_test')
166+
mutable_interface_compile := cmdexec.run(v3_bin, ['-silent', '-b', 'fastc', '-o',
167+
mutable_interface_binary, mutable_interface_source])
168+
assert mutable_interface_compile.exit_code == 0, mutable_interface_compile.output
169+
mutable_interface_run := cmdexec.run(mutable_interface_binary, [])
170+
assert mutable_interface_run.exit_code == 0, mutable_interface_run.output
171+
114172
old_vjobs := os.getenv('VJOBS')
115173
os.setenv('VJOBS', '4', true)
116174
mut selfhosted_v3 := v3_bin

0 commit comments

Comments
 (0)