Skip to content

Commit 6a63a27

Browse files
committed
v2: make math_test.v pass
1 parent 6fa4008 commit 6a63a27

15 files changed

Lines changed: 377 additions & 72 deletions

File tree

cmd/v2/test_all.sh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,30 @@ set -euo pipefail
33

44
cd "$(dirname "$0")"
55

6-
echo "=== 1/6: Self-host test ==="
6+
echo "=== 1/7: Self-host test ==="
77
bash test_v2_self.sh
88

99
echo ""
10-
echo "=== 2/6: Rebuild v2 and run builtin test files ==="
10+
echo "=== 2/7: Rebuild v2 and run builtin test files ==="
1111
v self && v -o v2 v2.v
1212
./v2 -o array_test ../../vlib/builtin/array_test.v && ./array_test && rm -f array_test array_test.c
1313
./v2 -o string_test ../../vlib/builtin/string_test.v && ./string_test && rm -f string_test string_test.c
1414
./v2 -o map_test ../../vlib/builtin/map_test.v && ./map_test && rm -f map_test map_test.c
1515

1616
echo ""
17-
echo "=== 3/6: SSA backends test (arm64) ==="
17+
echo "=== 3/7: Math test ==="
18+
./v2 -o math_test ../../vlib/math/math_test.v && ./math_test && rm -f math_test math_test.c
19+
20+
echo ""
21+
echo "=== 4/7: SSA backends test (arm64) ==="
1822
v -gc none run test_ssa_backends.v arm64
1923

2024
echo ""
21-
echo "=== 4/6: SSA backends test (cleanc) ==="
25+
echo "=== 5/7: SSA backends test (cleanc) ==="
2226
v -gc none run test_ssa_backends.v cleanc
2327

2428
echo ""
25-
echo "=== 5/6: Transformer v2 nix test ==="
29+
echo "=== 6/7: Transformer v2 nix test ==="
2630
v ../../vlib/v2/transformer/transformer_v2_nix_test.v
2731

2832
echo ""

