Skip to content

Commit df59af3

Browse files
authored
json2: reduce generated struct field code (#28250)
1 parent 7ddd19f commit df59af3

6 files changed

Lines changed: 139 additions & 56 deletions

File tree

vlib/json2/decode.v

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,25 @@ fn mark_struct_field_decoded(decoded_mask u64, mut decoded_fields []bool, field_
417417
return decoded_mask
418418
}
419419

420+
// find_struct_field centralizes the runtime part of struct key matching. Keeping
421+
// this loop outside the comptime field loop avoids emitting the same skip,
422+
// omitempty, length, and memory-comparison checks once for every struct field.
423+
@[noinline]
424+
fn (decoder &Decoder) find_struct_field(field_infos []StructFieldInfo, key_ptr voidptr, key_len int) int {
425+
for field_idx, field_info in field_infos {
426+
field_can_match := (!field_info.is_skip || field_info.is_required)
427+
&& !(field_info.is_omitempty
428+
&& decoder.is_empty_value(decoder.current_node.next.value))
429+
field_name_matches := key_len == field_info.json_name_len && unsafe {
430+
vmemcmp(key_ptr, field_info.json_name_ptr, field_info.json_name_len) == 0
431+
}
432+
if field_can_match && field_name_matches {
433+
return field_idx
434+
}
435+
}
436+
return -1
437+
}
438+
420439
// key_has_escape reports whether the JSON string key described by `key_info`
421440
// contains a `\` escape sequence.
422441
@[direct_array_access; inline]
@@ -880,37 +899,22 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
880899
key_len = unescaped_key.len
881900
}
882901

