1# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14# Add a panda assembly to the project using the specified source file
15#
16# Example usage:
17#
18#   add_panda_assembly(TARGET <name> SOURCE <source> INDIR <input directory> OUTDIR <output directory> TARGETNAME <target file name>)
19#
20# Adds a panda assembly target called <name> to be build from <source> file
21# listed in the command invocation.
22function(add_panda_assembly)
23    set(prefix ARG)
24    set(noValues)
25    set(singleValues TARGET SOURCE INDIR OUTDIR TARGETNAME)
26    set(multiValues)
27    cmake_parse_arguments(${prefix}
28                          "${noValues}"
29                          "${singleValues}"
30                          "${multiValues}"
31                          ${ARGN})
32    if (NOT DEFINED ARG_TARGET)
33        message(FATAL_ERROR "Mandatory TARGET argument is not defined.")
34    endif()
35
36    if (NOT DEFINED ARG_SOURCE)
37        message(FATAL_ERROR "Mandatory SOURCE argument is not defined.")
38    endif()
39
40    set(source_file_dir "${CMAKE_CURRENT_SOURCE_DIR}")
41
42    if (DEFINED ARG_INDIR)
43        set(source_file_dir "${ARG_INDIR}")
44    endif()
45
46    set(binary_file_dir "${CMAKE_CURRENT_BINARY_DIR}")
47
48    if (DEFINED ARG_OUTDIR)
49        set(binary_file_dir "${ARG_OUTDIR}")
50    endif()
51
52    set(target_file_name "${ARG_TARGET}")
53
54    if (DEFINED ARG_TARGETNAME)
55        set(target_file_name "${ARG_TARGETNAME}")
56    endif()
57
58    if (TARGET ARG_TARGET)
59        message(FATAL_ERROR "Target ${ARG_TARGET} is already defined.")
60    endif()
61
62    set(source_file "${source_file_dir}/${ARG_SOURCE}")
63    set(binary_file "${binary_file_dir}/${target_file_name}.abc")
64
65    if(CMAKE_CROSSCOMPILING)
66        ExternalProject_Get_Property(panda_host_tools binary_dir)
67        set(assembler_target panda_host_tools-build)
68        set(assembler_bin    "${binary_dir}/assembler/ark_asm")
69    else()
70        set(assembler_target ark_asm)
71        set(assembler_bin    $<TARGET_FILE:${assembler_target}>)
72    endif()
73
74    add_custom_command(OUTPUT "${binary_file}"
75                       COMMENT "Building ${ARG_TARGET}"
76                       COMMAND "${assembler_bin}" "${source_file}" "${binary_file}"
77                       DEPENDS ${assembler_target} "${source_file}")
78
79    add_custom_target(${ARG_TARGET} DEPENDS "${binary_file}")
80    set_target_properties(${ARG_TARGET} PROPERTIES FILE "${binary_file}")
81endfunction()
82
83# Use `mkdir` instead of `file(MAKE_DIRECTORY)` to create dependency on the directory existence:
84set(COMPILER_STATS_DIR "${CMAKE_BINARY_DIR}/compiler/stats/csv")
85add_custom_target(compiler_stats_dir
86                  COMMAND mkdir -p "${COMPILER_STATS_DIR}")
87
88# Add a single buildable and runnable Panda Assembly file to the build tree.
89#
90# Usage:
91#
92#   panda_add_asm_file(
93#        FILE <source>
94#        TARGET <target>
95#        [ENTRY <entry_point>]
96#        [SUBDIR <subdir>]
97#        [OUTPUT_FILE_VARIABLE <variable>]
98#        [ERROR_FILE_VARIABLE <variable>]
99#        [SKIP_BUILD TRUE|FALSE]
100#        [AOT_MODE TRUE|FALSE]
101#        [AOT_STATS TRUE|FALSE]
102#        [AOT_COMPILER aot|llvm]
103#        [DEPENDS <dependency>...]
104#        [RUNTIME_OPTIONS <option>...]
105#        [RUNTIME_FAIL_TEST]
106#        [COMPILER_OPTIONS <option>...]
107#        [AOT_GC_OPTION <option>]
108#        [ENTRY_ARGUMENTS <argument>...]
109#        [TIMEOUT <timeout>]
110#        [LANGUAGE_CONTEXT <language>]
111#    )
112#
113# Adds a target <target> which assembles the assembly file <source>
114# with Panda assembler and runs it with Panda interpreter.
115#
116# Options:
117#
118# ENTRY
119#   Entry point to execute in format <Class>::<method>. By default _GLOBAL::main is used
120#
121# SUBDIR
122#   Subdirectory in the current binary directory that is used to store build artifacts.
123#   Full path to directory with artifacts: ${CMAKE_CURRENT_BINARY_DIR}/<subdir>/<target>
124#
125# OUTPUT_FILE_VARIABLE, ERROR_FILE_VARIABLE
126#   The variable named will be set with the paths to files with contents of the stdout and
127#   stderr of the program respectively
128#
129# SKIP_BUILD
130#   Do not run assembly
131#
132# AOT_MODE
133#   Run test in AOT mode
134#
135# AOT_STATS
136#   Creates additional independent target `${TARGET}-stats`.
137#   `stats`-target dumps AOT compiler statistics in `${COMPILER_STATS_DIR}/${TARGET}.csv`
138#
139# DEPENDS
140#   List of additional dependences (exclude assembler and interpreter)
141#
142# RUNTIME_OPTIONS
143#   Runtime options
144#
145# RUNTIME_FAIL_TEST
146#   runtime is expected to fail
147#
148# COMPILER_OPTIONS
149#   Options for compiler, given both to panda and paoc
150#
151# ASSEMBLER_OPTIONS
152#   Options for assembler
153#
154# AOT_GC_OPTION
155#  Type of a gc
156#
157# ENTRY_ARGUMENTS
158#   List of arguments that will be passed to program's entry point
159#
160# TIMEOUT
161#   If specified, the program will be run and terminated with the signal 10 (corresponds
162#   to SIGUSR1 on most platforms) after the given timeout. The format of the value
163#   must match the `timeout` command. The run will be considered successful if the program
164#   exits before the timeout with the successful exit code or if it is terminated
165#   after the timeout with the signal 10.
166#
167# LANGUAGE_CONTEXT
168#   Set the language-dependent semantics for the code. Possible values: core, java.
169#
170function(panda_add_asm_file)
171    set(prefix ARG)
172    set(noValues RUNTIME_FAIL_TEST)
173    set(singleValues FILE ENTRY TARGET SUBDIR OUTPUT_FILE_VARIABLE ERROR_FILE_VARIABLE SKIP_BUILD AOT_MODE AOT_STATS AOT_COMPILER TIMEOUT LANGUAGE_CONTEXT AOT_GC_OPTION)
174    set(multiValues DEPENDS RUNTIME_OPTIONS COMPILER_OPTIONS ENTRY_ARGUMENTS PRLIMIT_OPTIONS ADDITIONAL_STDLIBS LLVMAOT_OPTIONS ASSEMBLER_OPTIONS)
175    cmake_parse_arguments(${prefix}
176                          "${noValues}"
177                          "${singleValues}"
178                          "${multiValues}"
179                          ${ARGN})
180
181    if (NOT DEFINED ARG_FILE)
182        message(FATAL_ERROR "Mandatory FILE argument is not defined.")
183    endif()
184
185    if (NOT DEFINED ARG_TARGET)
186        message(FATAL_ERROR "Mandatory TARGET argument is not defined.")
187    endif()
188
189    if (TARGET ARG_TARGET)
190        message(FATAL_ERROR "Target ${ARG_TARGET} is already defined.")
191    endif()
192
193    set(build_dir   "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBDIR}/${ARG_TARGET}")
194    set(build_log   "${build_dir}/build.log")
195    set(build_out   "${build_dir}/build.out")
196    set(build_err   "${build_dir}/build.err")
197    set(binary_file "${build_dir}/test.abc")
198    set(launch_file "${build_dir}/launch.sh")
199    set(output_file "${build_dir}/run.out")
200    set(error_file  "${build_dir}/run.err")
201
202    file(MAKE_DIRECTORY "${build_dir}")
203
204    if (DEFINED ARG_OUTPUT_FILE_VARIABLE)
205        set(${ARG_OUTPUT_FILE_VARIABLE} "${output_file}" PARENT_SCOPE)
206    endif()
207
208    if (DEFINED ARG_ERROR_FILE_VARIABLE)
209        set(${ARG_ERROR_FILE_VARIABLE} "${error_file}" PARENT_SCOPE)
210    endif()
211
212    if (ARG_SKIP_BUILD)
213        set(binary_file "${ARG_FILE}")
214    else()
215        set(assembler ark_asm)
216        add_custom_command(OUTPUT "${binary_file}"
217                          COMMENT "Building ${ARG_TARGET}"
218                          COMMAND ${PANDA_RUN_PREFIX} $<TARGET_FILE:${assembler}> ${ARG_ASSEMBLER_OPTIONS} --verbose --log-file "${build_log}" "${ARG_FILE}" "${binary_file}" 1>"${build_out}" 2>"${build_err}"
219                          DEPENDS ${assembler} "${ARG_FILE}")
220    endif()
221
222    if (DEFINED ARG_TIMEOUT AND NOT "${ARG_TIMEOUT}" STREQUAL "")
223        set(timeout_signal 10)
224        set(timeout_prefix "timeout --preserve-status --signal=${timeout_signal} --kill-after=10s ${ARG_TIMEOUT}")
225        set(timeout_suffix "|| [ `expr \$? % 128` -eq ${timeout_signal} ]")
226    else()
227        set(timeout_prefix "")
228        set(timeout_suffix "")
229    endif()
230
231    if (NOT DEFINED ARG_ENTRY)
232        set(ARG_ENTRY "_GLOBAL::main")
233    endif()
234
235    set(panda_stdlib arkstdlib)
236    set(panda_cli    ark)
237    set(context     "${ARG_LANGUAGE_CONTEXT}")
238    set(stdlibs)
239
240    if ("${context}" STREQUAL "core")
241        list(APPEND stdlibs "${${panda_stdlib}_BINARY_DIR}/${panda_stdlib}.abc")
242    endif()
243    if (DEFINED ARG_ADDITIONAL_STDLIBS)
244        list(APPEND stdlibs ${ARG_ADDITIONAL_STDLIBS})
245    endif()
246    string(REPLACE ";" ":" boot_stdlibs "${stdlibs}")
247
248    if (ARG_AOT_MODE)
249        set(aot_compiler ark_aot)
250        set(backend_compiler "aot")
251        if (DEFINED ARG_AOT_COMPILER)
252            set(backend_compiler ${ARG_AOT_COMPILER})
253        endif()
254        set(launch_aot_file "${build_dir}/launch_aot.sh")
255        set(aot_file        "${build_dir}/test.an")
256        set(aot_file_stats  "${build_dir}/test_stats.an")
257        set(launcher_aot
258            "${PANDA_RUN_PREFIX}"
259            "$<TARGET_FILE:${aot_compiler}>"
260            "--paoc-mode=${backend_compiler}"
261            "--boot-panda-files \"${boot_stdlibs}\""
262            "--load-runtimes=${context}"
263            "--paoc-panda-files \"${binary_file}\""
264            "--paoc-output \"${aot_file}\""
265            "--compiler-ignore-failures=false"
266            "${ARG_COMPILER_OPTIONS}"
267            "${ARG_LLVMAOT_OPTIONS}"
268            "${ARG_AOT_GC_OPTION}"
269            "1>>\"${build_out}\""
270            "2>>\"${build_err}\""
271        )
272        string(REPLACE ";" " " launcher_aot "${launcher_aot}")
273        file(GENERATE OUTPUT ${launch_aot_file} CONTENT "${launcher_aot}")
274
275        # TODO(msherstennikov): enable for arm64
276        # Problem in arm64 is that ark_aotdump cannot open aot elf file with error: "undefined symbol: aot"
277        if (NOT PANDA_MINIMAL_VIXL AND PANDA_TARGET_AMD64)
278            set(aotdump_command
279                    "${PANDA_RUN_PREFIX}"
280                    "$<TARGET_FILE:ark_aotdump>"
281                    "--output-file=/dev/null"
282                    "--show-code" "disasm"
283                    "${aot_file}")
284        endif()
285
286        set(aot_compile_depends "${aot_compiler}" "${binary_file}")
287        if (NOT PANDA_MINIMAL_VIXL)
288            list(APPEND aot_compile_depends "ark_aotdump")
289        endif()
290        if (DEFINED ARG_DEPENDS)
291            list(APPEND aot_compile_depends ${ARG_DEPENDS})
292        endif()
293
294        add_custom_target(${ARG_TARGET}-compile-aot
295                COMMENT "Running aot compiler for ${ARG_TARGET}"
296                COMMAND . ${launch_aot_file} || (cat ${build_err} && echo "Command: " && cat ${launch_aot_file} && false)
297                COMMAND ${aotdump_command}
298                DEPENDS ${aot_compile_depends})
299        add_custom_command(OUTPUT "${aot_file}"
300                           DEPENDS ${ARG_TARGET}-compile-aot)
301        list(APPEND ARG_RUNTIME_OPTIONS "--aot-file=${aot_file}")
302
303        if (ARG_AOT_STATS)
304            set(launch_aot_stats_file "${build_dir}/launch_aot_stats.sh")
305            set(launcher_aot_stats
306                "${PANDA_RUN_PREFIX}"
307                "$<TARGET_FILE:${aot_compiler}>"
308                "--boot-panda-files \"${boot_stdlibs}\""
309                "--load-runtimes=${context}"
310                "--paoc-panda-files \"${binary_file}\""
311                "--paoc-output \"${aot_file_stats}\""
312                "--compiler-ignore-failures=false"
313                "--compiler-dump-stats-csv=\"${COMPILER_STATS_DIR}/${ARG_TARGET}.csv\""
314                "${ARG_COMPILER_OPTIONS}"
315                "${ARG_AOT_GC_OPTION}"
316                "1>>\"${build_out}\""
317                "2>>\"${build_err}\""
318            )
319            string(REPLACE ";" " " launcher_aot_stats "${launcher_aot_stats}")
320            file(GENERATE OUTPUT ${launch_aot_stats_file} CONTENT "${launcher_aot_stats}")
321            add_custom_target(${ARG_TARGET}-stats
322                              COMMENT "Gathering AOT-statistics for ${ARG_TARGET}"
323                              COMMAND . ${launch_aot_stats_file}
324                              DEPENDS ${aot_compiler} "${binary_file}" compiler_stats_dir)
325            add_dependencies(${ARG_TARGET}-stats ${ARG_TARGET}-compile-aot)
326        endif()
327    endif()
328
329    # NB! The lines below imply that we cannot handle ";" properly
330    # in both Panda's own options and the runned program's options.
331    # TODO: Fix this once this becomes an issue.
332    string(REPLACE ";" " " runtime_options "${ARG_COMPILER_OPTIONS} ${ARG_RUNTIME_OPTIONS}")
333    string(REPLACE ";" " " entry_arguments "${ARG_ENTRY_ARGUMENTS}")
334
335    set(prlimit_cmd "")
336    if (DEFINED ARG_PRLIMIT_OPTIONS)
337        set(prlimit_cmd "prlimit ${ARG_PRLIMIT_OPTIONS}")
338        string(REPLACE ";" " " prlimit_cmd "${prlimit_cmd}")
339    endif()
340
341    if (${runtime_options} MATCHES ".*events-output=csv.*")
342        set(runtime_options "${runtime_options} --events-file=${build_dir}/events.csv")
343    endif()
344
345    set(launcher
346        "${timeout_prefix}"
347        "${prlimit_cmd}"
348        "${PANDA_RUN_PREFIX}"
349        $<TARGET_FILE:${panda_cli}>
350        "--boot-panda-files \"${boot_stdlibs}\""
351        "--load-runtimes=${context}"
352        "--compiler-ignore-failures=false"
353        "${runtime_options}"
354        "\"${binary_file}\""
355        "\"${ARG_ENTRY}\""
356        "${entry_arguments}"
357        "1>\"${output_file}\""
358        "2>\"${error_file}\""
359        "${timeout_suffix}"
360    )
361    string(REPLACE ";" " " launcher "${launcher}")
362    if (ARG_RUNTIME_FAIL_TEST)
363        string(APPEND launcher " || (exit 0)")
364    endif()
365    file(GENERATE OUTPUT ${launch_file} CONTENT "${launcher}")
366
367    add_custom_target(${ARG_TARGET}
368                      COMMENT "Running ${ARG_TARGET}"
369                      COMMAND . ${launch_file} || (cat ${error_file} && false)
370                      DEPENDS ${panda_cli} "${binary_file}" ${aot_file})
371
372    if (ARG_AOT_MODE)
373        add_dependencies(${ARG_TARGET} ${ARG_TARGET}-compile-aot)
374    endif()
375
376    if (DEFINED ARG_DEPENDS)
377        add_dependencies(${ARG_TARGET} ${ARG_DEPENDS})
378    endif()
379    if ("${context}" STREQUAL "core")
380        add_dependencies(${ARG_TARGET} ${panda_stdlib})
381    endif()
382
383endfunction()
384
385# Add a single buildable and verifiable Panda Assembly file to the build tree.
386#
387# Usage:
388#
389#   verifier_add_asm_file(
390#        FILE <source>
391#        TARGET <target>
392#        [RUNTIME_OPTIONS <runtime options>]
393#        [VERIFIER_OPTIONS <verifier options>]
394#        [SUBDIR <subdir>]
395#        [OUTPUT_FILE_VARIABLE <variable>]
396#        [ERROR_FILE_VARIABLE <variable>]
397#        [DEPENDS <dependency>...]
398#        [TIMEOUT <timeout>]
399#        [LANGUAGE_CONTEXT <language>]
400#    )
401#
402# Adds a target <target> which assembles the assembly file <source>
403# with Panda assembler and verifies it with verifier.
404#
405# Options:
406#
407# SUBDIR
408#   Subdirectory in the current binary directory that is used to store build artifacts.
409#   Full path to directory with artifacts: ${CMAKE_CURRENT_BINARY_DIR}/<subdir>/<target>
410#
411# OUTPUT_FILE_VARIABLE, ERROR_FILE_VARIABLE
412#   The variable named will be set with the paths to files with contents of the stdout and
413#   stderr of the program respectively
414#
415# DEPENDS
416#   List of additional dependences (exclude assembler and interpreter)
417#
418# RUNTIME_OPTIONS
419#   Runtime initialization options
420#
421# VERIFIER_OPTIONS
422#   Verifier CLI options
423#
424# VERIFIER_FAIL_TEST
425#   If true, verifier is allowed to exit with non-0 exit code
426#
427# TIMEOUT
428#   If specified, the program will be run and terminated with the signal 10 (corresponds
429#   to SIGUSR1 on most platforms) after the given timeout. The format of the value
430#   must match the `timeout` command. The run will be considered successful if the program
431#   exits before the timeout with the successful exit code or if it is terminated
432#   after the timeout with the signal 10.
433#
434# LANGUAGE_CONTEXT
435#   Set the language-dependent semantics for the code. Possible values: core, java.
436#
437function(verifier_add_asm_file)
438    set(prefix ARG)
439    set(noValues VERIFIER_FAIL_TEST)
440    set(singleValues
441        FILE
442        TARGET
443        SUBDIR
444        OUTPUT_FILE_VARIABLE
445        ERROR_FILE_VARIABLE
446        TIMEOUT
447        LANGUAGE_CONTEXT)
448    set(multiValues DEPENDS RUNTIME_OPTIONS VERIFIER_OPTIONS STDLIBS)
449    cmake_parse_arguments(${prefix}
450                          "${noValues}"
451                          "${singleValues}"
452                          "${multiValues}"
453                          ${ARGN})
454
455    if (NOT DEFINED ARG_FILE)
456        message(FATAL_ERROR "Mandatory FILE argument is not defined.")
457    endif()
458
459    if (NOT DEFINED ARG_TARGET)
460        message(FATAL_ERROR "Mandatory TARGET argument is not defined.")
461    endif()
462
463    if (TARGET ARG_TARGET)
464        message(FATAL_ERROR "Target ${ARG_TARGET} is already defined.")
465    endif()
466
467    set(build_dir   "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBDIR}/${ARG_TARGET}")
468    set(build_out   "${build_dir}/build.out")
469    set(build_err   "${build_dir}/build.err")
470    set(binary_file "${build_dir}/test.abc")
471    set(launch_file "${build_dir}/verifier_launch.sh")
472    set(output_file "${build_dir}/verify.out")
473    set(error_file  "${build_dir}/verify.err")
474
475    file(MAKE_DIRECTORY "${build_dir}")
476
477    if (DEFINED ARG_OUTPUT_FILE_VARIABLE)
478        set(${ARG_OUTPUT_FILE_VARIABLE} "${output_file}" PARENT_SCOPE)
479    endif()
480
481    if (DEFINED ARG_ERROR_FILE_VARIABLE)
482        set(${ARG_ERROR_FILE_VARIABLE} "${error_file}" PARENT_SCOPE)
483    endif()
484
485    set(assembler ark_asm)
486    add_custom_command(OUTPUT "${binary_file}"
487                      COMMENT "Building ${ARG_TARGET}"
488                      COMMAND ${PANDA_RUN_PREFIX} $<TARGET_FILE:${assembler}> "${ARG_FILE}" "${binary_file}" 1>"${build_out}" 2>"${build_err}"
489                      DEPENDS ${assembler} "${ARG_FILE}")
490
491    if (DEFINED ARG_TIMEOUT AND NOT "${ARG_TIMEOUT}" STREQUAL "")
492        set(timeout_signal 10)
493        set(timeout_prefix "timeout --preserve-status --signal=${timeout_signal} --kill-after=10s ${ARG_TIMEOUT}")
494        set(timeout_suffix "|| [ `expr \$? % 128` -eq ${timeout_signal} ]")
495    else()
496        set(timeout_prefix "")
497        set(timeout_suffix "")
498    endif()
499
500    set(panda_stdlib arkstdlib)
501    set(verifier_cli verifier)
502
503    set(spaces "core")
504    if (NOT "core" STREQUAL "${ARG_LANGUAGE_CONTEXT}")
505        set(spaces "${ARG_LANGUAGE_CONTEXT}")
506    else()
507        set(stdlibs "${${panda_stdlib}_BINARY_DIR}/${panda_stdlib}.abc")
508    endif()
509
510    list(APPEND stdlibs ${ARG_STDLIBS})
511
512    string(REPLACE ";" ":" boot_stdlibs "${stdlibs}")
513    string(REPLACE ";" ":" boot_spaces  "${spaces}")
514
515    set(launcher_verifier
516        "${PANDA_RUN_PREFIX}"
517        $<TARGET_FILE:${verifier_cli}>
518        "${ARG_VERIFIER_OPTIONS}"
519        "${ARG_RUNTIME_OPTIONS}"
520        "--boot-panda-files=\"${boot_stdlibs}\""
521        "--load-runtimes=${boot_spaces}"
522        "\"${binary_file}\""
523        "1>\"${output_file}\""
524        "2>\"${error_file}\""
525    )
526    string(REPLACE ";" " " launcher_verifier "${launcher_verifier}")
527    if (ARG_VERIFIER_FAIL_TEST)
528        string(APPEND launcher_verifier " || (exit 0)")
529    endif()
530
531    file(GENERATE OUTPUT ${launch_file} CONTENT "${launcher_verifier}")
532
533    add_custom_target(${ARG_TARGET}
534                      COMMENT "Verifying ${ARG_TARGET}"
535                      COMMAND . ${launch_file}
536                      DEPENDS ${verifier_cli} "${binary_file}")
537
538    if (DEFINED ARG_DEPENDS)
539        add_dependencies(${ARG_TARGET} ${ARG_DEPENDS})
540    endif()
541
542endfunction()
543