vlib/builtin/string.v

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,8 +2167,8 @@ pub fn (str string) is_hex() bool {
21672167
for i < str.len {
21682168
// TODO: remove this workaround for v2's parser
21692169
// vfmt off
2170-
if (str[i] < `0` || str[i] > `9`) &&
2171-
((str[i] < `a` || str[i] > `f`) && (str[i] < `A` || str[i] > `F`)) {
2170+
if (str[i] < `0` || str[i] > `9`)
2171+
&& ((str[i] < `a` || str[i] > `f`) && (str[i] < `A` || str[i] > `F`)) {
21722172
return false
21732173
}
21742174
// vfmt on
@@ -2910,8 +2910,8 @@ pub fn (s string) camel_to_snake() string {
29102910
// TODO: remove this workaround for v2's parser
29112911
// vfmt off
29122912
if ((c_is_upper && !prev_is_upper) ||
2913-
(!c_is_upper && prev_is_upper && s[i - 2].is_capital() && !prev_inserted_boundary && !skip_digit)) &&
2914-
c != `_` {
2913+
(!c_is_upper && prev_is_upper && s[i - 2].is_capital() && !prev_inserted_boundary && !skip_digit))
2914+
&& c != `_` {
29152915
unsafe {
29162916
if b[pos - 1] != `_` {
29172917
b[pos] = `_`

vlib/v2/builder/parse.v

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// that can be found in the LICENSE file.
44
module builder
55

6+
import os
67
import v2.ast
78
import v2.parser
89

@@ -31,8 +32,21 @@ fn (mut b Builder) parse_files(files []string) []ast.File {
3132
}
3233
}
3334
}
35+
// For test files, include all non-test sibling module files from the same directory
36+
mut all_user_files := files.clone()
37+
for file in files {
38+
if file.contains('_test.') && file.ends_with('.v') {
39+
dir := os.dir(file)
40+
sibling_files := get_v_files_from_dir(dir, b.pref.user_defines)
41+
for sf in sibling_files {
42+
if sf !in all_user_files {
43+
all_user_files << sf
44+
}
45+
}
46+
}
47+
}
3448
// parse user files
35-
parsed_user_files := parser_reused.parse_files(files, mut b.file_set)
49+
parsed_user_files := parser_reused.parse_files(all_user_files, mut b.file_set)
3650
ast_files << parsed_user_files
3751
skip_imports := b.pref.skip_imports
3852
if skip_imports {

vlib/v2/gen/cleanc/array.v

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,12 @@ fn (mut g Gen) try_emit_const_dynamic_array_call(name string, value ast.Expr) bo
528528
if elem_type == '' || elem_type == 'array' {
529529
return false
530530
}
531+
// Check that no element contains a function call (not valid in C static initializers)
532+
for elem in array_data.exprs {
533+
if g.contains_call_expr(elem) {
534+
return false
535+
}
536+
}
531537
mut len_expr := expr_to_int_str(call.args[0])
532538
mut cap_expr := expr_to_int_str(call.args[1])
533539
if len_expr == '0' && array_data.exprs.len > 0 {

vlib/v2/gen/cleanc/assign.v

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@ fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) {
1111
lhs := node.lhs[0]
1212
rhs := node.rhs[0]
1313

14+
// Multi-assignment with parallel RHS values (non-declaration):
15+
// `p, q = q, p` needs temp variables for correct swap semantics.
16+
if node.op != .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len {
17+
// First, evaluate all RHS values into temporaries
18+
for i, rhs_expr in node.rhs {
19+
mut typ := g.get_expr_type(rhs_expr)
20+
if typ == '' || typ == 'int_literal' {
21+
typ = 'int'
22+
}
23+
if typ == 'float_literal' {
24+
typ = 'f64'
25+
}
26+
g.write_indent()
27+
g.sb.write_string('${typ} _swap_${g.tmp_counter}_${i} = ')
28+
g.expr(rhs_expr)
29+
g.sb.writeln(';')
30+
}
31+
// Then assign from temporaries to LHS
32+
for i, lhs_expr in node.lhs {
33+
g.write_indent()
34+
g.expr(lhs_expr)
35+
g.sb.writeln(' = _swap_${g.tmp_counter}_${i};')
36+
}
37+
g.tmp_counter++
38+
return
39+
}
40+
1441
// Multi-declaration with parallel RHS values:
1542
// `a, b := x, y` should declare both variables (not just the first one).
1643
if node.op == .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len {

vlib/v2/gen/cleanc/cheaders.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const preamble_includes_full = r'// Generated by V Clean C Backend
2929
#include <stddef.h>
3030
#include <string.h>
3131
#include <float.h>
32+
#include <math.h>
3233
#include <unistd.h>
3334
#include <fcntl.h>
3435
#include <sys/stat.h>

vlib/v2/gen/cleanc/cleanc.v

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,12 @@ pub fn (mut g Gen) gen() string {
451451
if stmt.language == .c && stmt.stmts.len == 0 {
452452
continue
453453
}
454-
// Skip generic functions - they have unresolved type params
454+
// Generic functions: emit as macros for known simple functions
455455
if stmt.typ.generic_params.len > 0 {
456+
gfn_name := g.get_fn_name(stmt)
457+
if gfn_name != '' {
458+
g.emit_generic_fn_macro(gfn_name, stmt)
459+
}
456460
continue
457461
}
458462
fn_name := g.get_fn_name(stmt)
@@ -542,8 +546,11 @@ pub fn (mut g Gen) gen() string {
542546
}
543547
}
544548
}
545-
if '__v_init_consts_main' in g.fn_return_types {
546-
g.sb.writeln('\t__v_init_consts_main();')
549+
// Call all module const initialization functions
550+
for fn_name, _ in g.fn_return_types {
551+
if fn_name.contains('__v_init_consts_') {
552+
g.sb.writeln('\t${fn_name}();')
553+
}
547554
}
548555
for test_fn in test_fn_names {
549556
msg_run := 'Running test: ${test_fn}...'

vlib/v2/gen/cleanc/consts_and_globals.v

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,19 @@ fn (mut g Gen) gen_const_decl(node ast.ConstDecl) {
264264
elem_type = g.get_expr_type(array_value.exprs[0])
265265
}
266266
if elem_type != '' && elem_type != 'array' {
267-
is_fixed_array_const = true
268-
fixed_array_elem = elem_type
269-
fixed_array_len = array_value.exprs.len
267+
// Check that no element contains a function call (not valid in C static initializers)
268+
mut has_call := false
269+
for elem in array_value.exprs {
270+
if g.contains_call_expr(elem) {
271+
has_call = true
272+
break
273+
}
274+
}
275+
if !has_call {
276+
is_fixed_array_const = true
277+
fixed_array_elem = elem_type
278+
fixed_array_len = array_value.exprs.len
279+
}
270280
}
271281
}
272282
if is_fixed_array_const && fixed_array_elem != '' {

vlib/v2/gen/cleanc/fn.v

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -227,34 +227,11 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl) {
227227
if node.language == .c && node.stmts.len == 0 {
228228
return
229229
}
230-
// Generic functions: emit a stub that returns default value
230+
// Generic functions: handled via emit_generic_fn_macro in the forward declaration pass
231231
if node.typ.generic_params.len > 0 {
232-
fn_name := g.get_fn_name(node)
233-
if fn_name != '' {
234-
// Emit a forward declaration and stub body
235-
g.sb.write_string('/* generic stub */ array ${fn_name}(')
236-
for i, param in node.typ.params {
237-
if i > 0 {
238-
g.sb.write_string(', ')
239-
}
240-
g.sb.write_string('array')
241-
if param.is_mut {
242-
g.sb.write_string('*')
243-
}
244-
// Avoid C name clash: parameter named 'array' hides struct array
245-
pname := if param.name == 'array' { '_v_array' } else { param.name }
246-
g.sb.write_string(' ${pname}')
247-
}
248-
if node.typ.params.len == 0 {
249-
g.sb.write_string('void')
250-
}
251-
g.sb.writeln(') {')
252-
g.sb.writeln('\treturn (array){0};')
253-
g.sb.writeln('}')
254-
g.sb.writeln('')
255-
}
256232
return
257233
}
234+
258235
fn_name := g.get_fn_name(node)
259236
if fn_name == '' {
260237
return
@@ -499,12 +476,22 @@ fn (g &Gen) contains_call_expr(e ast.Expr) bool {
499476
if e is ast.CallExpr {
500477
return true
501478
}
479+
if e is ast.CallOrCastExpr {
480+
return true
481+
}
502482
if e is ast.CastExpr {
503483
return g.contains_call_expr(e.expr)
504484
}
505485
if e is ast.ParenExpr {
506486
return g.contains_call_expr(e.expr)
507487
}
488+
if e is ast.ArrayInitExpr {
489+
for elem in e.exprs {
490+
if g.contains_call_expr(elem) {
491+
return true
492+
}
493+
}
494+
}
508495
return false
509496
}
510497

@@ -1809,3 +1796,92 @@ fn (g &Gen) get_str_fn_for_type(expr_type string) ?string {
18091796
}
18101797
return none
18111798
}
1799+
1800+
// emit_generic_fn_macro emits a C macro definition for known simple generic functions
1801+
// (abs, min, max, clamp). For other generic functions, emits a stub.
1802+
// Also emits a typedef for the generic type parameter T.
1803+
fn (mut g Gen) emit_generic_fn_macro(fn_name string, node ast.FnDecl) {
1804+
macro_key := 'generic_macro_${fn_name}'
1805+
if macro_key in g.emitted_types {
1806+
return
1807+
}
1808+
g.emitted_types[macro_key] = true
1809+
if node.typ.params.len == 1 && node.name == 'abs' {
1810+
pname := node.typ.params[0].name
1811+
g.sb.writeln('#define ${fn_name}(${pname}) ((${pname}) < 0 ? -(${pname}) : (${pname}))')
1812+
} else if node.typ.params.len == 2 && node.name == 'min' {
1813+
p0 := node.typ.params[0].name
1814+
p1 := node.typ.params[1].name
1815+
g.sb.writeln('#define ${fn_name}(${p0}, ${p1}) ((${p0}) < (${p1}) ? (${p0}) : (${p1}))')
1816+
} else if node.typ.params.len == 2 && node.name == 'max' {
1817+
p0 := node.typ.params[0].name
1818+
p1 := node.typ.params[1].name
1819+
g.sb.writeln('#define ${fn_name}(${p0}, ${p1}) ((${p0}) > (${p1}) ? (${p0}) : (${p1}))')
1820+
} else if node.typ.params.len == 3 && node.name == 'clamp' {
1821+
p0 := node.typ.params[0].name
1822+
p1 := node.typ.params[1].name
1823+
p2 := node.typ.params[2].name
1824+
g.sb.writeln('#define ${fn_name}(${p0}, ${p1}, ${p2}) ((${p0}) < (${p1}) ? (${p1}) : ((${p0}) > (${p2}) ? (${p2}) : (${p0})))')
1825+
} else if node.typ.params.len == 0 && node.name in ['maxof', 'minof'] {
1826+
// Emit specialized macros for maxof[T]()/minof[T]() - comptime functions
1827+
// that return the min/max value for each numeric type.
1828+
prefix := if fn_name.contains('__') { fn_name.all_before_last('__') + '__' } else { '' }
1829+
is_max := node.name == 'maxof'
1830+
// max_f32/max_f64 are in the math module and emitted as math__max_f32/math__max_f64
1831+
type_const_pairs := if is_max {
1832+
[['i8', 'max_i8'], ['i16', 'max_i16'], ['i32', 'max_i32'],
1833+
['i64', 'max_i64'], ['int', 'max_int'], ['u8', 'max_u8'],
1834+
['u16', 'max_u16'], ['u32', 'max_u32'], ['u64', 'max_u64'],
1835+
['f32', 'math__max_f32'], ['f64', 'math__max_f64']]
1836+
} else {
1837+
[['i8', 'min_i8'], ['i16', 'min_i16'], ['i32', 'min_i32'],
1838+
['i64', 'min_i64'], ['int', 'min_int'], ['u8', '((u8)(0))'],
1839+
['u16', '((u16)(0))'], ['u32', '((u32)(0))'],
1840+
['u64', '((u64)(0))'], ['f32', '(-math__max_f32)'],
1841+
['f64', '(-math__max_f64)']]
1842+
}
1843+
for pair in type_const_pairs {
1844+
g.sb.writeln('#define ${prefix}${node.name}_${pair[0]}() (${pair[1]})')
1845+
}
1846+
} else {
1847+
// Fallback: emit a stub with array types.
1848+
// Only emit stub bodies for modules that should be emitted in the current
1849+
// cache bundle. Otherwise the same stub appears in both builtin.o and vlib.o.
1850+
if !g.should_emit_module(g.cur_module) {
1851+
return
1852+
}
1853+
g.sb.write_string('/* generic stub */ array ${fn_name}(')
1854+
for i, param in node.typ.params {
1855+
if i > 0 {
1856+
g.sb.write_string(', ')
1857+
}
1858+
g.sb.write_string('array')
1859+
if param.is_mut {
1860+
g.sb.write_string('*')
1861+
}
1862+
pname := if param.name == 'array' { '_v_array' } else { param.name }
1863+
g.sb.write_string(' ${pname}')
1864+
}
1865+
if node.typ.params.len == 0 {
1866+
g.sb.write_string('void')
1867+
}
1868+
g.sb.writeln(') {')
1869+
g.sb.writeln('\treturn (array){0};')
1870+
g.sb.writeln('}')
1871+
}
1872+
// Emit typedef for generic type parameter T as f64 (default numeric type)
1873+
for gp in node.typ.generic_params {
1874+
if gp is ast.Ident {
1875+
qualified := if fn_name.contains('__') {
1876+
'${fn_name.all_before_last('__')}__${gp.name}'
1877+
} else {
1878+
gp.name
1879+
}
1880+
tdef_key := 'typedef_${qualified}'
1881+
if tdef_key !in g.emitted_types {
1882+
g.emitted_types[tdef_key] = true
1883+
g.sb.writeln('typedef f64 ${qualified};')
1884+
}
1885+
}
1886+
}
1887+
}

vlib/v2/gen/cleanc/for.v

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ fn (mut g Gen) gen_stmt_inline(node ast.Stmt) {
7474
.assign { '=' }
7575
.plus_assign { '+=' }
7676
.minus_assign { '-=' }
77+
.mul_assign { '*=' }
78+
.div_assign { '/=' }
79+
.mod_assign { '%=' }
80+
.and_assign { '&=' }
81+
.or_assign { '|=' }
82+
.xor_assign { '^=' }
83+
.left_shift_assign { '<<=' }
84+
.right_shift_assign { '>>=' }
7785
else { '=' }
7886
}
7987
g.sb.write_string(' ${op_str} ')

0 commit comments

Comments
 (0)