Skip to content

Commit 03ea0e8

Browse files
committed
v3: fix native selfhost regressions
1 parent 73ec30e commit 03ea0e8

5 files changed

Lines changed: 219 additions & 29 deletions

File tree

vlib/v3/driver/driver.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8564,7 +8564,7 @@ pub fn run(args []string) {
85648564
} else if is_checker_fixture {
85658565
used_fns, uses_generics = markused.mark_used_with_generic_usage_full_runtime(a,
85668566
markused_tc)
8567-
} else if building_v && current_parallel_transform {
8567+
} else if building_v {
85688568
if prepare_markused_overlap {
85698569
used_fns = markused.mark_used_without_generic_detection_prepared(a, markused_tc, mut
85708570
prepared_markused)

vlib/v3/gen/arm64/gen.v

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ fn (mut g Gen) reserve_value_stack_slot(val_id int, current_offset int) int {
434434
}
435435
val := g.m.values[val_id]
436436
result_size := g.m.type_size(val.typ)
437-
alloc_size := if result_size > 8 && val.typ > 0 && val.typ < g.m.type_store.types.len
438-
&& g.m.type_store.types[val.typ].kind == .struct_t {
437+
alloc_size := if result_size > 8 && g.is_value_aggregate_type(val.typ) {
439438
(result_size + 7) & ~7
440439
} else {
441440
8
@@ -445,22 +444,22 @@ fn (mut g Gen) reserve_value_stack_slot(val_id int, current_offset int) int {
445444
return new_offset
446445
}
447446

448-
// is_large_struct_type reports whether is large struct type applies in arm64.
447+
// is_large_struct_type reports whether an aggregate uses indirect argument/return passing.
449448
fn (g &Gen) is_large_struct_type(typ_id ssa.TypeID) bool {
450-
if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
451-
return false
452-
}
453-
typ := g.m.type_store.types[typ_id]
454-
return typ.kind == .struct_t && g.m.type_size(typ_id) > 16
449+
return g.is_value_aggregate_type(typ_id) && g.m.type_size(typ_id) > 16
455450
}
456451

457-
// is_aggregate_type reports whether is aggregate type applies in arm64.
452+
// is_aggregate_type reports whether an aggregate needs more than one register/word.
458453
fn (g &Gen) is_aggregate_type(typ_id ssa.TypeID) bool {
454+
return g.is_value_aggregate_type(typ_id) && g.m.type_size(typ_id) > 8
455+
}
456+
457+
fn (g &Gen) is_value_aggregate_type(typ_id ssa.TypeID) bool {
459458
if typ_id <= 0 || typ_id >= g.m.type_store.types.len {
460459
return false
461460
}
462461
typ := g.m.type_store.types[typ_id]
463-
return typ.kind == .struct_t && g.m.type_size(typ_id) > 8
462+
return typ.kind in [.struct_t, .array_t]
464463
}
465464

466465
// is_zero_const reports whether is zero const applies in arm64.
@@ -652,8 +651,7 @@ fn (mut g Gen) gen_instr(val_id int) {
652651
g.emit32(asm_str_imm(Reg(10), Reg(ptr_reg), 1))
653652
} else {
654653
src_size := g.m.type_size(src_val.typ)
655-
if src_size > 8 && src_val.typ > 0 && src_val.typ < g.m.type_store.types.len
656-
&& g.m.type_store.types[src_val.typ].kind == .struct_t {
654+
if src_size > 8 && g.is_value_aggregate_type(src_val.typ) {
657655
if src_off := g.stack_slot(src_id) {
658656
ptr_reg := g.load_val(ptr_id, 9)
659657
copy_size := g.aggregate_store_size(ptr_id, src_val.typ)
@@ -701,9 +699,8 @@ fn (mut g Gen) gen_instr(val_id int) {
701699
} else {
702700
ptr_reg := g.load_val(ptr_id, 9)
703701
result_size := g.m.type_size(val.typ)
704-
if result_size > 8 && val.typ > 0 && val.typ < g.m.type_store.types.len {
705-
typ := g.m.type_store.types[val.typ]
706-
if typ.kind == .struct_t {
702+
if result_size > 8 && g.is_value_aggregate_type(val.typ) {
703+
if val.typ > 0 && val.typ < g.m.type_store.types.len {
707704
if off := g.stack_slot(val_id) {
708705
if g.is_string_struct_type(val.typ) {
709706
copy_size := g.aggregate_load_size(ptr_id, val.typ)
@@ -1041,8 +1038,7 @@ fn (mut g Gen) gen_instr(val_id int) {
10411038
g.emit32(asm_mov_reg(Reg(1), Reg(10)))
10421039
} else {
10431040
ret_size := g.m.type_size(ret_val.typ)
1044-
if ret_size > 8 && ret_val.typ > 0 && ret_val.typ < g.m.type_store.types.len
1045-
&& g.m.type_store.types[ret_val.typ].kind == .struct_t {
1041+
if ret_size > 8 && g.is_value_aggregate_type(ret_val.typ) {
10461042
if off := g.stack_slot(ret_id) {
10471043
if g.is_string_struct_type(ret_val.typ) {
10481044
g.emit_load_string_regs_from_fp(off, 0, 1, ret_val.typ)
@@ -1159,9 +1155,8 @@ fn (mut g Gen) gen_call(val_id int, instr ssa.Instruction) {
11591155
} else {
11601156
arg_type_id := arg_val.typ
11611157
arg_size := g.m.type_size(arg_type_id)
1162-
if arg_size > 8 && arg_type_id > 0 && arg_type_id < g.m.type_store.types.len {
1163-
typ := g.m.type_store.types[arg_type_id]
1164-
if typ.kind == .struct_t {
1158+
if arg_size > 8 && g.is_value_aggregate_type(arg_type_id) {
1159+
if arg_type_id > 0 && arg_type_id < g.m.type_store.types.len {
11651160
if g.is_large_struct_type(arg_type_id) {
11661161
if arg_reg < 8 {
11671162
if !g.emit_value_address(arg_id, arg_reg) {
@@ -1296,9 +1291,8 @@ fn (mut g Gen) gen_call(val_id int, instr ssa.Instruction) {
12961291

12971292
if instr.typ != 0 {
12981293
ret_size := g.m.type_size(instr.typ)
1299-
if ret_size > 8 && instr.typ > 0 && instr.typ < g.m.type_store.types.len {
1300-
typ := g.m.type_store.types[instr.typ]
1301-
if typ.kind == .struct_t {
1294+
if ret_size > 8 && g.is_value_aggregate_type(instr.typ) {
1295+
if instr.typ > 0 && instr.typ < g.m.type_store.types.len {
13021296
if off := g.stack_slot(val_id) {
13031297
if g.is_string_struct_type(instr.typ) {
13041298
g.emit_store_fp(0, off)
@@ -1377,8 +1371,7 @@ fn (g &Gen) call_stack_arg_size(instr ssa.Instruction) int {
13771371
n_words = 2
13781372
} else {
13791373
arg_size := g.m.type_size(arg_val.typ)
1380-
if arg_size > 8 && arg_val.typ > 0 && arg_val.typ < g.m.type_store.types.len
1381-
&& g.m.type_store.types[arg_val.typ].kind == .struct_t {
1374+
if arg_size > 8 && g.is_value_aggregate_type(arg_val.typ) {
13821375
n_words = if g.is_large_struct_type(arg_val.typ) { 1 } else { (arg_size + 7) / 8 }
13831376
}
13841377
}

vlib/v3/markused/markused.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
618618
}
619619
generic_type_bases: generic_type_bases
620620
detect_generics: detect_reachable_generics
621-
body_checker_edges_authoritative: use_prepared && !detect_reachable_generics
621+
body_checker_edges_authoritative: !detect_reachable_generics
622622
}
623623
// Precollect every body's call/initializer-ref lists up front (across
624624
// threads when available): the BFS below then only does the cheap

vlib/v3/ssa/builder.v

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ fn (mut b Builder) ssa_type_from_checker_type(typ types.Type) TypeID {
587587
return b.array_type
588588
}
589589
if typ is types.ArrayFixed {
590-
return b.m.type_store.get_ptr(b.ssa_type_from_checker_type(typ.elem_type))
590+
return b.m.type_store.get_array(b.ssa_type_from_checker_type(typ.elem_type), typ.len)
591591
}
592592
if typ is types.Map {
593593
return b.map_type
@@ -1182,7 +1182,9 @@ fn (mut b Builder) register_functions() {
11821182
b.register_printing_stubs()
11831183
b.register_at_exit_stub()
11841184
b.register_rand_prng_interface_stubs()
1185+
b.register_embed_file_interface_stubs()
11851186
b.register_pthread_compat_stubs()
1187+
b.register_closure_once_stub()
11861188
b.register_prealloc_allocator_stubs()
11871189
b.register_prealloc_atomic_stubs()
11881190
b.register_atomic_builtin_stubs()
@@ -2492,6 +2494,98 @@ fn (mut b Builder) register_array_runtime_stubs() {
24922494
array_repeat_id := b.register_synthetic_function('array.repeat_to_depth', b.array_type,
24932495
p3_repeat)
24942496
b.generate_array_repeat_to_depth_body(array_repeat_id)
2497+
2498+
for sort_type in ['int', 'i8', 'i16', 'i64', 'u8', 'u16', 'u32', 'u64', 'isize', 'usize', 'f32',
2499+
'f64', 'rune', 'char'] {
2500+
elem_type := match sort_type {
2501+
'int', 'rune' { b.i32_type }
2502+
'i8' { b.i8_type }
2503+
'i16' { b.m.type_store.get_int(16) }
2504+
'i64', 'isize' { b.i64_type }
2505+
'u8', 'char' { b.u8_type }
2506+
'u16' { b.u16_type }
2507+
'u32' { b.u32_type }
2508+
'u64', 'usize' { b.u64_type }
2509+
'f32' { b.f32_type }
2510+
else { b.f64_type }
2511+
}
2512+
mut sort_params := []TypeID{}
2513+
sort_params << ptr_array
2514+
sort_id := b.register_synthetic_function('v3_array_sort_${sort_type}', b.void_type,
2515+
sort_params)
2516+
b.generate_scalar_array_sort_body(sort_id, elem_type)
2517+
}
2518+
}
2519+
2520+
// generate_scalar_array_sort_body emits the native equivalent of the scalar qsort helpers
2521+
// generated by the C backend.
2522+
fn (mut b Builder) generate_scalar_array_sort_body(func_id int, elem_type TypeID) {
2523+
ptr_i8 := b.m.type_store.get_ptr(b.i8_type)
2524+
ptr_i64 := b.m.type_store.get_ptr(b.i64_type)
2525+
ptr_array := b.m.type_store.get_ptr(b.array_type)
2526+
ptr_elem := b.m.type_store.get_ptr(elem_type)
2527+
entry := b.m.add_block(func_id, 'entry')
2528+
arr := b.func_add_argument(func_id, ptr_array, 'arr')
2529+
i_slot := b.block_instr0(.alloca, entry, ptr_i64)
2530+
j_slot := b.block_instr0(.alloca, entry, ptr_i64)
2531+
data_ptr := b.block_struct_field_ptr(entry, arr, b.array_type, 0)
2532+
data := b.block_instr1(.load, entry, ptr_i8, data_ptr)
2533+
len := b.block_load_array_int_field(entry, arr, 2)
2534+
zero := b.m.get_or_add_const(b.i64_type, '0')
2535+
one := b.m.get_or_add_const(b.i64_type, '1')
2536+
elem_size := b.m.get_or_add_const(b.i64_type, '${b.m.type_size(elem_type)}')
2537+
has_multiple := b.block_instr2(.gt, entry, b.i1_type, len, one)
2538+
outer_init := b.m.add_block(func_id, 'sort_outer_init')
2539+
outer_check := b.m.add_block(func_id, 'sort_outer_check')
2540+
inner_init := b.m.add_block(func_id, 'sort_inner_init')
2541+
inner_check := b.m.add_block(func_id, 'sort_inner_check')
2542+
compare := b.m.add_block(func_id, 'sort_compare')
2543+
swap := b.m.add_block(func_id, 'sort_swap')
2544+
inner_next := b.m.add_block(func_id, 'sort_inner_next')
2545+
outer_next := b.m.add_block(func_id, 'sort_outer_next')
2546+
done := b.m.add_block(func_id, 'sort_done')
2547+
b.block_instr3(.br, entry, b.void_type, has_multiple, ValueID(outer_init), ValueID(done))
2548+
2549+
b.block_instr2(.store, outer_init, b.void_type, zero, i_slot)
2550+
b.block_instr1(.jmp, outer_init, b.void_type, ValueID(outer_check))
2551+
2552+
i := b.block_instr1(.load, outer_check, b.i64_type, i_slot)
2553+
last := b.block_instr2(.sub, outer_check, b.i64_type, len, one)
2554+
has_outer := b.block_instr2(.lt, outer_check, b.i1_type, i, last)
2555+
b.block_instr3(.br, outer_check, b.void_type, has_outer, ValueID(inner_init), ValueID(done))
2556+
2557+
b.block_instr2(.store, inner_init, b.void_type, one, j_slot)
2558+
b.block_instr1(.jmp, inner_init, b.void_type, ValueID(inner_check))
2559+
2560+
j := b.block_instr1(.load, inner_check, b.i64_type, j_slot)
2561+
remaining := b.block_instr2(.sub, inner_check, b.i64_type, len, i)
2562+
has_inner := b.block_instr2(.lt, inner_check, b.i1_type, j, remaining)
2563+
b.block_instr3(.br, inner_check, b.void_type, has_inner, ValueID(compare), ValueID(outer_next))
2564+
2565+
prev_idx := b.block_instr2(.sub, compare, b.i64_type, j, one)
2566+
cur_offset := b.block_instr2(.mul, compare, b.i64_type, j, elem_size)
2567+
prev_offset := b.block_instr2(.mul, compare, b.i64_type, prev_idx, elem_size)
2568+
cur_ptr := b.block_instr2(.get_element_ptr, compare, ptr_elem, data, cur_offset)
2569+
prev_ptr := b.block_instr2(.get_element_ptr, compare, ptr_elem, data, prev_offset)
2570+
cur := b.block_instr1(.load, compare, elem_type, cur_ptr)
2571+
prev := b.block_instr1(.load, compare, elem_type, prev_ptr)
2572+
less_op := if b.is_unsigned_type(elem_type) { OpCode.ult } else { OpCode.lt }
2573+
less := b.block_instr2(less_op, compare, b.i1_type, cur, prev)
2574+
b.block_instr3(.br, compare, b.void_type, less, ValueID(swap), ValueID(inner_next))
2575+
2576+
b.block_instr2(.store, swap, b.void_type, cur, prev_ptr)
2577+
b.block_instr2(.store, swap, b.void_type, prev, cur_ptr)
2578+
b.block_instr1(.jmp, swap, b.void_type, ValueID(inner_next))
2579+
2580+
next_j := b.block_instr2(.add, inner_next, b.i64_type, j, one)
2581+
b.block_instr2(.store, inner_next, b.void_type, next_j, j_slot)
2582+
b.block_instr1(.jmp, inner_next, b.void_type, ValueID(inner_check))
2583+
2584+
next_i := b.block_instr2(.add, outer_next, b.i64_type, i, one)
2585+
b.block_instr2(.store, outer_next, b.void_type, next_i, i_slot)
2586+
b.block_instr1(.jmp, outer_next, b.void_type, ValueID(outer_check))
2587+
2588+
b.block_instr0(.ret, done, b.void_type)
24952589
}
24962590

24972591
// register_panic_stub updates register panic stub state for ssa.
@@ -3993,6 +4087,8 @@ fn (mut b Builder) generate_at_exit_body(func_id int, result_type TypeID, params
39934087

39944088
fn (mut b Builder) register_pthread_compat_stubs() {
39954089
ptr_i8 := b.m.type_store.get_ptr(b.i8_type)
4090+
zero_id := b.register_synthetic_c_function('v3_pthread_zero', b.u64_type, []TypeID{})
4091+
b.generate_const_body_with_params(zero_id, b.u64_type, '0', []TypeID{})
39964092
mut p_create := []TypeID{}
39974093
p_create << ptr_i8
39984094
p_create << b.i64_type
@@ -4008,6 +4104,24 @@ fn (mut b Builder) register_pthread_compat_stubs() {
40084104
b.generate_const_body_with_params(setkind_id, b.i32_type, '0', p_setkind)
40094105
}
40104106

4107+
fn (mut b Builder) register_closure_once_stub() {
4108+
callback_type := b.m.type_store.get_ptr(b.i8_type)
4109+
params := [callback_type]
4110+
func_id := b.register_synthetic_c_function('v_closure_init_once', b.void_type, params)
4111+
once_flag := b.m.add_global('v3_closure_init_once_done', b.i1_type)
4112+
entry := b.m.add_block(func_id, 'closure_once_entry')
4113+
callback := b.func_add_argument(func_id, callback_type, 'init_fn')
4114+
done := b.block_instr1(.load, entry, b.i1_type, once_flag)
4115+
call_init := b.m.add_block(func_id, 'closure_once_call')
4116+
return_block := b.m.add_block(func_id, 'closure_once_return')
4117+
b.block_instr3(.br, entry, b.void_type, done, ValueID(return_block), ValueID(call_init))
4118+
true_value := b.m.get_or_add_const(b.i1_type, '1')
4119+
b.block_instr2(.store, call_init, b.void_type, true_value, once_flag)
4120+
b.block_instr1(.call_indirect, call_init, b.void_type, callback)
4121+
b.block_instr1(.jmp, call_init, b.void_type, ValueID(return_block))
4122+
b.block_instr0(.ret, return_block, b.void_type)
4123+
}
4124+
40114125
fn (mut b Builder) register_rand_prng_interface_stubs() {
40124126
ptr_i8 := b.m.type_store.get_ptr(b.i8_type)
40134127
mut p_recv := []TypeID{}
@@ -4053,6 +4167,20 @@ fn (mut b Builder) register_rand_prng_interface_stubs() {
40534167
}
40544168
}
40554169

4170+
fn (mut b Builder) register_embed_file_interface_stubs() {
4171+
result_type := b.option_type_id('[]u8')
4172+
mut params := []TypeID{}
4173+
params << b.resolve_type('embed_file.Decoder')
4174+
params << b.array_type
4175+
func_id := b.register_synthetic_function('embed_file.Decoder.decompress', result_type, params)
4176+
entry := b.m.add_block(func_id, 'decompress_entry')
4177+
for i, param_type in params {
4178+
_ := b.func_add_argument(func_id, param_type, 'arg${i}')
4179+
}
4180+
result := b.block_option_value(entry, result_type, true, ValueID(0))
4181+
b.block_instr1(.ret, entry, b.void_type, result)
4182+
}
4183+
40564184
fn (mut b Builder) generate_noop_body(func_id int, params []TypeID) {
40574185
entry := b.m.add_block(func_id, 'noop_entry')
40584186
for i, param_type in params {
@@ -10121,7 +10249,13 @@ fn (mut b Builder) build_indirect_call(id flat.NodeId, node flat.Node, callee_id
1012110249

1012210250
fn (b &Builder) is_function_value_expr(id flat.NodeId) bool {
1012310251
typ := b.checked_expr_type_name(id).trim_space()
10124-
return typ.starts_with('fn(') || typ.starts_with('fn (')
10252+
if typ.starts_with('fn(') || typ.starts_with('fn (') {
10253+
return true
10254+
}
10255+
if b.tc != unsafe { nil } && typ.len > 0 && typ != 'unknown' {
10256+
return types.unalias_type(b.tc.parse_type(typ)) is types.FnType
10257+
}
10258+
return false
1012510259
}
1012610260

1012710261
fn (mut b Builder) call_expr_result_type(id flat.NodeId, node flat.Node) TypeID {

vlib/v3/tests/ssa_builder_parity_test.v

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,19 @@ fn main() {
219219
assert f.blocks.len > 0
220220
}
221221

222+
// test_scalar_array_sort_helper_builds_for_ssa validates this v3 regression case.
223+
fn test_scalar_array_sort_helper_builds_for_ssa() {
224+
m := build_transformed_source('scalar_array_sort_helper', '
225+
fn main() {
226+
mut values := [3, 1, 2]
227+
values.sort()
228+
}
229+
')
230+
assert has_call_to(m, 'main', 'v3_array_sort_int')
231+
f := find_func(m, 'v3_array_sort_int')
232+
assert f.blocks.len > 1
233+
}
234+
222235
// test_u8_array_bytestr_alias_builds_for_ssa validates this v3 regression case.
223236
fn test_u8_array_bytestr_alias_builds_for_ssa() {
224237
m := build_source('u8_array_bytestr_alias', '
@@ -320,6 +333,56 @@ fn apply(f fn (int, int) int, x int, y int) int {
320333
assert found_indirect
321334
}
322335

336+
// test_function_alias_field_call_lowers_to_call_indirect validates this v3 regression case.
337+
fn test_function_alias_field_call_lowers_to_call_indirect() {
338+
m := build_source('call_indirect_alias_field', '
339+
type Callback = fn (int, int) int
340+
341+
struct Holder {
342+
callback Callback
343+
}
344+
345+
fn apply(holder Holder, x int, y int) int {
346+
return holder.callback(x, y)
347+
}
348+
')
349+
assert has_instr_op(m, 'apply', .call_indirect)
350+
}
351+
352+
// test_embed_file_decoder_dispatch_stub_builds_for_ssa validates this v3 regression case.
353+
fn test_embed_file_decoder_dispatch_stub_builds_for_ssa() {
354+
m := build_source('embed_file_decoder_stub', 'fn main() {}')
355+
f := find_func(m, 'embed_file.Decoder.decompress')
356+
assert f.blocks.len == 1
357+
}
358+
359+
// test_closure_once_stub_builds_for_ssa validates this v3 regression case.
360+
fn test_closure_once_stub_builds_for_ssa() {
361+
m := build_source('closure_once_stub', 'fn main() {}')
362+
f := find_func(m, 'v_closure_init_once')
363+
assert f.blocks.len == 3
364+
assert has_instr_op(m, 'v_closure_init_once', .call_indirect)
365+
zero_fn := find_func(m, 'v3_pthread_zero')
366+
assert zero_fn.blocks.len == 1
367+
}
368+
369+
// test_fixed_array_return_keeps_value_type validates this v3 regression case.
370+
fn test_fixed_array_return_keeps_value_type() {
371+
m := build_source('fixed_array_return', '
372+
struct Digest {
373+
bytes [32]u8
374+
}
375+
376+
fn get_bytes(d &Digest) [32]u8 {
377+
return d.bytes
378+
}
379+
')
380+
f := find_func(m, 'get_bytes')
381+
typ := m.type_store.types[f.typ]
382+
assert typ.kind == .array_t
383+
assert typ.len == 32
384+
}
385+
323386
// test_label_and_goto_lower_to_jump_blocks validates this v3 regression case.
324387
fn test_label_and_goto_lower_to_jump_blocks() {
325388
m := build_source('label_goto', '

0 commit comments

Comments
 (0)