Skip to content

Commit a5403f9

Browse files
committed
v2: test-self
1 parent 58f1857 commit a5403f9

7 files changed

Lines changed: 277 additions & 30 deletions

File tree

‎cmd/v2/v2.v‎

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@
44
module main
55

66
import os
7+
import time
78
import v2.pref
89
import v2.builder
910

1011
fn main() {
1112
compile_args, runtime_args := split_eval_runtime_args(os.args[1..])
1213

13-
// Check for 'ast' subcommand
14+
// Check for subcommands
1415
if compile_args.len > 0 && compile_args[0] == 'ast' {
1516
run_ast_command(compile_args[1..])
1617
return
1718
}
19+
if compile_args.len > 0 && compile_args[0] == 'test-self' {
20+
run_test_self()
21+
return
22+
}
1823

1924
mut prefs := pref.new_preferences_from_args(compile_args)
2025

@@ -119,3 +124,128 @@ fn get_files(args []string) []string {
119124
}
120125
return files
121126
}
127+
128+
fn resolve_own_path() string {
129+
arg0 := os.args[0]
130+
if os.is_abs_path(arg0) {
131+
return arg0
132+
}
133+
return os.join_path(os.getwd(), arg0)
134+
}
135+
136+
// detect_vroot walks up from `start` looking for a directory containing vlib/builtin.
137+
fn detect_vroot(start string) string {
138+
mut dir := start
139+
if !os.is_abs_path(dir) {
140+
dir = os.join_path(os.getwd(), dir)
141+
}
142+
for _ in 0 .. 10 {
143+
if os.is_dir(os.join_path(dir, 'vlib', 'builtin')) {
144+
return dir
145+
}
146+
parent := os.dir(dir)
147+
if parent == dir {
148+
break
149+
}
150+
dir = parent
151+
}
152+
return dir
153+
}
154+
155+
fn run_test_self() {
156+
t0 := time.now()
157+
// Resolve the v2 binary's own path. Cannot use @VEXE because when v1
158+
// compiles v2, @VEXE bakes in v1's path instead of v2's.
159+
vexe := resolve_own_path()
160+
// Walk up from the binary to find the repo root (directory containing vlib/builtin).
161+
// This allows running from subdirectories like cmd/v2/.
162+
vroot := detect_vroot(vexe)
163+
v2_dir := os.join_path(vroot, 'cmd', 'v2')
164+
165+
mut all_test_files := []string{}
166+
167+
// Builtin test files
168+
for name in ['array_test.v', 'string_test.v', 'map_test.v'] {
169+
all_test_files << os.join_path(vroot, 'vlib', 'builtin', name)
170+
}
171+
172+
// Math test
173+
all_test_files << os.join_path(vroot, 'vlib', 'math', 'math_test.v')
174+
175+
// Sumtype tests in cmd/v2/
176+
v2_files := os.ls(v2_dir) or { []string{} }
177+
for file in v2_files {
178+
if file.starts_with('test_sumtype') && file.ends_with('.v') {
179+
all_test_files << os.join_path(v2_dir, file)
180+
}
181+
}
182+
183+
// Cleanc regression tests
184+
cleanc_tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
185+
cleanc_files := os.ls(cleanc_tests_dir) or { []string{} }
186+
for file in cleanc_files {
187+
if file.ends_with('.v') && file != 'run_tests.v' {
188+
all_test_files << os.join_path(cleanc_tests_dir, file)
189+
}
190+
}
191+
192+
total := all_test_files.len
193+
mut passed := 0
194+
mut failed := 0
195+
mut failed_files := []string{}
196+
197+
eprintln('---- v2 test-self: ${total} test files ----')
198+
199+
for i, test_file in all_test_files {
200+
short_name := test_file.replace(vroot + '/', '')
201+
t1 := time.now()
202+
203+
// Determine output binary path
204+
base := os.file_name(test_file).all_before_last('.v')
205+
out_bin := os.join_path(os.temp_dir(), 'v2_test_self_${base}')
206+
207+
// Compile (use os.system to avoid pipe deadlocks with popen/GC)
208+
compile_cmd := '${vexe} -o ${out_bin} "${test_file}" > /dev/null 2>&1'
209+
compile_ret := os.system(compile_cmd)
210+
compile_ms := f64(time.since(t1)) / f64(time.millisecond)
211+
212+
if compile_ret != 0 || !os.exists(out_bin) {
213+
failed++
214+
failed_files << short_name
215+
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms ${short_name}')
216+
os.rm(out_bin) or {}
217+
os.rm('${out_bin}.c') or {}
218+
continue
219+
}
220+
221+
// Run the compiled binary
222+
t2 := time.now()
223+
run_ret := os.system(out_bin + ' > /dev/null 2>&1')
224+
run_ms := f64(time.since(t2)) / f64(time.millisecond)
225+
226+
if run_ret != 0 {
227+
failed++
228+
failed_files << short_name
229+
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
230+
} else {
231+
passed++
232+
eprintln('OK [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
233+
}
234+
235+
os.rm(out_bin) or {}
236+
os.rm('${out_bin}.c') or {}
237+
}
238+
239+
elapsed := time.since(t0)
240+
eprintln('------------------------------------------------------------------------')
241+
eprintln('${passed} passed, ${failed} failed, ${total} total (${elapsed})')
242+
if failed_files.len > 0 {
243+
eprintln('Failed:')
244+
for f in failed_files {
245+
eprintln(' ${f}')
246+
}
247+
}
248+
if failed > 0 {
249+
exit(1)
250+
}
251+
}

‎vlib/v2/builder/cache_headers.v‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,6 @@ fn (mut b Builder) ensure_core_module_headers() {
430430
header_source = merge_missing_source_fn_decls(header_source, source_fn_decls)
431431
source_struct_fields := b.source_struct_field_types_for_module(module_name)
432432
header_source = repair_missing_struct_field_types(header_source, source_struct_fields)
433-
header_source = ensure_ierror_interface_methods(header_source)
434433
if header_source.len == 0 {
435434
// Empty header would cause missing symbols in split compilation.
436435
// Remove any partial headers already written and skip stamp update
@@ -1509,20 +1508,6 @@ fn merge_missing_source_fn_decls(header_source string, source_fn_decls map[strin
15091508
return merged
15101509
}
15111510

1512-
fn ensure_ierror_interface_methods(header_source string) string {
1513-
empty_pub := 'pub interface IError {\n}\n'
1514-
full_pub := 'pub interface IError {\n\tmsg fn() string\n\tcode fn() int\n}\n'
1515-
if header_source.contains(empty_pub) {
1516-
return header_source.replace(empty_pub, full_pub)
1517-
}
1518-
empty_plain := 'interface IError {\n}\n'
1519-
full_plain := 'interface IError {\n\tmsg fn() string\n\tcode fn() int\n}\n'
1520-
if header_source.contains(empty_plain) {
1521-
return header_source.replace(empty_plain, full_plain)
1522-
}
1523-
return header_source
1524-
}
1525-
15261511
fn header_struct_block_name(trimmed string) ?string {
15271512
if !trimmed.ends_with('{') {
15281513
return none

‎vlib/v2/gen/cleanc/cleanc.v‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ mut:
9797
test_fn_names []string // test function names collected in Pass 4
9898
has_main bool // whether a main() function was found in Pass 4
9999
fn_owner_file map[string]int // fn_key -> first file index (for parallel dedup)
100+
global_owner_file map[string]int // global_name -> first file index (for parallel dedup)
100101
generic_struct_bindings map[string]map[string]types.Type // struct_name -> {T: concrete_type}
101102
c_file_fn_keys map[string]bool // fn_key -> emitted from a .c.v file, so plain .v fallback should be skipped
102103
typedef_c_types map[string]bool // C struct names with @[typedef] attribute (emit without 'struct' prefix)
@@ -1144,13 +1145,30 @@ pub fn (mut g Gen) gen_pass5_pre() []int {
11441145
g.gen_file_extern_globals(file)
11451146
}
11461147
// Emit extern consts for non-emitted modules, collect emittable file indices.
1148+
// Also build global_owner_file: assign each global to the first file that declares it
1149+
// so parallel workers can avoid emitting duplicate definitions.
11471150
mut emit_indices := []int{cap: g.files.len}
11481151
for fi, file in g.files {
11491152
g.set_file_module(file)
11501153
if !g.should_emit_module(g.cur_module) {
11511154
g.gen_file_extern_consts(file)
11521155
} else {
11531156
emit_indices << fi
1157+
for stmt in file.stmts {
1158+
if stmt is ast.GlobalDecl {
1159+
for field in stmt.fields {
1160+
gname := if g.cur_module != '' && g.cur_module != 'main'
1161+
&& g.cur_module != 'builtin' {
1162+
'${g.cur_module}__${field.name}'
1163+
} else {
1164+
field.name
1165+
}
1166+
if gname !in g.global_owner_file {
1167+
g.global_owner_file[gname] = fi
1168+
}
1169+
}
1170+
}
1171+
}
11541172
}
11551173
}
11561174
return emit_indices
@@ -1185,14 +1203,20 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen {
11851203
for fi in file_indices {
11861204
owned_files[fi] = true
11871205
}
1188-
// Clone emitted_types and reserve functions owned by other workers.
1206+
// Clone emitted_types and reserve functions/globals owned by other workers.
11891207
mut worker_emitted := g.emitted_types.clone()
11901208
mut blocked_fn_keys := map[string]bool{}
11911209
for fn_key, owner_fi in g.fn_owner_file {
11921210
if owner_fi !in owned_files {
11931211
blocked_fn_keys[fn_key] = true
11941212
}
11951213
}
1214+
// Block globals owned by other workers to prevent duplicate definitions.
1215+
for gname, owner_fi in g.global_owner_file {
1216+
if owner_fi !in owned_files {
1217+
worker_emitted['global_${gname}'] = true
1218+
}
1219+
}
11961220
return &Gen{
11971221
files: g.files
11981222
env: unsafe { g.env }

‎vlib/v2/gen/cleanc/expr.v‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3154,6 +3154,22 @@ fn (g &Gen) eval_comptime_cond(cond ast.Expr) bool {
31543154
if cond.op == .logical_or {
31553155
return g.eval_comptime_cond(cond.lhs) || g.eval_comptime_cond(cond.rhs)
31563156
}
3157+
// Handle `$if T is i8` — comptime generic type check
3158+
if cond.op == .key_is || cond.op == .not_is {
3159+
if cond.lhs is ast.Ident && cond.rhs is ast.Ident {
3160+
if concrete := g.active_generic_types[cond.lhs.name] {
3161+
concrete_name := concrete.name()
3162+
target_name := cond.rhs.name
3163+
// Normalize type aliases: byte == u8, int == i32
3164+
matched := concrete_name == target_name
3165+
|| (target_name == 'byte' && concrete_name == 'u8')
3166+
|| (target_name == 'u8' && concrete_name == 'byte')
3167+
|| (target_name == 'int' && concrete_name == 'i32')
3168+
|| (target_name == 'i32' && concrete_name == 'int')
3169+
return if cond.op == .key_is { matched } else { !matched }
3170+
}
3171+
}
3172+
}
31573173
}
31583174
ast.PostfixExpr {
31593175
if cond.op == .question && cond.expr is ast.Ident {

‎vlib/v2/gen/cleanc/fn.v‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ fn (mut g Gen) specialized_fn_name(node ast.FnDecl, generic_types map[string]typ
639639
if suffixes.len == 0 {
640640
return base_name
641641
}
642-
return '${base_name}_${suffixes.join('_')}'
642+
return '${base_name}_T_${suffixes.join('_')}'
643643
}
644644

645645
fn (g &Gen) generic_key_matches_decl(node ast.FnDecl, key string) bool {
@@ -934,7 +934,7 @@ fn (g &Gen) find_specialized_call_name(name string, token string) ?string {
934934
if name == '' || token == '' {
935935
return none
936936
}
937-
candidate := '${name}_${token}'
937+
candidate := '${name}_T_${token}'
938938
if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types {
939939
return candidate
940940
}
@@ -964,7 +964,7 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp
964964
arg_type_name = (g.get_local_var_c_type(base_arg.name) or { '' }).trim_space().trim_right('*')
965965
}
966966
if arg_type_name == 'string' {
967-
candidate := '${name}_string'
967+
candidate := '${name}_T_string'
968968
if candidate in g.fn_param_is_ptr || candidate in g.fn_return_types {
969969
return candidate
970970
}
@@ -1009,7 +1009,7 @@ fn (mut g Gen) try_specialize_generic_call_name(name string, call_args []ast.Exp
10091009
mut changed := false
10101010
for i, part in parts {
10111011
if concrete := g.active_generic_types[part] {
1012-
parts[i] = g.generic_specialization_token_from_type(concrete)
1012+
parts[i] = 'T_${g.generic_specialization_token_from_type(concrete)}'
10131013
changed = true
10141014
}
10151015
}
@@ -1261,6 +1261,11 @@ fn (mut g Gen) gen_fn_decl_ptr(node &ast.FnDecl) {
12611261
g.active_generic_types = prev_generic_types.clone()
12621262
}
12631263
if g.generic_fn_param_names(*node).len > 0 {
1264+
// maxof[T]/minof[T] are compile-time functions fully inlined by the transformer.
1265+
// Skip emitting stub bodies (which contain invalid C).
1266+
if node.name in ['maxof', 'minof'] {
1267+
return
1268+
}
12641269
prev_generic_types := g.active_generic_types.clone()
12651270
for spec in g.generic_fn_specializations(*node) {
12661271
g.active_generic_types = spec.generic_types.clone()
@@ -4750,10 +4755,12 @@ fn (mut g Gen) emit_generic_fn_macro(fn_name string, node ast.FnDecl) {
47504755
|| stub_ret.starts_with('Array_fixed_') {
47514756
stub_ret = 'array'
47524757
}
4753-
// Emit a stub that aborts at runtime with a clear message instead
4754-
// of silently returning a zero value. This surfaces missing
4755-
// generic specialization bugs immediately rather than hiding them.
4758+
// Emit a stub that surfaces missing generic specializations both
4759+
// at compile time (#warning) and at runtime (abort).
47564760
g.sb.writeln('/* unresolved generic */ ${stub_ret} ${fn_name}() {')
4761+
g.sb.writeln('#if !defined(__TINYC__)')
4762+
g.sb.writeln('#warning "v2: unresolved generic stub: ${fn_name}"')
4763+
g.sb.writeln('#endif')
47574764
g.sb.writeln('\tfputs("v2: unresolved generic call: ${fn_name}\\n", stderr);')
47584765
g.sb.writeln('\tabort();')
47594766
if stub_ret != 'void' {

‎vlib/v2/transformer/fn.v‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,9 @@ fn (t &Transformer) resolve_method_call_name(receiver ast.Expr, method_name stri
18021802
}
18031803
// Self-hosted ARM64 builds can misresolve byte-oriented receivers as `i8`
18041804
// even though the builtin helper methods are declared on `u8`.
1805-
if c_prefix == 'i8' && t.lookup_method_cached('u8', method_name) != none {
1805+
// Only fall back to u8 when i8 has no method of its own (e.g. bytestr, hex).
1806+
if c_prefix == 'i8' && t.lookup_method_cached('i8', method_name) == none
1807+
&& t.lookup_method_cached('u8', method_name) != none {
18061808
c_prefix = 'u8'
18071809
}
18081810
if c_prefix == '' {
@@ -2867,6 +2869,15 @@ fn (t &Transformer) get_enum_type(expr ast.Expr) string {
28672869
return t.type_to_c_name(base)
28682870
}
28692871
}
2872+
// Fallback: for SelectorExpr (struct field access), resolve via struct field type
2873+
if expr is ast.SelectorExpr {
2874+
if field_type := t.get_struct_field_type(expr) {
2875+
base := t.unwrap_alias_and_pointer_type(field_type)
2876+
if base is types.Enum {
2877+
return t.type_to_c_name(base)
2878+
}
2879+
}
2880+
}
28702881
return ''
28712882
}
28722883

0 commit comments

Comments
 (0)