902+
matched_field_idx := decoder.find_struct_field(field_infos, key_ptr, key_len)
883903
mut matched := false
884904
field_idx = 0
885905
$for field in T.fields {
886906
if field.attrs.contains('skip') {
887-
if !matched {
888-
field_info := field_infos[field_idx]
889-
field_can_match := field_info.is_required
890-
&& !(field_info.is_omitempty
891-
&& decoder.is_empty_value(decoder.current_node.next.value))
892-
field_name_matches := key_len == field_info.json_name_len && unsafe {
893-
vmemcmp(key_ptr, field_info.json_name_ptr, field_info.json_name_len) == 0
894-
}
895-
if field_can_match && field_name_matches {
907+
if !matched && field_idx == matched_field_idx {
896908
decoder.current_node = decoder.current_node.next
897909
decoded_mask = mark_struct_field_decoded(decoded_mask, mut
898910
decoded_fields, field_idx)
899911
if decoder.current_node != unsafe { nil } {
900912
decoder.current_node = decoder.current_node.next
901913
}
902914
matched = true
903-
}
904915
}
905-
} else if !matched {
916+
} else if !matched && field_idx == matched_field_idx {
906917
field_info := field_infos[field_idx]
907-
field_can_match := (!field_info.is_skip || field_info.is_required)
908-
&& !(field_info.is_omitempty
909-
&& decoder.is_empty_value(decoder.current_node.next.value))
910-
field_name_matches := key_len == field_info.json_name_len && unsafe {
911-
vmemcmp(key_ptr, field_info.json_name_ptr, field_info.json_name_len) == 0
912-
}
913-
if field_can_match && field_name_matches {
914918
// value node
915919
decoder.current_node = decoder.current_node.next
916920

@@ -1039,7 +1043,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
10391043
decoded_mask = mark_struct_field_decoded(decoded_mask, mut
10401044
decoded_fields, field_idx)
10411045
matched = true
1042-
}
10431046
}
10441047
field_idx++
10451048
}

vlib/json2/encode.v

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,28 @@ fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool {
729729
return true
730730
}
731731

732+
// encode_struct_field_key keeps the non-type-specific part of struct field
733+
// encoding out of the comptime field loop. Otherwise every field gets its own
734+
// copy of the key-collision scan and key selection code.
735+
@[noinline]
736+
fn (mut encoder Encoder) encode_struct_field_key(mut used_keys []string, old_used_keys []string, prefix string, field_info EncoderFieldInfo, is_first bool) bool {
737+
if field_info.key_name in old_used_keys {
738+
return encoder.encode_object_key(is_first, prefix + field_info.key_name)
739+
}
740+
used_keys << field_info.key_name
741+
return encoder.encode_object_key(is_first, field_info.key_name)
742+
}
743+
744+
@[noinline]
745+
fn (mut encoder Encoder) encode_embedded_struct_field_key(mut used_keys []string, reserved_keys []string, prefix string, field_info EncoderFieldInfo, is_first bool) bool {
746+
should_prefix := field_info.key_name in used_keys || field_info.key_name in reserved_keys
747+
json_key := if should_prefix { prefix + field_info.key_name } else { field_info.key_name }
748+
if !should_prefix {
749+
used_keys << field_info.key_name
750+
}
751+
return encoder.encode_object_key(is_first, json_key)
752+
}
753+
732754
@[unsafe]
733755
fn (mut encoder Encoder) encode_struct_with_embeds[T](val T) {
734756
encoder.output << `{`
@@ -772,27 +794,17 @@ fn (mut encoder Encoder) encode_struct_fields[T](val T, was_first bool, old_used
772794
write_field = struct_field_should_encode(field_info, field_value)
773795

774796
if write_field {
775-
if field_info.key_name in old_used_keys {
776-
is_first = encoder.encode_object_key(is_first, prefix +
777-
field_info.key_name)
778-
} else {
779-
is_first = encoder.encode_object_key(is_first, field_info.key_name)
780-
used_keys << field_info.key_name
781-
}
797+
is_first = encoder.encode_struct_field_key(mut used_keys, old_used_keys,
798+
prefix, field_info, is_first)
782799
encoder.encode_struct_field_value(field_value)
783800
}
784801
}
785802
} $else {
786803
write_field = struct_field_should_encode(field_info, val.$(field.name))
787804

788805
if write_field {
789-
if field_info.key_name in old_used_keys {
790-
is_first = encoder.encode_object_key(is_first, prefix +
791-
field_info.key_name)
792-
} else {
793-
is_first = encoder.encode_object_key(is_first, field_info.key_name)
794-
used_keys << field_info.key_name
795-
}
806+
is_first = encoder.encode_struct_field_key(mut used_keys, old_used_keys,
807+
prefix, field_info, is_first)
796808
encoder.encode_struct_field_value(val.$(field.name))
797809
}
798810
}
@@ -858,34 +870,16 @@ fn (mut encoder Encoder) encode_embedded_struct_fields[T](val T, was_first bool,
858870
rlock field_value {
859871
write_field = struct_field_should_encode(field_info, field_value)
860872
if write_field {
861-
should_prefix := field_info.key_name in used_keys
862-
|| field_info.key_name in reserved_keys
863-
json_key := if should_prefix {
864-
prefix + field_info.key_name
865-
} else {
866-
field_info.key_name
867-
}
868-
is_first = encoder.encode_object_key(is_first, json_key)
869-
if !should_prefix {
870-
used_keys << field_info.key_name
871-
}
873+
is_first = encoder.encode_embedded_struct_field_key(mut used_keys,
874+
reserved_keys, prefix, field_info, is_first)
872875
encoder.encode_struct_field_value(field_value)
873876
}
874877
}
875878
} $else {
876879
write_field = struct_field_should_encode(field_info, val.$(field.name))
877880
if write_field {
878-
should_prefix := field_info.key_name in used_keys
879-
|| field_info.key_name in reserved_keys
880-
json_key := if should_prefix {
881-
prefix + field_info.key_name
882-
} else {
883-
field_info.key_name
884-
}
885-
is_first = encoder.encode_object_key(is_first, json_key)
886-
if !should_prefix {
887-
used_keys << field_info.key_name
888-
}
881+
is_first = encoder.encode_embedded_struct_field_key(mut used_keys,
882+
reserved_keys, prefix, field_info, is_first)
889883
encoder.encode_struct_field_value(val.$(field.name))
890884
}
891885
}

vlib/v3/gen/c/attributes_test.v

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module c
2+
3+
import v3.flat
4+
import v3.token
5+
import v3.types
6+
7+
fn cgen_attribute_test_gen() &FlatGen {
8+
mut ast := &flat.FlatAst{}
9+
mut tc := types.TypeChecker.new(ast)
10+
mut g := FlatGen.new()
11+
g.a = ast
12+
g.tc = &tc
13+
return &g
14+
}
15+
16+
fn test_noinline_attribute_is_preserved_for_generic_specialization() {
17+
mut g := cgen_attribute_test_gen()
18+
g.ccompiler = 'clang'
19+
source_pos := token.new_span(1, 20, 40)
20+
template_id := g.a.add_node(flat.Node{
21+
kind: .fn_decl
22+
value: 'helper'
23+
pos: source_pos
24+
})
25+
specialization_id := g.a.add_node(flat.Node{
26+
kind: .fn_decl
27+
value: 'helper_T_int'
28+
pos: source_pos
29+
})
30+
g.a.specialized_fn_nodes[int(specialization_id)] = true
31+
g.decl_attrs[int(template_id)] = ['noinline']
32+
g.decl_attrs_by_source_position[flat_fn_source_position_key(g.a.nodes[int(template_id)])] = [
33+
'noinline',
34+
]
35+
36+
assert g.fn_decl_c_attribute(template_id) == ' __attribute__((noinline))'
37+
assert g.fn_decl_c_attribute(specialization_id) == ' __attribute__((noinline))'
38+
39+
g.ccompiler = 'msvc'
40+
assert g.fn_decl_c_attribute(specialization_id) == ''
41+
assert g.fn_decl_msvc_noinline_prefix(specialization_id) == '__declspec(noinline) '
42+
}

vlib/v3/gen/c/cleanc.v

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ mut:
458458
struct_decl_infos map[string]StructDeclInfo
459459
struct_decl_short_infos map[string]StructDeclInfo
460460
decl_attrs map[int][]string
461+
decl_attrs_by_source_position map[u64][]string
461462
c_decl_abi_names map[string]string
462463
c_extern_global_names map[string]string
463464
shared_type_names map[string]SharedTypeInfo // __shared__ wrapper name -> wrapped type metadata
@@ -1132,6 +1133,7 @@ pub fn FlatGen.new() FlatGen {
11321133
struct_decl_infos: map[string]StructDeclInfo{}
11331134
struct_decl_short_infos: map[string]StructDeclInfo{}
11341135
decl_attrs: map[int][]string{}
1136+
decl_attrs_by_source_position: map[u64][]string{}
11351137
c_decl_abi_names: map[string]string{}
11361138
c_extern_global_names: map[string]string{}
11371139
shared_type_names: map[string]SharedTypeInfo{}
@@ -2966,6 +2968,7 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
29662968
g.struct_decl_infos.clear()
29672969
g.struct_decl_short_infos.clear()
29682970
g.decl_attrs.clear()
2971+
g.decl_attrs_by_source_position.clear()
29692972
g.c_decl_abi_names.clear()
29702973
g.c_extern_global_names.clear()
29712974
g.shared_type_names.clear()
@@ -4022,6 +4025,14 @@ fn (mut g FlatGen) collect_gen_info(no_parallel bool) {
40224025
target_idx := node.value['@attributes:'.len..].int()
40234026
attrs := node.generic_params().clone()
40244027
g.decl_attrs[target_idx] = attrs
4028+
if target_idx >= 0 && target_idx < g.a.nodes.len {
4029+
target := g.a.nodes[target_idx]
4030+
// Monomorphization erases the generic template node but preserves
4031+
// its source position, which is also retained by its specializations.
4032+
if target.pos.is_valid() {
4033+
g.decl_attrs_by_source_position[flat_fn_source_position_key(target)] = attrs
4034+
}
4035+
}
40254036
g.index_c_decl_attributes(target_idx, cur_module, attrs)
40264037
continue
40274038
}

vlib/v3/gen/c/fn.v

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1613,16 +1613,35 @@ fn (mut g FlatGen) gen_fn(node flat.Node) {
16131613
g.gen_fn_in_module(flat.empty_node, node, g.tc.cur_module, false)
16141614
}
16151615

1616+
fn (g &FlatGen) fn_decl_attributes(node_id flat.NodeId) []string {
1617+
if int(node_id) < 0 {
1618+
return []string{}
1619+
}
1620+
if attrs := g.decl_attrs[int(node_id)] {
1621+
return attrs
1622+
}
1623+
if g.a.specialized_fn_nodes[int(node_id)] {
1624+
node := g.a.nodes[int(node_id)]
1625+
if node.pos.is_valid() {
1626+
return g.decl_attrs_by_source_position[flat_fn_source_position_key(node)] or {
1627+
[]string{}
1628+
}
1629+
}
1630+
}
1631+
return []string{}
1632+
}
1633+
16161634
fn (g &FlatGen) fn_decl_c_attribute(node_id flat.NodeId) string {
16171635
if int(node_id) < 0 || g.ccompiler == 'msvc' {
16181636
return ''
16191637
}
1620-
attrs := g.decl_attrs[int(node_id)] or { return '' }
1638+
attrs := g.fn_decl_attributes(node_id)
16211639
mut c_attrs := []string{}
16221640
for raw_attr in attrs {
16231641
match raw_attr.all_before(':').trim_space() {
16241642
'_constructor' { c_attrs << 'constructor' }
16251643
'_destructor' { c_attrs << 'destructor' }
1644+
'noinline' { c_attrs << 'noinline' }
16261645
else {}
16271646
}
16281647
}
@@ -1632,6 +1651,18 @@ fn (g &FlatGen) fn_decl_c_attribute(node_id flat.NodeId) string {
16321651
return ' __attribute__((${c_attrs.join(', ')}))'
16331652
}
16341653

1654+
fn (g &FlatGen) fn_decl_msvc_noinline_prefix(node_id flat.NodeId) string {
1655+
if g.ccompiler != 'msvc' {
1656+
return ''
1657+
}
1658+
for raw_attr in g.fn_decl_attributes(node_id) {
1659+
if raw_attr.all_before(':').trim_space() == 'noinline' {
1660+
return '__declspec(noinline) '
1661+
}
1662+
}
1663+
return ''
1664+
}
1665+
16351666
fn (g &FlatGen) fn_decl_noreturn_prefix(node_id flat.NodeId) string {
16361667
if int(node_id) >= 0 && g.tc.declaration_has_attribute(node_id, 'noreturn') {
16371668
return 'VNORETURN '
@@ -4421,6 +4452,7 @@ fn (mut g FlatGen) gen_fn_in_module(node_id flat.NodeId, node flat.Node, module_
44214452
} else {
44224453
ret_type := g.fn_node_return_type(node, module_name)
44234454
g.set_cur_fn_ret(ret_type)
4455+
g.write(g.fn_decl_msvc_noinline_prefix(node_id))
44244456
if export_name := g.export_fn_name_in_module(module_name, node.value) {
44254457
if export_name == generated_fn_name {
44264458
g.write(g.exported_symbol_attribute())

vlib/v3/gen/c/fn_parallel_notd_v3_no_parallel.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,6 +2444,7 @@ fn (g &FlatGen) new_parallel_worker_config(worker_id int, result_only bool) &Fla
24442444
struct_decl_infos: g.struct_decl_infos
24452445
struct_decl_short_infos: g.struct_decl_short_infos
24462446
decl_attrs: g.decl_attrs
2447+
decl_attrs_by_source_position: g.decl_attrs_by_source_position
24472448
shared_type_names: g.shared_type_names
24482449
shared_alias_pointer_shorts: g.shared_alias_pointer_shorts
24492450
const_runtime_inits: if result_only {

0 commit comments

Comments
 (0)