| 1 | import os |
| 2 | import strings |
| 3 | |
| 4 | const const_cb_vexe = @VEXE |
| 5 | const const_cb_tests_dir = os.dir(@FILE) |
| 6 | const const_cb_v3_dir = os.dir(const_cb_tests_dir) |
| 7 | const const_cb_vlib_dir = os.dir(const_cb_v3_dir) |
| 8 | const const_cb_v3_src = os.join_path(const_cb_v3_dir, 'v3.v') |
| 9 | |
| 10 | fn const_cb_build_v3() string { |
| 11 | return const_cb_build_v3_with_flags('serial', '') |
| 12 | } |
| 13 | |
| 14 | fn const_cb_build_v3_parallel() string { |
| 15 | return const_cb_build_v3_with_flags('parallel', '-d parallel') |
| 16 | } |
| 17 | |
| 18 | fn const_cb_build_v3_with_flags(name string, flags string) string { |
| 19 | v3_bin := os.join_path(os.temp_dir(), 'v3_const_callback_test_${name}_${os.getpid()}') |
| 20 | os.rm(v3_bin) or {} |
| 21 | build := |
| 22 | os.execute('${const_cb_vexe} ${flags} -gc none -path "${const_cb_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${const_cb_v3_src}') |
| 23 | assert build.exit_code == 0, build.output |
| 24 | return v3_bin |
| 25 | } |
| 26 | |
| 27 | fn const_cb_write_project(source string) string { |
| 28 | root := os.join_path(os.temp_dir(), 'v3_const_callback_${os.getpid()}') |
| 29 | os.rmdir_all(root) or {} |
| 30 | os.mkdir_all(root) or { panic(err) } |
| 31 | header := '#ifndef V3_CONST_CALLBACK_NATIVE_H |
| 32 | #define V3_CONST_CALLBACK_NATIVE_H |
| 33 | typedef struct native_event { |
| 34 | int value; |
| 35 | } native_event; |
| 36 | typedef struct native_desc { |
| 37 | void (*cb)(const struct native_event*, void*); |
| 38 | void (*plain)(struct native_event*, void*); |
| 39 | void* user_data; |
| 40 | } native_desc; |
| 41 | typedef struct alias_desc { |
| 42 | void (*cb)(const struct native_event*, void*); |
| 43 | void* user_data; |
| 44 | } alias_desc; |
| 45 | typedef struct late_alias_desc { |
| 46 | void (*cb)(const struct native_event*, void*); |
| 47 | void* user_data; |
| 48 | } late_alias_desc; |
| 49 | #endif |
| 50 | ' |
| 51 | os.write_file(os.join_path(root, 'native_callback.h'), header) or { panic(err) } |
| 52 | path := os.join_path(root, 'main.v') |
| 53 | os.write_file(path, source) or { panic(err) } |
| 54 | return path |
| 55 | } |
| 56 | |
| 57 | fn test_c_callback_const_qualifier_codegen() { |
| 58 | v3_bin := const_cb_build_v3() |
| 59 | src := const_cb_write_project('module main |
| 60 | |
| 61 | #include "@DIR/native_callback.h" |
| 62 | |
| 63 | @[typedef] |
| 64 | struct C.native_event { |
| 65 | value int |
| 66 | } |
| 67 | |
| 68 | type Event = C.native_event |
| 69 | type ConstNativeCb = fn (const_event &Event, user_data voidptr) |
| 70 | |
| 71 | @[typedef] |
| 72 | struct C.native_desc { |
| 73 | mut: |
| 74 | cb fn (const_event &C.native_event, voidptr) = unsafe { nil } |
| 75 | plain fn (&C.native_event, voidptr) = unsafe { nil } |
| 76 | user_data voidptr |
| 77 | } |
| 78 | |
| 79 | @[typedef] |
| 80 | struct C.alias_desc { |
| 81 | mut: |
| 82 | cb ConstNativeCb = unsafe { nil } |
| 83 | user_data voidptr |
| 84 | } |
| 85 | |
| 86 | struct App { |
| 87 | mut: |
| 88 | hits int |
| 89 | } |
| 90 | |
| 91 | fn erased_event(e voidptr, data voidptr) { |
| 92 | event := unsafe { &C.native_event(e) } |
| 93 | mut app := unsafe { &App(data) } |
| 94 | app.hits += event.value |
| 95 | } |
| 96 | |
| 97 | fn plain_event(e &C.native_event, data voidptr) { |
| 98 | mut app := unsafe { &App(data) } |
| 99 | app.hits += e.value * 10 |
| 100 | } |
| 101 | |
| 102 | fn concrete_event(e &C.native_event, data voidptr) { |
| 103 | mut app := unsafe { &App(data) } |
| 104 | app.hits += e.value * 100 |
| 105 | } |
| 106 | |
| 107 | fn main() { |
| 108 | mut app := App{} |
| 109 | mut desc := C.native_desc{ |
| 110 | cb: erased_event |
| 111 | plain: plain_event |
| 112 | user_data: voidptr(&app) |
| 113 | } |
| 114 | event := C.native_event{value: 3} |
| 115 | desc.cb = erased_event |
| 116 | desc.cb(&event, desc.user_data) |
| 117 | desc.plain(&event, desc.user_data) |
| 118 | mut concrete := C.native_desc{ |
| 119 | user_data: voidptr(&app) |
| 120 | } |
| 121 | concrete.cb = concrete_event |
| 122 | concrete.cb(&event, concrete.user_data) |
| 123 | mut alias_desc := C.alias_desc{ |
| 124 | user_data: voidptr(&app) |
| 125 | } |
| 126 | alias_desc.cb = concrete_event |
| 127 | alias_desc.cb(&event, alias_desc.user_data) |
| 128 | println(int_str(app.hits)) |
| 129 | } |
| 130 | ') |
| 131 | out := os.join_path(os.temp_dir(), 'v3_const_callback_out_${os.getpid()}') |
| 132 | compile := os.execute('${v3_bin} ${src} -b c -o ${out}') |
| 133 | assert compile.exit_code == 0, compile.output |
| 134 | run := os.execute(out) |
| 135 | assert run.exit_code == 0, run.output |
| 136 | assert run.output.trim_space() == '633' |
| 137 | generated := os.read_file(out + '.c') or { panic(err) } |
| 138 | assert generated.contains('typedef void (*'), generated |
| 139 | assert generated.contains('(const struct native_event*, void*)'), generated |
| 140 | assert generated.contains('(struct native_event*, void*)'), generated |
| 141 | assert generated.contains('erased_event_callback_adapter_'), generated |
| 142 | assert generated.contains('concrete_event_callback_adapter_'), generated |
| 143 | assert generated.contains('const struct native_event* arg0, void* arg1'), generated |
| 144 | assert generated.contains('erased_event((void*)arg0, arg1);'), generated |
| 145 | assert generated.contains('concrete_event((struct native_event*)arg0, arg1);'), generated |
| 146 | assert generated.contains('.cb = erased_event_callback_adapter_'), generated |
| 147 | assert generated.contains('desc.cb = erased_event_callback_adapter_'), generated |
| 148 | assert generated.contains('concrete.cb = concrete_event_callback_adapter_'), generated |
| 149 | assert generated.contains('alias_desc.cb = concrete_event_callback_adapter_'), generated |
| 150 | assert generated.contains('.plain = plain_event'), generated |
| 151 | assert !generated.contains('.cb = (_fn_ptr'), generated |
| 152 | assert !generated.contains('desc.cb = (_fn_ptr'), generated |
| 153 | assert !generated.contains('concrete.cb = concrete_event;'), generated |
| 154 | assert !generated.contains('alias_desc.cb = concrete_event;'), generated |
| 155 | assert !generated.contains('.plain = plain_event_callback_adapter_'), generated |
| 156 | } |
| 157 | |
| 158 | fn test_generic_heap_positional_c_callback_codegen() { |
| 159 | v3_bin := const_cb_build_v3() |
| 160 | src := const_cb_write_project('module main |
| 161 | |
| 162 | #include "@DIR/native_callback.h" |
| 163 | |
| 164 | @[typedef] |
| 165 | struct C.native_event { |
| 166 | value int |
| 167 | } |
| 168 | |
| 169 | @[typedef] |
| 170 | struct C.native_desc { |
| 171 | mut: |
| 172 | cb fn (const_event &C.native_event, voidptr) = unsafe { nil } |
| 173 | plain fn (&C.native_event, voidptr) = unsafe { nil } |
| 174 | user_data voidptr |
| 175 | } |
| 176 | |
| 177 | struct App { |
| 178 | mut: |
| 179 | hits int |
| 180 | } |
| 181 | |
| 182 | fn concrete_event(e &C.native_event, data voidptr) { |
| 183 | mut app := unsafe { &App(data) } |
| 184 | app.hits += e.value |
| 185 | } |
| 186 | |
| 187 | fn plain_event(e &C.native_event, data voidptr) { |
| 188 | mut app := unsafe { &App(data) } |
| 189 | app.hits += e.value * 10 |
| 190 | } |
| 191 | |
| 192 | fn make_desc[T](data T) &C.native_desc { |
| 193 | return &C.native_desc{concrete_event, plain_event, voidptr(data)} |
| 194 | } |
| 195 | |
| 196 | fn main() { |
| 197 | mut app := App{} |
| 198 | event := C.native_event{value: 5} |
| 199 | desc := make_desc(&app) |
| 200 | desc.cb(&event, desc.user_data) |
| 201 | desc.plain(&event, desc.user_data) |
| 202 | println(int_str(app.hits)) |
| 203 | } |
| 204 | ') |
| 205 | out := os.join_path(os.temp_dir(), 'v3_const_callback_heap_positional_out_${os.getpid()}') |
| 206 | compile := os.execute('${v3_bin} ${src} -b c -o ${out}') |
| 207 | assert compile.exit_code == 0, compile.output |
| 208 | run := os.execute(out) |
| 209 | assert run.exit_code == 0, run.output |
| 210 | assert run.output.trim_space() == '55' |
| 211 | generated := os.read_file(out + '.c') or { panic(err) } |
| 212 | assert generated.contains('concrete_event_callback_adapter_'), generated |
| 213 | assert generated.contains('.cb = concrete_event_callback_adapter_'), generated |
| 214 | assert !generated.contains('.cb = concrete_event,'), generated |
| 215 | } |
| 216 | |
| 217 | fn test_c_callback_c_alias_declared_after_field_codegen() { |
| 218 | v3_bin := const_cb_build_v3() |
| 219 | src := const_cb_write_project('module main |
| 220 | |
| 221 | #include "@DIR/native_callback.h" |
| 222 | |
| 223 | @[typedef] |
| 224 | struct C.late_alias_desc { |
| 225 | mut: |
| 226 | cb fn (&Event, voidptr) = unsafe { nil } |
| 227 | user_data voidptr |
| 228 | } |
| 229 | |
| 230 | @[typedef] |
| 231 | struct C.native_event { |
| 232 | value int |
| 233 | } |
| 234 | |
| 235 | type Event = C.native_event |
| 236 | |
| 237 | struct App { |
| 238 | mut: |
| 239 | hits int |
| 240 | } |
| 241 | |
| 242 | fn late_alias_event(e &C.native_event, data voidptr) { |
| 243 | mut app := unsafe { &App(data) } |
| 244 | app.hits += e.value |
| 245 | } |
| 246 | |
| 247 | fn main() { |
| 248 | mut app := App{} |
| 249 | event := C.native_event{value: 11} |
| 250 | mut desc := C.late_alias_desc{ |
| 251 | cb: late_alias_event |
| 252 | user_data: voidptr(&app) |
| 253 | } |
| 254 | desc.cb(&event, desc.user_data) |
| 255 | desc.cb = late_alias_event |
| 256 | desc.cb(&event, desc.user_data) |
| 257 | println(int_str(app.hits)) |
| 258 | } |
| 259 | ') |
| 260 | out := os.join_path(os.temp_dir(), 'v3_const_callback_late_alias_out_${os.getpid()}') |
| 261 | compile := os.execute('${v3_bin} ${src} -b c -o ${out}') |
| 262 | assert compile.exit_code == 0, compile.output |
| 263 | run := os.execute(out) |
| 264 | assert run.exit_code == 0, run.output |
| 265 | assert run.output.trim_space() == '22' |
| 266 | generated := os.read_file(out + '.c') or { panic(err) } |
| 267 | assert generated.contains('(const struct native_event*, void*)'), generated |
| 268 | assert generated.contains('late_alias_event_callback_adapter_'), generated |
| 269 | assert generated.contains('const struct native_event* arg0, void* arg1'), generated |
| 270 | assert generated.contains('late_alias_event((struct native_event*)arg0, arg1);'), generated |
| 271 | assert generated.contains('.cb = late_alias_event_callback_adapter_'), generated |
| 272 | assert generated.contains('desc.cb = late_alias_event_callback_adapter_'), generated |
| 273 | assert !generated.contains('main__Event*'), generated |
| 274 | assert !generated.contains('.cb = late_alias_event,'), generated |
| 275 | assert !generated.contains('desc.cb = late_alias_event;'), generated |
| 276 | } |
| 277 | |
| 278 | fn test_c_callback_const_qualifier_parallel_worker_codegen() { |
| 279 | v3_bin := const_cb_build_v3_parallel() |
| 280 | mut src := strings.new_builder(96 * 1024) |
| 281 | src.write_string('module main |
| 282 | |
| 283 | #include "@DIR/native_callback.h" |
| 284 | |
| 285 | @[typedef] |
| 286 | struct C.native_event { |
| 287 | value int |
| 288 | } |
| 289 | |
| 290 | type Event = C.native_event |
| 291 | type ConstNativeCb = fn (const_event &Event, user_data voidptr) |
| 292 | |
| 293 | @[typedef] |
| 294 | struct C.alias_desc { |
| 295 | mut: |
| 296 | cb ConstNativeCb = unsafe { nil } |
| 297 | user_data voidptr |
| 298 | } |
| 299 | |
| 300 | struct App { |
| 301 | mut: |
| 302 | hits int |
| 303 | } |
| 304 | |
| 305 | fn concrete_event(e &C.native_event, data voidptr) { |
| 306 | mut app := unsafe { &App(data) } |
| 307 | app.hits += e.value |
| 308 | } |
| 309 | |
| 310 | ') |
| 311 | for i in 0 .. 1100 { |
| 312 | src.write_string('fn filler_${i}() int { return ${i} }\n') |
| 313 | } |
| 314 | src.write_string(' |
| 315 | fn run_parallel(mut app App) { |
| 316 | mut desc := C.alias_desc{ |
| 317 | user_data: voidptr(app) |
| 318 | } |
| 319 | desc.cb = concrete_event |
| 320 | event := C.native_event{value: 7} |
| 321 | desc.cb(&event, desc.user_data) |
| 322 | } |
| 323 | |
| 324 | fn main() { |
| 325 | mut app := App{} |
| 326 | run_parallel(mut app) |
| 327 | println(int_str(app.hits)) |
| 328 | } |
| 329 | ') |
| 330 | path := const_cb_write_project(src.str()) |
| 331 | out := os.join_path(os.temp_dir(), 'v3_const_callback_parallel_out_${os.getpid()}') |
| 332 | compile := os.execute('VJOBS=2 ${v3_bin} ${path} -b c -o ${out}') |
| 333 | assert compile.exit_code == 0, compile.output |
| 334 | run := os.execute(out) |
| 335 | assert run.exit_code == 0, run.output |
| 336 | assert run.output.trim_space() == '7' |
| 337 | generated := os.read_file(out + '.c') or { panic(err) } |
| 338 | assert generated.contains('concrete_event_callback_adapter_'), generated |
| 339 | assert generated.contains('const struct native_event* arg0, void* arg1'), generated |
| 340 | assert generated.contains('concrete_event((struct native_event*)arg0, arg1);'), generated |
| 341 | assert generated.contains('desc.cb = concrete_event_callback_adapter_'), generated |
| 342 | assert !generated.contains('const gg__Event'), generated |
| 343 | assert !generated.contains('desc.cb = concrete_event;'), generated |
| 344 | } |
| 345 | |